Add teleport-tools package for use with client tools managed updates (#58685)

* Add `teleport-tools` package to be used by client tools managed updates

* Add runtime check

* Update lib/autoupdate/tools/utils.go

Co-authored-by: Edoardo Spadolini <edoardo.spadolini@goteleport.com>

* Use general `version.EnsureSemver` for semantic version parsing

---------

Co-authored-by: Edoardo Spadolini <edoardo.spadolini@goteleport.com>
This commit is contained in:
Vadym Popov
2025-09-24 21:57:22 +00:00
committed by GitHub
co-authored by Edoardo Spadolini
parent 0920363895
commit 4c7ad913e2
3 changed files with 125 additions and 7 deletions
+2
View File
@@ -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" }}
+24 -7
View File
@@ -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
}
+99
View File
@@ -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"