mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
cmd/tailscale/cli: implement update for arch-based distros (#8655)
Arch version of tailscale is not maintained by us, but is generally up-to-date with our releases. Therefore "tailscale update" is just a thin wrapper around "pacman -Sy tailscale" with different flags. Updates #6995 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
parent
3f6b0d8c84
commit
efd6d90dd7
@ -45,8 +45,12 @@ var updateCmd = &ffcli.Command{
|
|||||||
fs.BoolVar(&updateArgs.yes, "yes", false, "update without interactive prompts")
|
fs.BoolVar(&updateArgs.yes, "yes", false, "update without interactive prompts")
|
||||||
fs.BoolVar(&updateArgs.dryRun, "dry-run", false, "print what update would do without doing it, or prompts")
|
fs.BoolVar(&updateArgs.dryRun, "dry-run", false, "print what update would do without doing it, or prompts")
|
||||||
fs.BoolVar(&updateArgs.appStore, "app-store", false, "check the App Store for updates, even if this is not an App Store install (for testing only!)")
|
fs.BoolVar(&updateArgs.appStore, "app-store", false, "check the App Store for updates, even if this is not an App Store install (for testing only!)")
|
||||||
fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable" or "unstable" (dev); empty means same as current`)
|
// These flags are not supported on Arch-based installs. Arch only
|
||||||
fs.StringVar(&updateArgs.version, "version", "", `explicit version to update/downgrade to`)
|
// offers one variant of tailscale and it's always the latest version.
|
||||||
|
if distro.Get() != distro.Arch {
|
||||||
|
fs.StringVar(&updateArgs.track, "track", "", `which track to check for updates: "stable" or "unstable" (dev); empty means same as current`)
|
||||||
|
fs.StringVar(&updateArgs.version, "version", "", `explicit version to update/downgrade to`)
|
||||||
|
}
|
||||||
return fs
|
return fs
|
||||||
})(),
|
})(),
|
||||||
}
|
}
|
||||||
@ -139,6 +143,8 @@ func newUpdater() (*updater, error) {
|
|||||||
up.update = up.updateSynology
|
up.update = up.updateSynology
|
||||||
case distro.Debian: // includes Ubuntu
|
case distro.Debian: // includes Ubuntu
|
||||||
up.update = up.updateDebLike
|
up.update = up.updateDebLike
|
||||||
|
case distro.Arch:
|
||||||
|
up.update = up.updateArch
|
||||||
}
|
}
|
||||||
case "darwin":
|
case "darwin":
|
||||||
switch {
|
switch {
|
||||||
@ -173,6 +179,8 @@ func (up *updater) currentOrDryRun(ver string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errUserAborted = errors.New("aborting update")
|
||||||
|
|
||||||
func (up *updater) confirm(ver string) error {
|
func (up *updater) confirm(ver string) error {
|
||||||
if updateArgs.yes {
|
if updateArgs.yes {
|
||||||
log.Printf("Updating Tailscale from %v to %v; --yes given, continuing without prompts.\n", version.Short(), ver)
|
log.Printf("Updating Tailscale from %v to %v; --yes given, continuing without prompts.\n", version.Short(), ver)
|
||||||
@ -187,7 +195,7 @@ func (up *updater) confirm(ver string) error {
|
|||||||
case "y", "yes", "sure":
|
case "y", "yes", "sure":
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("aborting update")
|
return errUserAborted
|
||||||
}
|
}
|
||||||
|
|
||||||
func (up *updater) updateSynology() error {
|
func (up *updater) updateSynology() error {
|
||||||
@ -326,6 +334,63 @@ func updateDebianAptSourcesListBytes(was []byte, dstTrack string) (newContent []
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (up *updater) updateArch() (err error) {
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
return errors.New("must be root; use sudo")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil && !errors.Is(err, errUserAborted) {
|
||||||
|
err = fmt.Errorf(`%w; you can try updating using "pacman --sync --refresh tailscale"`, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
out, err := exec.Command("pacman", "--sync", "--refresh", "--info", "tailscale").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed checking pacman for latest tailscale version: %w, output: %q", err, out)
|
||||||
|
}
|
||||||
|
ver, err := parsePacmanVersion(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if up.currentOrDryRun(ver) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := up.confirm(ver); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("pacman", "--sync", "--noconfirm", "tailscale")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed tailscale update using pacman: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePacmanVersion(out []byte) (string, error) {
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
// The line we're looking for looks like this:
|
||||||
|
// Version : 1.44.2-1
|
||||||
|
if !strings.HasPrefix(line, "Version") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", fmt.Errorf("version output from pacman is malformed: %q, cannot determine upgrade version", line)
|
||||||
|
}
|
||||||
|
ver := strings.TrimSpace(parts[1])
|
||||||
|
// Trim the Arch patch version.
|
||||||
|
ver = strings.Split(ver, "-")[0]
|
||||||
|
if ver == "" {
|
||||||
|
return "", fmt.Errorf("version output from pacman is malformed: %q, cannot determine upgrade version", line)
|
||||||
|
}
|
||||||
|
return ver, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("could not find latest version of tailscale via pacman")
|
||||||
|
}
|
||||||
|
|
||||||
func (up *updater) updateMacSys() error {
|
func (up *updater) updateMacSys() error {
|
||||||
// use sparkle? do we have permissions from this context? does sudo help?
|
// use sparkle? do we have permissions from this context? does sudo help?
|
||||||
// We can at least fail with a command they can run to update from the shell.
|
// We can at least fail with a command they can run to update from the shell.
|
||||||
|
@ -84,7 +84,7 @@ func TestParseSoftwareupdateList(t *testing.T) {
|
|||||||
name: "update-at-end-of-list",
|
name: "update-at-end-of-list",
|
||||||
input: []byte(`
|
input: []byte(`
|
||||||
Software Update Tool
|
Software Update Tool
|
||||||
|
|
||||||
Finding available software
|
Finding available software
|
||||||
Software Update found the following new or updated software:
|
Software Update found the following new or updated software:
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
* Label: MacBookAirEFIUpdate2.4-2.4
|
||||||
@ -100,7 +100,7 @@ func TestParseSoftwareupdateList(t *testing.T) {
|
|||||||
name: "update-in-middle-of-list",
|
name: "update-in-middle-of-list",
|
||||||
input: []byte(`
|
input: []byte(`
|
||||||
Software Update Tool
|
Software Update Tool
|
||||||
|
|
||||||
Finding available software
|
Finding available software
|
||||||
Software Update found the following new or updated software:
|
Software Update found the following new or updated software:
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
* Label: MacBookAirEFIUpdate2.4-2.4
|
||||||
@ -116,7 +116,7 @@ func TestParseSoftwareupdateList(t *testing.T) {
|
|||||||
name: "update-not-in-list",
|
name: "update-not-in-list",
|
||||||
input: []byte(`
|
input: []byte(`
|
||||||
Software Update Tool
|
Software Update Tool
|
||||||
|
|
||||||
Finding available software
|
Finding available software
|
||||||
Software Update found the following new or updated software:
|
Software Update found the following new or updated software:
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
* Label: MacBookAirEFIUpdate2.4-2.4
|
||||||
@ -130,7 +130,7 @@ func TestParseSoftwareupdateList(t *testing.T) {
|
|||||||
name: "decoy-in-list",
|
name: "decoy-in-list",
|
||||||
input: []byte(`
|
input: []byte(`
|
||||||
Software Update Tool
|
Software Update Tool
|
||||||
|
|
||||||
Finding available software
|
Finding available software
|
||||||
Software Update found the following new or updated software:
|
Software Update found the following new or updated software:
|
||||||
* Label: MacBookAirEFIUpdate2.4-2.4
|
* Label: MacBookAirEFIUpdate2.4-2.4
|
||||||
@ -151,3 +151,105 @@ func TestParseSoftwareupdateList(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParsePacmanVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
out string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid version",
|
||||||
|
out: `
|
||||||
|
:: Synchronizing package databases...
|
||||||
|
endeavouros is up to date
|
||||||
|
core is up to date
|
||||||
|
extra is up to date
|
||||||
|
multilib is up to date
|
||||||
|
Repository : extra
|
||||||
|
Name : tailscale
|
||||||
|
Version : 1.44.2-1
|
||||||
|
Description : A mesh VPN that makes it easy to connect your devices, wherever they are.
|
||||||
|
Architecture : x86_64
|
||||||
|
URL : https://tailscale.com
|
||||||
|
Licenses : MIT
|
||||||
|
Groups : None
|
||||||
|
Provides : None
|
||||||
|
Depends On : glibc
|
||||||
|
Optional Deps : None
|
||||||
|
Conflicts With : None
|
||||||
|
Replaces : None
|
||||||
|
Download Size : 7.98 MiB
|
||||||
|
Installed Size : 32.47 MiB
|
||||||
|
Packager : Christian Heusel <gromit@archlinux.org>
|
||||||
|
Build Date : Tue 18 Jul 2023 12:28:37 PM PDT
|
||||||
|
Validated By : MD5 Sum SHA-256 Sum Signature
|
||||||
|
`,
|
||||||
|
want: "1.44.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "version without Arch patch number",
|
||||||
|
out: `
|
||||||
|
... snip ...
|
||||||
|
Name : tailscale
|
||||||
|
Version : 1.44.2
|
||||||
|
Description : A mesh VPN that makes it easy to connect your devices, wherever they are.
|
||||||
|
... snip ...
|
||||||
|
`,
|
||||||
|
want: "1.44.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing version",
|
||||||
|
out: `
|
||||||
|
... snip ...
|
||||||
|
Name : tailscale
|
||||||
|
Description : A mesh VPN that makes it easy to connect your devices, wherever they are.
|
||||||
|
... snip ...
|
||||||
|
`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty version",
|
||||||
|
out: `
|
||||||
|
... snip ...
|
||||||
|
Name : tailscale
|
||||||
|
Version :
|
||||||
|
Description : A mesh VPN that makes it easy to connect your devices, wherever they are.
|
||||||
|
... snip ...
|
||||||
|
`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty input",
|
||||||
|
out: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "sneaky version in description",
|
||||||
|
out: `
|
||||||
|
... snip ...
|
||||||
|
Name : tailscale
|
||||||
|
Description : A mesh VPN that makes it easy to connect your devices, wherever they are. Version : 1.2.3
|
||||||
|
Version : 1.44.2
|
||||||
|
... snip ...
|
||||||
|
`,
|
||||||
|
want: "1.44.2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
got, err := parsePacmanVersion([]byte(tt.out))
|
||||||
|
if err == nil && tt.wantErr {
|
||||||
|
t.Fatalf("got nil error and version %q, want non-nil error", got)
|
||||||
|
}
|
||||||
|
if err != nil && !tt.wantErr {
|
||||||
|
t.Fatalf("got error: %q, want nil", err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("got version: %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user