diff --git a/integration/autoupdate/tools/main_test.go b/integration/autoupdate/tools/main_test.go
index bbc3f559f65c0..07d1263a91811 100644
--- a/integration/autoupdate/tools/main_test.go
+++ b/integration/autoupdate/tools/main_test.go
@@ -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); err != nil {
+ if err := buildBinary(output, toolsDir, version, baseURL, app); err != nil {
return trace.Wrap(err)
}
}
@@ -156,16 +156,16 @@ func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, vers
}
}
-// buildBinary executes command to build binary with updater logic only for testing.
-func buildBinary(output string, toolsDir string, version string, baseURL string) error {
+// 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 {
cmd := exec.Command(
"go", "build", "-o", output,
"-ldflags", strings.Join([]string{
- fmt.Sprintf("-X 'main.toolsDir=%s'", toolsDir),
- fmt.Sprintf("-X 'main.version=%s'", version),
- fmt.Sprintf("-X 'main.baseURL=%s'", baseURL),
+ fmt.Sprintf("-X 'github.com/gravitational/teleport/integration/autoupdate/tools/updater.version=%s'", version),
+ fmt.Sprintf("-X 'github.com/gravitational/teleport/lib/autoupdate/tools.version=%s'", version),
+ fmt.Sprintf("-X 'github.com/gravitational/teleport/lib/autoupdate/tools.baseURL=%s'", baseURL),
}, " "),
- "./updater",
+ fmt.Sprintf("./updater/%s", app),
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
diff --git a/integration/autoupdate/tools/updater/main.go b/integration/autoupdate/tools/updater/main.go
deleted file mode 100644
index 8a12dbbd1c9f7..0000000000000
--- a/integration/autoupdate/tools/updater/main.go
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2024 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 .
- */
-
-package main
-
-import (
- "context"
- "errors"
- "fmt"
- "log"
- "os"
- "os/signal"
- "syscall"
- "time"
-
- "github.com/gravitational/teleport/lib/autoupdate/tools"
-)
-
-var (
- version = "development"
- baseURL = "http://localhost"
- toolsDir = ""
-)
-
-func main() {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
- defer cancel()
- ctx, _ = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
-
- updater := tools.NewUpdater(
- toolsDir,
- version,
- tools.WithBaseURL(baseURL),
- )
- toolsVersion, reExec, err := updater.CheckLocal()
- if err != nil {
- log.Fatal(err)
- }
- if reExec {
- // Download and update the version of client tools required by the cluster.
- // This is required if the user passed in the TELEPORT_TOOLS_VERSION explicitly.
- err := updater.UpdateWithLock(ctx, toolsVersion)
- if errors.Is(err, context.Canceled) {
- os.Exit(0)
- return
- }
- if err != nil {
- log.Fatalf("failed to download version (%v): %v\n", toolsVersion, err)
- return
- }
-
- // Re-execute client tools with the correct version of client tools.
- code, err := updater.Exec()
- if err != nil {
- log.Fatalf("Failed to re-exec client tool: %v\n", err)
- } else {
- os.Exit(code)
- }
- }
- if len(os.Args) > 1 && os.Args[1] == "version" {
- fmt.Printf("Teleport v%v git\n", version)
- }
-}
diff --git a/integration/autoupdate/tools/updater/modules.go b/integration/autoupdate/tools/updater/modules.go
new file mode 100644
index 0000000000000..5b426f4bc9a39
--- /dev/null
+++ b/integration/autoupdate/tools/updater/modules.go
@@ -0,0 +1,104 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 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 .
+ */
+
+package updater
+
+import (
+ "context"
+ "crypto"
+ "fmt"
+ "time"
+
+ "github.com/gravitational/trace"
+
+ "github.com/gravitational/teleport"
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/api/types/accesslist"
+ "github.com/gravitational/teleport/api/utils/keys"
+ "github.com/gravitational/teleport/lib/modules"
+ "github.com/gravitational/teleport/lib/tlsca"
+)
+
+// TestPassword is password generated during the test to login in test cluster.
+const TestPassword = "UPDATER_TEST_PASSWORD"
+
+var (
+ version = teleport.Version
+)
+
+type TestModules struct{}
+
+func (p *TestModules) GenerateAccessRequestPromotions(context.Context, modules.AccessResourcesGetter, types.AccessRequest) (*types.AccessRequestAllowedPromotions, error) {
+ return &types.AccessRequestAllowedPromotions{}, nil
+}
+
+func (p *TestModules) GetSuggestedAccessLists(ctx context.Context, identity *tlsca.Identity, clt modules.AccessListSuggestionClient, accessListGetter modules.AccessListGetter, requestID string) ([]*accesslist.AccessList, error) {
+ return []*accesslist.AccessList{}, nil
+}
+
+// BuildType returns build type (OSS or Enterprise)
+func (p *TestModules) BuildType() string {
+ return "CLI"
+}
+
+// IsEnterpriseBuild returns false for [TestModules].
+func (p *TestModules) IsEnterpriseBuild() bool {
+ return false
+}
+
+// IsOSSBuild returns false for [TestModules].
+func (p *TestModules) IsOSSBuild() bool {
+ return false
+}
+
+// LicenseExpiry returns the expiry date of the enterprise license, if applicable.
+func (p *TestModules) LicenseExpiry() time.Time {
+ return time.Time{}
+}
+
+// PrintVersion prints the Teleport version.
+func (p *TestModules) PrintVersion() {
+ fmt.Printf("Teleport v%v git\n", version)
+}
+
+// Features returns supported features
+func (p *TestModules) Features() modules.Features {
+ return modules.Features{
+ AdvancedAccessWorkflows: true,
+ }
+}
+
+// IsBoringBinary checks if the binary was compiled with BoringCrypto.
+func (p *TestModules) IsBoringBinary() bool {
+ return false
+}
+
+// AttestHardwareKey attests a hardware key.
+func (p *TestModules) AttestHardwareKey(context.Context, interface{}, *keys.AttestationStatement, crypto.PublicKey, time.Duration) (*keys.AttestationData, error) {
+ return nil, trace.NotFound("no attestation data for the given key")
+}
+
+func (p *TestModules) EnableRecoveryCodes() {}
+
+func (p *TestModules) EnablePlugins() {}
+
+func (p *TestModules) SetFeatures(f modules.Features) {}
+
+func (p *TestModules) EnableAccessGraph() {}
+
+func (p *TestModules) EnableAccessMonitoring() {}
diff --git a/integration/autoupdate/tools/updater/tctl/main.go b/integration/autoupdate/tools/updater/tctl/main.go
new file mode 100644
index 0000000000000..099ea24ba226b
--- /dev/null
+++ b/integration/autoupdate/tools/updater/tctl/main.go
@@ -0,0 +1,37 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 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 .
+ */
+
+package main
+
+import (
+ "context"
+
+ "github.com/gravitational/teleport/integration/autoupdate/tools/updater"
+ "github.com/gravitational/teleport/lib/modules"
+ stacksignal "github.com/gravitational/teleport/lib/utils/signal"
+ tctl "github.com/gravitational/teleport/tool/tctl/common"
+)
+
+func main() {
+ ctx, cancel := stacksignal.GetSignalHandler().NotifyContext(context.Background())
+ defer cancel()
+
+ modules.SetModules(&updater.TestModules{})
+
+ tctl.Run(ctx, tctl.Commands())
+}
diff --git a/integration/autoupdate/tools/updater/tsh/main.go b/integration/autoupdate/tools/updater/tsh/main.go
new file mode 100644
index 0000000000000..eab022e08d336
--- /dev/null
+++ b/integration/autoupdate/tools/updater/tsh/main.go
@@ -0,0 +1,44 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 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 .
+ */
+
+package main
+
+import (
+ "context"
+ "os"
+
+ "github.com/gravitational/teleport/api/utils/prompt"
+ "github.com/gravitational/teleport/integration/autoupdate/tools/updater"
+ "github.com/gravitational/teleport/lib/modules"
+ "github.com/gravitational/teleport/lib/utils"
+ stacksignal "github.com/gravitational/teleport/lib/utils/signal"
+ tsh "github.com/gravitational/teleport/tool/tsh/common"
+)
+
+func main() {
+ ctx, cancel := stacksignal.GetSignalHandler().NotifyContext(context.Background())
+ defer cancel()
+
+ modules.SetModules(&updater.TestModules{})
+ prompt.SetStdin(prompt.NewFakeReader().AddString(os.Getenv(updater.TestPassword)))
+
+ err := tsh.Run(ctx, os.Args[1:])
+ if err != nil {
+ utils.FatalError(err)
+ }
+}
diff --git a/integration/autoupdate/tools/updater_test.go b/integration/autoupdate/tools/updater_test.go
index 6e617b3e3cdb1..0c05f2524caf0 100644
--- a/integration/autoupdate/tools/updater_test.go
+++ b/integration/autoupdate/tools/updater_test.go
@@ -33,6 +33,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/autoupdate/tools"
)
@@ -44,8 +45,8 @@ var (
// TestUpdate verifies the basic update logic. We first download a lower version, then request
// an update to a newer version, expecting it to re-execute with the updated version.
func TestUpdate(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
+ t.Setenv(types.HomeEnvVar, t.TempDir())
+ ctx := context.Background()
// Fetch compiled test binary with updater logic and install to $TELEPORT_HOME.
updater := tools.NewUpdater(
@@ -57,7 +58,7 @@ func TestUpdate(t *testing.T) {
require.NoError(t, err)
// Verify that the installed version is equal to requested one.
- cmd := exec.CommandContext(ctx, filepath.Join(toolsDir, "tsh"), "version")
+ cmd := exec.CommandContext(ctx, filepath.Join(toolsDir, "tctl"), "version")
out, err := cmd.Output()
require.NoError(t, err)
@@ -85,8 +86,8 @@ func TestUpdate(t *testing.T) {
// first update is complete, other processes should acquire the lock one by one and re-execute
// the command with the updated version without any new downloads.
func TestParallelUpdate(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
+ t.Setenv(types.HomeEnvVar, t.TempDir())
+ ctx := context.Background()
// Initial fetch the updater binary un-archive and replace.
updater := tools.NewUpdater(
@@ -158,8 +159,8 @@ func TestParallelUpdate(t *testing.T) {
// TestUpdateInterruptSignal verifies the interrupt signal send to the process must stop downloading.
func TestUpdateInterruptSignal(t *testing.T) {
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
+ t.Setenv(types.HomeEnvVar, t.TempDir())
+ ctx := context.Background()
// Initial fetch the updater binary un-archive and replace.
updater := tools.NewUpdater(
diff --git a/integration/autoupdate/tools/updater_tsh_test.go b/integration/autoupdate/tools/updater_tsh_test.go
new file mode 100644
index 0000000000000..01b9ca7679a42
--- /dev/null
+++ b/integration/autoupdate/tools/updater_tsh_test.go
@@ -0,0 +1,146 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 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 .
+ */
+
+package tools_test
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "gopkg.in/yaml.v3"
+
+ "github.com/gravitational/teleport/api/constants"
+ autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/api/types/autoupdate"
+ "github.com/gravitational/teleport/integration/autoupdate/tools/updater"
+ "github.com/gravitational/teleport/lib/autoupdate/tools"
+ "github.com/gravitational/teleport/lib/client"
+ "github.com/gravitational/teleport/lib/utils"
+ testserver "github.com/gravitational/teleport/tool/teleport/testenv"
+)
+
+// TestAliasLoginWithUpdater runs test cluster with enabled auto updates for client tools,
+// checks that defined alias in tsh configuration is replaced to the proper login command
+// and after auto update this not leads to recursive alias re-execution.
+func TestAliasLoginWithUpdater(t *testing.T) {
+ ctx := context.Background()
+
+ homeDir := filepath.Join(t.TempDir(), "home")
+ require.NoError(t, os.MkdirAll(homeDir, 0700))
+ installDir := filepath.Join(t.TempDir(), "local")
+ require.NoError(t, os.MkdirAll(installDir, 0700))
+
+ t.Setenv(types.HomeEnvVar, homeDir)
+
+ alice, err := types.NewUser("alice")
+ require.NoError(t, err)
+ alice.SetRoles([]string{"access"})
+
+ // Enable client tools auto updates and set the target version.
+ config, err := autoupdate.NewAutoUpdateConfig(&autoupdatev1pb.AutoUpdateConfigSpec{
+ Tools: &autoupdatev1pb.AutoUpdateConfigSpecTools{
+ Mode: autoupdate.ToolsUpdateModeEnabled,
+ },
+ })
+ require.NoError(t, err)
+ version, err := autoupdate.NewAutoUpdateVersion(&autoupdatev1pb.AutoUpdateVersionSpec{
+ Tools: &autoupdatev1pb.AutoUpdateVersionSpecTools{
+ TargetVersion: testVersions[1], // [v3.2.1]
+ },
+ })
+ require.NoError(t, err)
+
+ // Disable 2fa to simplify login for test.
+ ap, err := types.NewAuthPreferenceFromConfigFile(types.AuthPreferenceSpecV2{
+ Type: constants.Local,
+ SecondFactor: constants.SecondFactorOff,
+ Webauthn: &types.Webauthn{
+ RPID: "localhost",
+ },
+ })
+ require.NoError(t, err)
+
+ rootServer := testserver.MakeTestServer(t,
+ testserver.WithBootstrap(alice),
+ testserver.WithClusterName(t, "root"),
+ testserver.WithAuthPreference(ap),
+ )
+ authService := rootServer.GetAuthServer()
+ _, err = authService.UpsertAutoUpdateConfig(ctx, config)
+ require.NoError(t, err)
+ _, err = authService.UpsertAutoUpdateVersion(ctx, version)
+ require.NoError(t, err)
+ password, err := utils.CryptoRandomHex(6)
+ require.NoError(t, err)
+ t.Setenv(updater.TestPassword, password)
+ err = authService.UpsertPassword("alice", []byte(password))
+ require.NoError(t, err)
+
+ // Assign alias to the login command for test cluster.
+ proxyAddr, err := rootServer.ProxyWebAddr()
+ require.NoError(t, err)
+ configPath := filepath.Join(homeDir, client.TSHConfigPath)
+ require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0700))
+ executable := filepath.Join(installDir, "tsh")
+ out, err := yaml.Marshal(client.TSHConfig{
+ Aliases: map[string]string{
+ "loginalice": fmt.Sprintf(
+ "%s login --insecure --proxy %s --user alice --auth %s",
+ executable, proxyAddr, constants.LocalConnector,
+ ),
+ },
+ })
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(configPath, out, 0600))
+
+ // Fetch compiled test binary and install to tools dir [v1.2.3].
+ err = tools.NewUpdater(installDir, testVersions[0], tools.WithBaseURL(baseURL)).Update(ctx, testVersions[0])
+ require.NoError(t, err)
+
+ // Execute alias command which must be transformed to the login command.
+ // Since client tools autoupdates is enabled and target version is set
+ // in the test cluster, we have to update client tools to new version.
+ cmd := exec.CommandContext(ctx, executable, "loginalice")
+ cmd.Env = os.Environ()
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ require.NoError(t, cmd.Run())
+
+ // Verify tctl status after login.
+ cmd = exec.CommandContext(ctx, filepath.Join(installDir, "tctl"), "status", "--insecure")
+ cmd.Env = os.Environ()
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ require.NoError(t, cmd.Run())
+
+ // Run version command to verify that login command executed auto update and
+ // tsh was upgraded to [v3.2.1].
+ cmd = exec.CommandContext(ctx, executable, "version")
+ out, err = cmd.Output()
+ require.NoError(t, err)
+
+ matches := pattern.FindStringSubmatch(string(out))
+ require.Len(t, matches, 2)
+ require.Equal(t, testVersions[1], matches[1])
+}
diff --git a/lib/autoupdate/tools/helper.go b/lib/autoupdate/tools/helper.go
index 79069c3d3c90b..a3322f88d767b 100644
--- a/lib/autoupdate/tools/helper.go
+++ b/lib/autoupdate/tools/helper.go
@@ -26,9 +26,18 @@ import (
"github.com/gravitational/trace"
+ "github.com/gravitational/teleport"
stacksignal "github.com/gravitational/teleport/lib/utils/signal"
)
+// Variables might to be overridden during compilation time for integration tests.
+var (
+ // version is the current version of the Teleport.
+ version = teleport.Version
+ // baseURL is CDN URL for downloading official Teleport packages.
+ baseURL = defaultBaseURL
+)
+
// CheckAndUpdateLocal verifies if the TELEPORT_TOOLS_VERSION environment variable
// is set and a version is defined (or disabled by setting it to "off"). The requested
// version is compared with the current client tools version. If they differ, the version
@@ -36,13 +45,14 @@ import (
// with the updated version.
// If $TELEPORT_HOME/bin contains downloaded client tools, it always re-executes
// using the version from the home directory.
-func CheckAndUpdateLocal(ctx context.Context, currentVersion string) error {
+func CheckAndUpdateLocal(ctx context.Context, reExecArgs []string) error {
toolsDir, err := Dir()
if err != nil {
slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
return nil
}
- updater := NewUpdater(toolsDir, currentVersion)
+
+ 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
// environment variable. If so, re-exec that version of client tools.
@@ -51,7 +61,7 @@ func CheckAndUpdateLocal(ctx context.Context, currentVersion string) error {
return trace.Wrap(err)
}
if reExec {
- return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion))
+ return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion, reExecArgs))
}
return nil
@@ -64,13 +74,13 @@ func CheckAndUpdateLocal(ctx context.Context, currentVersion string) error {
// with the updated version.
// If $TELEPORT_HOME/bin contains downloaded client tools, it always re-executes
// using the version from the home directory.
-func CheckAndUpdateRemote(ctx context.Context, currentVersion string, proxy string, insecure bool) error {
+func CheckAndUpdateRemote(ctx context.Context, proxy string, insecure bool, reExecArgs []string) error {
toolsDir, err := Dir()
if err != nil {
slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
return nil
}
- updater := NewUpdater(toolsDir, currentVersion)
+ 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.
//
@@ -81,13 +91,13 @@ func CheckAndUpdateRemote(ctx context.Context, currentVersion string, proxy stri
return trace.Wrap(err)
}
if reExec {
- return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion))
+ return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion, reExecArgs))
}
return nil
}
-func updateAndReExec(ctx context.Context, updater *Updater, toolsVersion string) error {
+func updateAndReExec(ctx context.Context, updater *Updater, toolsVersion string, args []string) error {
ctxUpdate, cancel := stacksignal.GetSignalHandler().NotifyContext(ctx)
defer cancel()
// Download the version of client tools required by the cluster. This
@@ -99,7 +109,7 @@ func updateAndReExec(ctx context.Context, updater *Updater, toolsVersion string)
}
// Re-execute client tools with the correct version of client tools.
- code, err := updater.Exec()
+ code, err := updater.Exec(args)
if err != nil && !errors.Is(err, os.ErrNotExist) {
slog.DebugContext(ctx, "Failed to re-exec client tool", "error", err)
os.Exit(code)
diff --git a/lib/autoupdate/tools/updater.go b/lib/autoupdate/tools/updater.go
index 8bad07c395391..2845864f9c5ef 100644
--- a/lib/autoupdate/tools/updater.go
+++ b/lib/autoupdate/tools/updater.go
@@ -49,8 +49,8 @@ import (
const (
// teleportToolsVersionEnv is environment name for requesting specific version for update.
teleportToolsVersionEnv = "TELEPORT_TOOLS_VERSION"
- // baseURL is CDN URL for downloading official Teleport packages.
- baseURL = "https://cdn.teleport.dev"
+ // 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
@@ -61,7 +61,7 @@ const (
)
var (
- // // pattern is template for response on version command for client tools {tsh, tctl}.
+ // pattern is template for response on version command for client tools {tsh, tctl}.
pattern = regexp.MustCompile(`(?m)Teleport v(.*) git`)
)
@@ -109,7 +109,7 @@ func NewUpdater(toolsDir, localVersion string, options ...Option) *Updater {
tools: DefaultClientTools(),
toolsDir: toolsDir,
localVersion: localVersion,
- baseURL: baseURL,
+ baseURL: defaultBaseURL,
client: http.DefaultClient,
}
for _, option := range options {
@@ -145,7 +145,7 @@ func (u *Updater) CheckLocal() (version string, reExec bool, err error) {
// If a version of client tools has already been downloaded to
// tools directory, return that.
toolsVersion, err := CheckToolVersion(u.toolsDir)
- if trace.IsNotFound(err) {
+ if trace.IsNotFound(err) || toolsVersion == u.localVersion {
return u.localVersion, false, nil
}
if err != nil {
@@ -327,16 +327,28 @@ func (u *Updater) update(ctx context.Context, pkg packageURL, pkgName string) er
}
// Exec re-executes tool command with same arguments and environ variables.
-func (u *Updater) Exec() (int, error) {
+func (u *Updater) Exec(args []string) (int, error) {
path, err := toolName(u.toolsDir)
if err != nil {
return 0, trace.Wrap(err)
}
- // To prevent re-execution loop we have to disable update logic for re-execution.
- env := append(os.Environ(), teleportToolsVersionEnv+"=off")
+ // To prevent re-execution loop we have to disable update logic for re-execution,
+ // by unsetting current tools version env variable and setting it to "off".
+ if err := os.Unsetenv(teleportToolsVersionEnv); err != nil {
+ return 0, trace.Wrap(err)
+ }
+
+ env := os.Environ()
+ executablePath, err := os.Executable()
+ if err != nil {
+ return 0, trace.Wrap(err)
+ }
+ if path == executablePath {
+ env = append(env, teleportToolsVersionEnv+"=off")
+ }
if runtime.GOOS == constants.WindowsOS {
- cmd := exec.Command(path, os.Args[1:]...)
+ cmd := exec.Command(path, args...)
cmd.Env = env
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
@@ -348,7 +360,7 @@ func (u *Updater) Exec() (int, error) {
return cmd.ProcessState.ExitCode(), nil
}
- if err := syscall.Exec(path, append([]string{path}, os.Args[1:]...), env); err != nil {
+ if err := syscall.Exec(path, append([]string{path}, args...), env); err != nil {
return 0, trace.Wrap(err)
}
@@ -415,7 +427,6 @@ func (u *Updater) downloadArchive(ctx context.Context, url string, f io.Writer)
return nil, trace.Wrap(err)
}
}
-
h := sha256.New()
// It is a little inefficient to download the file to disk and then re-load
// it into memory to unarchive later, but this is safer as it allows client
diff --git a/lib/client/api.go b/lib/client/api.go
index 89009f5daa1c5..cee602e7234c7 100644
--- a/lib/client/api.go
+++ b/lib/client/api.go
@@ -669,7 +669,7 @@ func RetryWithRelogin(ctx context.Context, tc *TeleportClient, fn func() error,
return trace.Wrap(err)
}
- if err := tools.CheckAndUpdateRemote(ctx, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(ctx, tc.WebProxyAddr, tc.InsecureSkipVerify, os.Args[1:]); err != nil {
return trace.Wrap(err)
}
diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go
index 6644bbbbdcfdc..6c8440e6ad0c4 100644
--- a/tool/tctl/common/tctl.go
+++ b/tool/tctl/common/tctl.go
@@ -105,7 +105,7 @@ type CLICommand interface {
//
// distribution: name of the Teleport distribution
func Run(ctx context.Context, commands []CLICommand) {
- if err := tools.CheckAndUpdateLocal(ctx, teleport.Version); err != nil {
+ if err := tools.CheckAndUpdateLocal(ctx, os.Args[1:]); err != nil {
utils.FatalError(err)
}
diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go
index d22e15ed554b3..e019ccf0c68dd 100644
--- a/tool/tsh/common/tsh.go
+++ b/tool/tsh/common/tsh.go
@@ -704,7 +704,7 @@ func initLogger(cf *CLIConf) {
//
// DO NOT RUN TESTS that call Run() in parallel (unless you taken precautions).
func Run(ctx context.Context, args []string, opts ...CliOption) error {
- if err := tools.CheckAndUpdateLocal(ctx, teleport.Version); err != nil {
+ if err := tools.CheckAndUpdateLocal(ctx, args); err != nil {
return trace.Wrap(err)
}
@@ -1469,7 +1469,7 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error {
case sessionsList.FullCommand():
err = onListSessions(&cf)
case login.FullCommand():
- err = onLogin(&cf)
+ err = onLogin(&cf, args...)
case logout.FullCommand():
err = onLogout(&cf)
case show.FullCommand():
@@ -1814,7 +1814,7 @@ func serializeVersion(format string, proxyVersion string, proxyPublicAddress str
}
// onLogin logs in with remote proxy and gets signed certificates
-func onLogin(cf *CLIConf) error {
+func onLogin(cf *CLIConf, reExecArgs ...string) error {
autoRequest := true
// special case: --request-roles=no disables auto-request behavior.
if cf.DesiredRoles == "no" {
@@ -1855,7 +1855,7 @@ func onLogin(cf *CLIConf) error {
// The user is not logged in and has typed in `tsh --proxy=... login`, if
// the running binary needs to be updated, update and re-exec.
if profile == nil {
- if err := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
}
@@ -1873,7 +1873,7 @@ func onLogin(cf *CLIConf) error {
// The user has typed `tsh login`, if the running binary needs to
// be updated, update and re-exec.
- if err := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
@@ -1893,7 +1893,7 @@ func onLogin(cf *CLIConf) error {
// The user has typed `tsh login`, if the running binary needs to
// be updated, update and re-exec.
- if err := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
@@ -1969,7 +1969,7 @@ func onLogin(cf *CLIConf) error {
default:
// The user is logged in and has typed in `tsh --proxy=... login`, if
// the running binary needs to be updated, update and re-exec.
- if err := tools.CheckAndUpdateRemote(cf.Context, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
+ if err := tools.CheckAndUpdateRemote(cf.Context, tc.WebProxyAddr, tc.InsecureSkipVerify, reExecArgs); err != nil {
return trace.Wrap(err)
}
}