mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-21 12:28:39 +00:00
cmd/tailscale/cli,version/distro: update support for Alpine (#8701)
Similar to Arch support, use the latest version info from the official `apk` repo and don't offer explicit track or version switching. Add detection for Alpine Linux in version/distro along the way. Updates #6995 Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
parent
6afffece8a
commit
306deea03a
@ -45,9 +45,10 @@ 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, "HIDDEN: 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, "HIDDEN: check the App Store for updates, even if this is not an App Store install (for testing only)")
|
||||||
// These flags are not supported on Arch-based installs. Arch only
|
// These flags are not supported on Arch or Alpine-based installs.
|
||||||
// offers one variant of tailscale and it's always the latest version.
|
// Package repos on these distros only offer one variant of tailscale
|
||||||
if distro.Get() != distro.Arch {
|
// and it's always the latest version.
|
||||||
|
if distro.Get() != distro.Arch && distro.Get() != distro.Alpine {
|
||||||
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" 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`)
|
||||||
}
|
}
|
||||||
@ -145,6 +146,8 @@ func newUpdater() (*updater, error) {
|
|||||||
up.update = up.updateDebLike
|
up.update = up.updateDebLike
|
||||||
case distro.Arch:
|
case distro.Arch:
|
||||||
up.update = up.updateArchLike
|
up.update = up.updateArchLike
|
||||||
|
case distro.Alpine:
|
||||||
|
up.update = up.updateAlpineLike
|
||||||
}
|
}
|
||||||
// TODO(awly): add support for Alpine
|
// TODO(awly): add support for Alpine
|
||||||
switch {
|
switch {
|
||||||
@ -158,6 +161,8 @@ func newUpdater() (*updater, error) {
|
|||||||
up.update = up.updateFedoraLike("dnf")
|
up.update = up.updateFedoraLike("dnf")
|
||||||
case haveExecutable("yum"):
|
case haveExecutable("yum"):
|
||||||
up.update = up.updateFedoraLike("yum")
|
up.update = up.updateFedoraLike("yum")
|
||||||
|
case haveExecutable("apk"):
|
||||||
|
up.update = up.updateAlpineLike
|
||||||
}
|
}
|
||||||
case "darwin":
|
case "darwin":
|
||||||
switch {
|
switch {
|
||||||
@ -462,6 +467,63 @@ func updateYUMRepoTrack(repoFile, dstTrack string) (rewrote bool, err error) {
|
|||||||
return true, os.WriteFile(repoFile, newContent.Bytes(), 0644)
|
return true, os.WriteFile(repoFile, newContent.Bytes(), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (up *updater) updateAlpineLike() (err error) {
|
||||||
|
if err := requireRoot(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil && !errors.Is(err, errUserAborted) {
|
||||||
|
err = fmt.Errorf(`%w; you can try updating using "apk upgrade tailscale"`, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
out, err := exec.Command("apk", "update").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed refresh apk repository indexes: %w, output: %q", err, out)
|
||||||
|
}
|
||||||
|
out, err = exec.Command("apk", "info", "tailscale").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed checking apk for latest tailscale version: %w, output: %q", err, out)
|
||||||
|
}
|
||||||
|
ver, err := parseAlpinePackageVersion(out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(`failed to parse latest version from "apk info tailscale": %w`, err)
|
||||||
|
}
|
||||||
|
if up.currentOrDryRun(ver) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := up.confirm(ver); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("apk", "upgrade", "tailscale")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed tailscale update using apk: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAlpinePackageVersion(out []byte) (string, error) {
|
||||||
|
s := bufio.NewScanner(bytes.NewReader(out))
|
||||||
|
for s.Scan() {
|
||||||
|
// The line should look like this:
|
||||||
|
// tailscale-1.44.2-r0 description:
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if !strings.HasPrefix(line, "tailscale-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(line, "-", 3)
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return "", fmt.Errorf("malformed info line: %q", line)
|
||||||
|
}
|
||||||
|
return parts[1], nil
|
||||||
|
}
|
||||||
|
return "", errors.New("tailscale version not found in output")
|
||||||
|
}
|
||||||
|
|
||||||
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.
|
||||||
|
@ -368,3 +368,75 @@ skip_if_unavailable=False
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseAlpinePackageVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
out string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "valid version",
|
||||||
|
out: `
|
||||||
|
tailscale-1.44.2-r0 description:
|
||||||
|
The easiest, most secure way to use WireGuard and 2FA
|
||||||
|
|
||||||
|
tailscale-1.44.2-r0 webpage:
|
||||||
|
https://tailscale.com/
|
||||||
|
|
||||||
|
tailscale-1.44.2-r0 installed size:
|
||||||
|
32 MiB
|
||||||
|
`,
|
||||||
|
want: "1.44.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "wrong package output",
|
||||||
|
out: `
|
||||||
|
busybox-1.36.1-r0 description:
|
||||||
|
Size optimized toolbox of many common UNIX utilities
|
||||||
|
|
||||||
|
busybox-1.36.1-r0 webpage:
|
||||||
|
https://busybox.net/
|
||||||
|
|
||||||
|
busybox-1.36.1-r0 installed size:
|
||||||
|
924 KiB
|
||||||
|
`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing version",
|
||||||
|
out: `
|
||||||
|
tailscale description:
|
||||||
|
The easiest, most secure way to use WireGuard and 2FA
|
||||||
|
|
||||||
|
tailscale webpage:
|
||||||
|
https://tailscale.com/
|
||||||
|
|
||||||
|
tailscale installed size:
|
||||||
|
32 MiB
|
||||||
|
`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty output",
|
||||||
|
out: "",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
got, err := parseAlpinePackageVersion([]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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@ const (
|
|||||||
Gokrazy = Distro("gokrazy")
|
Gokrazy = Distro("gokrazy")
|
||||||
WDMyCloud = Distro("wdmycloud")
|
WDMyCloud = Distro("wdmycloud")
|
||||||
Unraid = Distro("unraid")
|
Unraid = Distro("unraid")
|
||||||
|
Alpine = Distro("alpine")
|
||||||
)
|
)
|
||||||
|
|
||||||
var distro lazy.SyncValue[Distro]
|
var distro lazy.SyncValue[Distro]
|
||||||
@ -93,6 +94,8 @@ func linuxDistro() Distro {
|
|||||||
return WDMyCloud
|
return WDMyCloud
|
||||||
case have("/etc/unraid-version"):
|
case have("/etc/unraid-version"):
|
||||||
return Unraid
|
return Unraid
|
||||||
|
case have("/etc/alpine-release"):
|
||||||
|
return Alpine
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user