diff --git a/lib/autoupdate/package_url.go b/lib/autoupdate/package_url.go index b00eb59fea5..d0da9a46160 100644 --- a/lib/autoupdate/package_url.go +++ b/lib/autoupdate/package_url.go @@ -43,6 +43,8 @@ const ( DefaultBaseURL = "https://cdn.teleport.dev" // DefaultPackage is the name of Teleport package. DefaultPackage = "teleport" + // DefaultToolsPackage is the name of separate Teleport package with client tools only (tsh, tctl). + DefaultToolsPackage = "teleport-tools" // DefaultCDNURITemplate is the default template for the Teleport CDN download URL. DefaultCDNURITemplate = `{{ .BaseURL }}/ {{- if eq .OS "darwin" }} diff --git a/lib/autoupdate/tools/utils.go b/lib/autoupdate/tools/utils.go index 2c963dee647..9f596188d3e 100644 --- a/lib/autoupdate/tools/utils.go +++ b/lib/autoupdate/tools/utils.go @@ -37,6 +37,7 @@ import ( "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/automaticupgrades/version" "github.com/gravitational/teleport/lib/autoupdate" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/utils" @@ -200,7 +201,12 @@ type packageURL struct { } // teleportPackageURLs returns URLs for the Teleport archives to download. -func teleportPackageURLs(ctx context.Context, uriTmpl string, baseURL, version string) ([]packageURL, error) { +func teleportPackageURLs(ctx context.Context, uriTmpl string, baseURL, requestedVersion string) ([]packageURL, error) { + semVersion, err := version.EnsureSemver(requestedVersion) + if err != nil { + return nil, trace.BadParameter("version %q is not following semver", requestedVersion) + } + m := modules.GetModules() envBaseURL := os.Getenv(autoupdate.BaseURLEnvVar) if m.BuildType() == modules.BuildOSS && envBaseURL == "" { @@ -216,24 +222,35 @@ func teleportPackageURLs(ctx context.Context, uriTmpl string, baseURL, version s flags |= autoupdate.FlagEnterprise } - teleportURL, err := autoupdate.MakeURL(uriTmpl, baseURL, autoupdate.DefaultPackage, version, flags) + // TODO(vapopov): DELETE in v22.0.0 version check - the separate `teleport-tools` package + // will be included in all supported versions. + pkg := autoupdate.DefaultPackage + if runtime.GOOS == constants.DarwinOS && + (semVersion.Major > 18 || + semVersion.Major == 18 && semVersion.Compare(semver.Version{Major: 18, Minor: 1, Patch: 5}) >= 0 || + semVersion.Major == 17 && semVersion.Compare(semver.Version{Major: 17, Minor: 7, Patch: 2}) >= 0) { + pkg = autoupdate.DefaultToolsPackage + } + + teleportURL, err := autoupdate.MakeURL(uriTmpl, baseURL, pkg, requestedVersion, flags) if err != nil { return nil, trace.Wrap(err) } - if runtime.GOOS == constants.DarwinOS { - tshURL, err := autoupdate.MakeURL(uriTmpl, baseURL, "tsh", version, flags) + // TODO(vapopov): DELETE in v20.0.0 - the separate `tsh` package will no longer be supported. + if runtime.GOOS == constants.DarwinOS && semVersion.Major < 17 { + tshURL, err := autoupdate.MakeURL(uriTmpl, baseURL, "tsh", requestedVersion, flags) if err != nil { return nil, trace.Wrap(err) } return []packageURL{ - {Version: version, Archive: teleportURL, Hash: teleportURL + ".sha256"}, - {Version: version, Archive: tshURL, Hash: tshURL + ".sha256", Optional: true}, + {Version: requestedVersion, Archive: teleportURL, Hash: teleportURL + ".sha256"}, + {Version: requestedVersion, Archive: tshURL, Hash: tshURL + ".sha256", Optional: true}, }, nil } return []packageURL{ - {Version: version, Archive: teleportURL, Hash: teleportURL + ".sha256"}, + {Version: requestedVersion, Archive: teleportURL, Hash: teleportURL + ".sha256"}, }, nil } diff --git a/lib/autoupdate/tools/utils_test.go b/lib/autoupdate/tools/utils_test.go index 0d5904bb526..3a95ceb0d58 100644 --- a/lib/autoupdate/tools/utils_test.go +++ b/lib/autoupdate/tools/utils_test.go @@ -19,12 +19,111 @@ package tools import ( + "context" "fmt" + "runtime" + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/lib/autoupdate" + "github.com/gravitational/teleport/lib/modules" + "github.com/gravitational/teleport/lib/modules/modulestest" ) +func TestTeleportPackageURLs(t *testing.T) { + currentModules := modules.GetModules() + t.Cleanup(func() { modules.SetModules(currentModules) }) + modules.SetModules(&modulestest.Modules{TestBuildType: modules.BuildCommunity}) + + ctx := context.Background() + + type expected struct { + archivePrefix string + optional bool + } + + for _, tt := range []struct { + name string + version string + expected func() []expected + }{ + { + name: "v17", + version: "17.0.0", + expected: func() []expected { + return []expected{{archivePrefix: "/teleport-", optional: false}} + }, + }, + { + name: "v17-latest", + version: "17.7.2", + expected: func() []expected { + if runtime.GOOS == constants.DarwinOS { + return []expected{{archivePrefix: "/teleport-tools-", optional: false}} + } + return []expected{{archivePrefix: "/teleport-", optional: false}} + }, + }, + { + name: "v18", + version: "18.0.0", + expected: func() []expected { + return []expected{{archivePrefix: "/teleport-", optional: false}} + }, + }, + { + name: "v18-latest", + version: "18.1.5", + expected: func() []expected { + if runtime.GOOS == constants.DarwinOS { + return []expected{{archivePrefix: "/teleport-tools-", optional: false}} + } + return []expected{{archivePrefix: "/teleport-", optional: false}} + }, + }, + { + name: "v19", + version: "19.0.0", + expected: func() []expected { + if runtime.GOOS == constants.DarwinOS { + return []expected{{archivePrefix: "/teleport-tools-", optional: false}} + } + return []expected{{archivePrefix: "/teleport-", optional: false}} + }, + }, + { + name: "v16", + version: "16.0.0", + expected: func() []expected { + if runtime.GOOS == constants.DarwinOS { + return []expected{ + {archivePrefix: "/teleport-", optional: false}, + {archivePrefix: "/tsh-", optional: true}, + } + } + return []expected{{archivePrefix: "/teleport-", optional: false}} + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + pkgURLs, err := teleportPackageURLs(ctx, autoupdate.DefaultCDNURITemplate, "", tt.version) + require.NoError(t, err) + + exp := tt.expected() + require.Len(t, pkgURLs, len(exp)) + for i := range pkgURLs { + assert.True(t, strings.HasPrefix(pkgURLs[i].Archive, exp[i].archivePrefix)) + assert.Equal(t, exp[i].optional, pkgURLs[i].Optional) + assert.Equal(t, tt.version, pkgURLs[i].Version) + } + }) + } +} + // TestFilterEnv verifies excluding environment variables by the list of the keys. func TestFilterEnv(t *testing.T) { env := "TEST_ENV_WITHOUT_FILTER"