clientupdate, cmd/tailscale/cli: support updating to release-candidates (#18632)

Adds a new track for release candidates. Supports querying by track in
version and updating to RCs in update for supported platforms.

updates #18193

Signed-off-by: Will Hannah <willh@tailscale.com>
main
Will Hannah 2 months ago committed by GitHub
parent 0bac4223d1
commit 36d359e585
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 19
      clientupdate/clientupdate.go
  2. 95
      clientupdate/clientupdate_test.go
  3. 7
      cmd/tailscale/cli/update.go
  4. 6
      cmd/tailscale/cli/version.go

@ -40,6 +40,7 @@ import (
const ( const (
StableTrack = "stable" StableTrack = "stable"
UnstableTrack = "unstable" UnstableTrack = "unstable"
ReleaseCandidateTrack = "release-candidate"
) )
var CurrentTrack = func() string { var CurrentTrack = func() string {
@ -80,6 +81,8 @@ type Arguments struct {
// running binary // running binary
// - StableTrack and UnstableTrack will use the latest versions of the // - StableTrack and UnstableTrack will use the latest versions of the
// corresponding tracks // corresponding tracks
// - ReleaseCandidateTrack will use the newest version from StableTrack
// and ReleaseCandidateTrack.
// //
// Leaving this empty will use Version or fall back to CurrentTrack if both // Leaving this empty will use Version or fall back to CurrentTrack if both
// Track and Version are empty. // Track and Version are empty.
@ -114,7 +117,7 @@ func (args Arguments) validate() error {
return fmt.Errorf("only one of Version(%q) or Track(%q) can be set", args.Version, args.Track) return fmt.Errorf("only one of Version(%q) or Track(%q) can be set", args.Version, args.Track)
} }
switch args.Track { switch args.Track {
case StableTrack, UnstableTrack, "": case StableTrack, UnstableTrack, ReleaseCandidateTrack, "":
// All valid values. // All valid values.
default: default:
return fmt.Errorf("unsupported track %q", args.Track) return fmt.Errorf("unsupported track %q", args.Track)
@ -496,10 +499,10 @@ func (up *Updater) updateDebLike() error {
const aptSourcesFile = "/etc/apt/sources.list.d/tailscale.list" const aptSourcesFile = "/etc/apt/sources.list.d/tailscale.list"
// updateDebianAptSourcesList updates the /etc/apt/sources.list.d/tailscale.list // updateDebianAptSourcesList updates the /etc/apt/sources.list.d/tailscale.list
// file to make sure it has the provided track (stable or unstable) in it. // file to make sure it has the provided track (stable, unstable, or release-candidate) in it.
// //
// If it already has the right track (including containing both stable and // If it already has the right track (including containing both stable,
// unstable), it does nothing. // unstable, and release-candidate), it does nothing.
func updateDebianAptSourcesList(dstTrack string) (rewrote bool, err error) { func updateDebianAptSourcesList(dstTrack string) (rewrote bool, err error) {
was, err := os.ReadFile(aptSourcesFile) was, err := os.ReadFile(aptSourcesFile)
if err != nil { if err != nil {
@ -522,7 +525,7 @@ func updateDebianAptSourcesListBytes(was []byte, dstTrack string) (newContent []
bs := bufio.NewScanner(bytes.NewReader(was)) bs := bufio.NewScanner(bytes.NewReader(was))
hadCorrect := false hadCorrect := false
commentLine := regexp.MustCompile(`^\s*\#`) commentLine := regexp.MustCompile(`^\s*\#`)
pkgsURL := regexp.MustCompile(`\bhttps://pkgs\.tailscale\.com/((un)?stable)/`) pkgsURL := regexp.MustCompile(`\bhttps://pkgs\.tailscale\.com/(stable|unstable|release-candidate)/`)
for bs.Scan() { for bs.Scan() {
line := bs.Bytes() line := bs.Bytes()
if !commentLine.Match(line) { if !commentLine.Match(line) {
@ -616,15 +619,15 @@ func (up *Updater) updateFedoraLike(packageManager string) func() error {
} }
// updateYUMRepoTrack updates the repoFile file to make sure it has the // updateYUMRepoTrack updates the repoFile file to make sure it has the
// provided track (stable or unstable) in it. // provided track (stable, unstable, or release-candidate) in it.
func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) { func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) {
was, err := os.ReadFile(repoFile) was, err := os.ReadFile(repoFile)
if err != nil { if err != nil {
return false, err return false, err
} }
urlRe := regexp.MustCompile(`^(baseurl|gpgkey)=https://pkgs\.tailscale\.com/(un)?stable/`) urlRe := regexp.MustCompile(`^(baseurl|gpgkey)=https://pkgs\.tailscale\.com/(stable|unstable|release-candidate)`)
urlReplacement := fmt.Sprintf("$1=https://pkgs.tailscale.com/%s/", dstTrack) urlReplacement := fmt.Sprintf("$1=https://pkgs.tailscale.com/%s", dstTrack)
s := bufio.NewScanner(bytes.NewReader(was)) s := bufio.NewScanner(bytes.NewReader(was))
newContent := bytes.NewBuffer(make([]byte, 0, len(was))) newContent := bytes.NewBuffer(make([]byte, 0, len(was)))

@ -86,18 +86,8 @@ func TestUpdateDebianAptSourcesListBytes(t *testing.T) {
} }
} }
func TestUpdateYUMRepoTrack(t *testing.T) { var YUMRepos = map[string]string{
tests := []struct { StableTrack: `
desc string
before string
track string
after string
rewrote bool
wantErr bool
}{
{
desc: "same track",
before: `
[tailscale-stable] [tailscale-stable]
name=Tailscale stable name=Tailscale stable
baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch
@ -107,46 +97,30 @@ repo_gpgcheck=1
gpgcheck=0 gpgcheck=0
gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg
`, `,
track: StableTrack,
after: ` UnstableTrack: `
[tailscale-stable] [tailscale-unstable]
name=Tailscale stable name=Tailscale unstable
baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch baseurl=https://pkgs.tailscale.com/unstable/fedora/$basearch
enabled=1
type=rpm
repo_gpgcheck=1
gpgcheck=0
gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg
`,
},
{
desc: "change track",
before: `
[tailscale-stable]
name=Tailscale stable
baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch
enabled=1 enabled=1
type=rpm type=rpm
repo_gpgcheck=1 repo_gpgcheck=1
gpgcheck=0 gpgcheck=0
gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg gpgkey=https://pkgs.tailscale.com/unstable/fedora/repo.gpg
`, `,
track: UnstableTrack,
after: ` ReleaseCandidateTrack: `
[tailscale-unstable] [tailscale-release-candidate]
name=Tailscale unstable name=Tailscale release-candidate
baseurl=https://pkgs.tailscale.com/unstable/fedora/$basearch baseurl=https://pkgs.tailscale.com/release-candidate/fedora/$basearch
enabled=1 enabled=1
type=rpm type=rpm
repo_gpgcheck=1 repo_gpgcheck=1
gpgcheck=0 gpgcheck=0
gpgkey=https://pkgs.tailscale.com/unstable/fedora/repo.gpg gpgkey=https://pkgs.tailscale.com/release-candidate/fedora/repo.gpg
`, `,
rewrote: true,
}, "FakeRepo": `
{
desc: "non-tailscale repo file",
before: `
[fedora] [fedora]
name=Fedora $releasever - $basearch name=Fedora $releasever - $basearch
#baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/ #baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/
@ -158,8 +132,41 @@ repo_gpgcheck=0
type=rpm type=rpm
gpgcheck=1 gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False skip_if_unavailable=False`,
`, }
func TestUpdateYUMRepoTrack(t *testing.T) {
tests := []struct {
desc string
before string
track string
after string
rewrote bool
wantErr bool
}{
{
desc: "same track",
before: YUMRepos[StableTrack],
track: StableTrack,
after: YUMRepos[StableTrack],
},
{
desc: "change track",
before: YUMRepos[StableTrack],
track: UnstableTrack,
after: YUMRepos[UnstableTrack],
rewrote: true,
},
{
desc: "change track RC",
before: YUMRepos[StableTrack],
track: ReleaseCandidateTrack,
after: YUMRepos[ReleaseCandidateTrack],
rewrote: true,
},
{
desc: "non-tailscale repo file",
before: YUMRepos["FakeRepo"],
track: StableTrack, track: StableTrack,
wantErr: true, wantErr: true,
}, },

@ -22,8 +22,11 @@ import (
func init() { func init() {
maybeUpdateCmd = func() *ffcli.Command { return updateCmd } maybeUpdateCmd = func() *ffcli.Command { return updateCmd }
clientupdateLatestTailscaleVersion.Set(func() (string, error) { clientupdateLatestTailscaleVersion.Set(func(track string) (string, error) {
if track == "" {
return clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack) return clientupdate.LatestTailscaleVersion(clientupdate.CurrentTrack)
}
return clientupdate.LatestTailscaleVersion(track)
}) })
} }
@ -50,7 +53,7 @@ var updateCmd = &ffcli.Command{
distro.Get() != distro.Synology && distro.Get() != distro.Synology &&
runtime.GOOS != "freebsd" && runtime.GOOS != "freebsd" &&
runtime.GOOS != "darwin" { runtime.GOOS != "darwin" {
fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable" or "unstable" (dev); empty means same as current`) fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable", "release-candidate", or "unstable" (dev); empty means same as current`)
fs.StringVar(&updateArgs.version, "version", "", `explicit version to update/downgrade to`) fs.StringVar(&updateArgs.version, "version", "", `explicit version to update/downgrade to`)
} }
return fs return fs

@ -24,6 +24,7 @@ var versionCmd = &ffcli.Command{
fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version") fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version")
fs.BoolVar(&versionArgs.json, "json", false, "output in JSON format") fs.BoolVar(&versionArgs.json, "json", false, "output in JSON format")
fs.BoolVar(&versionArgs.upstream, "upstream", false, "fetch and print the latest upstream release version from pkgs.tailscale.com") fs.BoolVar(&versionArgs.upstream, "upstream", false, "fetch and print the latest upstream release version from pkgs.tailscale.com")
fs.StringVar(&versionArgs.track, "track", "", `which track to check for updates: "stable", "release-candidate", or "unstable" (dev); empty means same as current`)
return fs return fs
})(), })(),
Exec: runVersion, Exec: runVersion,
@ -33,9 +34,10 @@ var versionArgs struct {
daemon bool // also check local node's daemon version daemon bool // also check local node's daemon version
json bool json bool
upstream bool upstream bool
track string
} }
var clientupdateLatestTailscaleVersion feature.Hook[func() (string, error)] var clientupdateLatestTailscaleVersion feature.Hook[func(string) (string, error)]
func runVersion(ctx context.Context, args []string) error { func runVersion(ctx context.Context, args []string) error {
if len(args) > 0 { if len(args) > 0 {
@ -57,7 +59,7 @@ func runVersion(ctx context.Context, args []string) error {
if !ok { if !ok {
return fmt.Errorf("fetching latest version not supported in this build") return fmt.Errorf("fetching latest version not supported in this build")
} }
upstreamVer, err = f() upstreamVer, err = f(versionArgs.track)
if err != nil { if err != nil {
return err return err
} }

Loading…
Cancel
Save