Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v15] Add template for client tools auto-update download url #51482

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions integration/autoupdate/tools/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestMain(m *testing.M) {
}))
baseURL = server.URL
for _, version := range testVersions {
if err := buildAndArchiveApps(ctx, tmp, toolsDir, version, server.URL); err != nil {
if err := buildAndArchiveApps(ctx, tmp, version, server.URL); err != nil {
log.Fatalf("failed to build testing app binary archive: %v", err)
}
}
Expand Down Expand Up @@ -129,7 +129,7 @@ func serve256File(w http.ResponseWriter, _ *http.Request, filePath string) {
}

// buildAndArchiveApps compiles the updater integration and pack it depends on platform is used.
func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, version string, baseURL string) error {
func buildAndArchiveApps(ctx context.Context, path string, version string, baseURL string) error {
versionPath := filepath.Join(path, version)
for _, app := range []string{"tsh", "tctl"} {
output := filepath.Join(versionPath, app)
Expand All @@ -139,7 +139,7 @@ func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, vers
case constants.DarwinOS:
output = filepath.Join(versionPath, app+".app", "Contents", "MacOS", app)
}
if err := buildBinary(output, toolsDir, version, baseURL, app); err != nil {
if err := buildBinary(output, version, baseURL, app); err != nil {
return trace.Wrap(err)
}
}
Expand All @@ -157,7 +157,7 @@ func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, vers
}

// buildBinary executes command to build client tool binary with updater logic for testing.
func buildBinary(output string, toolsDir string, version string, baseURL string, app string) error {
func buildBinary(output string, version string, baseURL string, app string) error {
cmd := exec.Command(
"go", "build", "-o", output,
"-ldflags", strings.Join([]string{
Expand Down
82 changes: 82 additions & 0 deletions lib/autoupdate/package_url.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package autoupdate

import (
"bytes"
"runtime"
"text/template"

"github.com/gravitational/trace"
)

// InstallFlags sets flags for the Teleport installation.
type InstallFlags int

const (
// FlagEnterprise installs enterprise Teleport.
FlagEnterprise InstallFlags = 1 << iota
// FlagFIPS installs FIPS Teleport
FlagFIPS
)

const (
// DefaultBaseURL is CDN URL for downloading official Teleport packages.
DefaultBaseURL = "https://cdn.teleport.dev"
// DefaultPackage is the name of Teleport package.
DefaultPackage = "teleport"
// DefaultCDNURITemplate is the default template for the Teleport CDN download URL.
DefaultCDNURITemplate = `{{ .BaseURL }}/
{{- if eq .OS "darwin" }}
{{- .Package }}{{ if and .Enterprise (eq .Package "teleport") }}-ent{{ end }}-{{ .Version }}.pkg
{{- else if eq .OS "windows" }}
{{- .Package }}-v{{ .Version }}-{{ .OS }}-amd64-bin.zip
{{- else }}
{{- .Package }}{{ if .Enterprise }}-ent{{ end }}-v{{ .Version }}-{{ .OS }}-{{ .Arch }}{{ if .FIPS }}-fips{{ end }}-bin.tar.gz
{{- end }}`
// BaseURLEnvVar allows to override base URL for the Teleport package URL via env var.
BaseURLEnvVar = "TELEPORT_CDN_BASE_URL"
)

// MakeURL constructs the package download URL from template, base URL and revision.
func MakeURL(uriTmpl string, baseURL string, pkg string, version string, flags InstallFlags) (string, error) {
tmpl, err := template.New("uri").Parse(uriTmpl)
if err != nil {
return "", trace.Wrap(err)
}
var uriBuf bytes.Buffer
params := struct {
BaseURL, OS, Version, Arch, Package string
FIPS, Enterprise bool
}{
BaseURL: baseURL,
OS: runtime.GOOS,
Version: version,
Arch: runtime.GOARCH,
FIPS: flags&FlagFIPS != 0,
Enterprise: flags&(FlagEnterprise|FlagFIPS) != 0,
Package: pkg,
}
err = tmpl.Execute(&uriBuf, params)
if err != nil {
return "", trace.Wrap(err)
}

return uriBuf.String(), nil
}
18 changes: 12 additions & 6 deletions lib/autoupdate/tools/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/gravitational/trace"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/autoupdate"
stacksignal "github.com/gravitational/teleport/lib/utils/signal"
)

Expand All @@ -35,7 +36,7 @@ var (
// version is the current version of the Teleport.
version = teleport.Version
// baseURL is CDN URL for downloading official Teleport packages.
baseURL = defaultBaseURL
baseURL = autoupdate.DefaultBaseURL
)

// CheckAndUpdateLocal verifies if the TELEPORT_TOOLS_VERSION environment variable
Expand All @@ -52,6 +53,11 @@ func CheckAndUpdateLocal(ctx context.Context, reExecArgs []string) error {
return nil
}

// Overrides default base URL for custom CDN for downloading updates.
if envBaseURL := os.Getenv(autoupdate.BaseURLEnvVar); envBaseURL != "" {
baseURL = envBaseURL
}

updater := NewUpdater(toolsDir, version, WithBaseURL(baseURL))
// At process startup, check if a version has already been downloaded to
// $TELEPORT_HOME/bin or if the user has set the TELEPORT_TOOLS_VERSION
Expand Down Expand Up @@ -80,12 +86,12 @@ func CheckAndUpdateRemote(ctx context.Context, proxy string, insecure bool, reEx
slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
return nil
}
// Overrides default base URL for custom CDN for downloading updates.
if envBaseURL := os.Getenv(autoupdate.BaseURLEnvVar); envBaseURL != "" {
baseURL = envBaseURL
}

updater := NewUpdater(toolsDir, version, WithBaseURL(baseURL))
// The user has typed a command like `tsh ssh ...` without being logged in,
// if the running binary needs to be updated, update and re-exec.
//
// If needed, download the new version of client tools and re-exec. Make
// sure to exit this process with the same exit code as the child process.
toolsVersion, reExec, err := updater.CheckRemote(ctx, proxy, insecure)
if err != nil {
return trace.Wrap(err)
Expand Down
20 changes: 14 additions & 6 deletions lib/autoupdate/tools/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,14 @@ import (

"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/lib/autoupdate"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/packaging"
)

const (
// teleportToolsVersionEnv is environment name for requesting specific version for update.
teleportToolsVersionEnv = "TELEPORT_TOOLS_VERSION"
// defaultBaseURL is CDN URL for downloading official Teleport packages.
defaultBaseURL = "https://cdn.teleport.dev"
// reservedFreeDisk is the predefined amount of free disk space (in bytes) required
// to remain available after downloading archives.
reservedFreeDisk = 10 * 1024 * 1024 // 10 Mb
Expand All @@ -75,6 +74,13 @@ func WithBaseURL(baseURL string) Option {
}
}

// WithURITemplate defines custom URI template for the updater.
func WithURITemplate(uriTemplate string) Option {
return func(u *Updater) {
u.uriTemplate = uriTemplate
}
}

// WithClient defines custom http client for the Updater.
func WithClient(client *http.Client) Option {
return func(u *Updater) {
Expand All @@ -94,9 +100,10 @@ type Updater struct {
toolsDir string
localVersion string
tools []string
uriTemplate string
baseURL string

baseURL string
client *http.Client
client *http.Client
}

// NewUpdater initializes the updater for client tools auto updates. We need to specify the tools directory
Expand All @@ -109,7 +116,8 @@ func NewUpdater(toolsDir, localVersion string, options ...Option) *Updater {
tools: DefaultClientTools(),
toolsDir: toolsDir,
localVersion: localVersion,
baseURL: defaultBaseURL,
uriTemplate: autoupdate.DefaultCDNURITemplate,
baseURL: autoupdate.DefaultBaseURL,
client: http.DefaultClient,
}
for _, option := range options {
Expand Down Expand Up @@ -255,7 +263,7 @@ func (u *Updater) UpdateWithLock(ctx context.Context, updateToolsVersion string)
// with defined updater directory suffix.
func (u *Updater) Update(ctx context.Context, toolsVersion string) error {
// Get platform specific download URLs.
packages, err := teleportPackageURLs(u.baseURL, toolsVersion)
packages, err := teleportPackageURLs(u.uriTemplate, u.baseURL, toolsVersion)
if err != nil {
return trace.Wrap(err)
}
Expand Down
58 changes: 27 additions & 31 deletions lib/autoupdate/tools/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (

"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/autoupdate"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/utils"
)
Expand Down Expand Up @@ -125,41 +126,36 @@ type packageURL struct {
Optional bool
}

// teleportPackageURLs returns the URL for the Teleport archive to download. The format is:
// https://cdn.teleport.dev/teleport-{, ent-}v15.3.0-{linux, darwin, windows}-{amd64,arm64,arm,386}-{fips-}bin.tar.gz
func teleportPackageURLs(baseURL, toolsVersion string) ([]packageURL, error) {
switch runtime.GOOS {
case "darwin":
tsh := baseURL + "/tsh-" + toolsVersion + ".pkg"
teleport := baseURL + "/teleport-" + toolsVersion + ".pkg"
return []packageURL{
{Archive: teleport, Hash: teleport + ".sha256"},
{Archive: tsh, Hash: tsh + ".sha256", Optional: true},
}, nil
case "windows":
archive := baseURL + "/teleport-v" + toolsVersion + "-windows-amd64-bin.zip"
return []packageURL{
{Archive: archive, Hash: archive + ".sha256"},
}, nil
case "linux":
m := modules.GetModules()
var b strings.Builder
b.WriteString(baseURL + "/teleport-")
if m.BuildType() == modules.BuildEnterprise || m.IsBoringBinary() {
b.WriteString("ent-")
}
b.WriteString("v" + toolsVersion + "-" + runtime.GOOS + "-" + runtime.GOARCH + "-")
if m.IsBoringBinary() {
b.WriteString("fips-")
// teleportPackageURLs returns the URL for the Teleport archive to download.
func teleportPackageURLs(uriTmpl string, baseURL, version string) ([]packageURL, error) {
var flags autoupdate.InstallFlags
m := modules.GetModules()
if m.IsBoringBinary() {
flags |= autoupdate.FlagFIPS
}
if m.BuildType() == modules.BuildEnterprise || m.IsBoringBinary() {
flags |= autoupdate.FlagEnterprise
}

teleportURL, err := autoupdate.MakeURL(uriTmpl, baseURL, autoupdate.DefaultPackage, version, flags)
if err != nil {
return nil, trace.Wrap(err)
}
if runtime.GOOS == constants.DarwinOS {
tshURL, err := autoupdate.MakeURL(uriTmpl, baseURL, "tsh", version, flags)
if err != nil {
return nil, trace.Wrap(err)
}
b.WriteString("bin.tar.gz")
archive := b.String()

return []packageURL{
{Archive: archive, Hash: archive + ".sha256"},
{Archive: teleportURL, Hash: teleportURL + ".sha256"},
{Archive: tshURL, Hash: tshURL + ".sha256", Optional: true},
}, nil
default:
return nil, trace.BadParameter("unsupported runtime: %v", runtime.GOOS)
}

return []packageURL{
{Archive: teleportURL, Hash: teleportURL + ".sha256"},
}, nil
}

// toolName returns the path to {tsh, tctl} for the executable that started
Expand Down
Loading