Skip to content

Commit

Permalink
Add template for client tools auto-update download url (#51210)
Browse files Browse the repository at this point in the history
* Add templates for client tools auto-update download url

* Change to base url setting by env

MakeURL moved to common function to be general for both, agent and client tools

* Add godoc for common function and constant for default package

* Use flags and version arguments instead of revision

* Move base url env to shared constant
  • Loading branch information
vapopov authored Jan 24, 2025
1 parent 63537e3 commit 4ffaa89
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 47 deletions.
8 changes: 4 additions & 4 deletions integration/autoupdate/tools/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,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 @@ -131,7 +131,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 @@ -141,7 +141,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 @@ -159,7 +159,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.IsEnterpriseBuild() || 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.IsEnterpriseBuild() || 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

0 comments on commit 4ffaa89

Please sign in to comment.