diff --git a/integration/autoupdate/tools/updater_tsh_test.go b/integration/autoupdate/tools/updater_tsh_test.go index 01b9ca7679a42..b2372683b6c5a 100644 --- a/integration/autoupdate/tools/updater_tsh_test.go +++ b/integration/autoupdate/tools/updater_tsh_test.go @@ -143,4 +143,7 @@ func TestAliasLoginWithUpdater(t *testing.T) { matches := pattern.FindStringSubmatch(string(out)) require.Len(t, matches, 2) require.Equal(t, testVersions[1], matches[1]) + + // Verifies that version commands shows version re-executed from. + require.Contains(t, string(out), fmt.Sprintf("Re-executed from version: %s", testVersions[0])) } diff --git a/lib/autoupdate/tools/updater.go b/lib/autoupdate/tools/updater.go index eded34049e473..b236324c81d88 100644 --- a/lib/autoupdate/tools/updater.go +++ b/lib/autoupdate/tools/updater.go @@ -50,6 +50,9 @@ import ( const ( // teleportToolsVersionEnv is environment name for requesting specific version for update. teleportToolsVersionEnv = "TELEPORT_TOOLS_VERSION" + // teleportToolsVersionReExecEnv is internal environment name for transferring original + // version to re-executed ones. + teleportToolsVersionReExecEnv = "TELEPORT_TOOLS_VERSION_REEXEC" // reservedFreeDisk is the predefined amount of free disk space (in bytes) required // to remain available after downloading archives. reservedFreeDisk = 10 * 1024 * 1024 // 10 Mb @@ -345,6 +348,9 @@ func (u *Updater) Exec(args []string) (int, error) { if err := os.Unsetenv(teleportToolsVersionEnv); err != nil { return 0, trace.Wrap(err) } + if err := os.Unsetenv(teleportToolsVersionReExecEnv); err != nil { + return 0, trace.Wrap(err) + } env := os.Environ() executablePath, err := os.Executable() @@ -354,6 +360,7 @@ func (u *Updater) Exec(args []string) (int, error) { if path == executablePath { env = append(env, teleportToolsVersionEnv+"=off") } + env = append(env, fmt.Sprintf("%s=%s", teleportToolsVersionReExecEnv, u.localVersion)) if runtime.GOOS == constants.WindowsOS { cmd := exec.Command(path, args...) diff --git a/lib/autoupdate/tools/utils.go b/lib/autoupdate/tools/utils.go index 6da7974bd5793..e158ef534169a 100644 --- a/lib/autoupdate/tools/utils.go +++ b/lib/autoupdate/tools/utils.go @@ -120,6 +120,19 @@ func CheckToolVersion(toolsDir string) (string, error) { return "", trace.BadParameter("unable to determine version") } +// GetReExecFromVersion returns the version if current execution binary is re-executed from +// another version. +func GetReExecFromVersion(ctx context.Context) string { + reExecFromVersion := os.Getenv(teleportToolsVersionReExecEnv) + if reExecFromVersion != "" { + if _, err := semver.NewVersion(reExecFromVersion); err != nil { + slog.WarnContext(ctx, "Failed to parse teleport 'TELEPORT_TOOLS_VERSION_REEXEC'", "error", err) + return "" + } + } + return reExecFromVersion +} + // packageURL defines URLs to the archive and their archive sha256 hash file, and marks // if this package is optional, for such case download needs to be ignored if package // not found in CDN. diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 6c0e44ee427ee..5c56c68154fd3 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -1802,6 +1802,7 @@ func onVersion(cf *CLIConf) error { proxyPublicAddr = ppa } + reExecFromVersion := tools.GetReExecFromVersion(cf.Context) format := strings.ToLower(cf.Format) switch format { case teleport.Text, "": @@ -1810,8 +1811,11 @@ func onVersion(cf *CLIConf) error { fmt.Printf("Proxy version: %s\n", proxyVersion) fmt.Printf("Proxy: %s\n", proxyPublicAddr) } + if reExecFromVersion != "" { + fmt.Printf("Re-executed from version: %s\n", reExecFromVersion) + } case teleport.JSON, teleport.YAML: - out, err := serializeVersion(format, proxyVersion, proxyPublicAddr) + out, err := serializeVersion(format, proxyVersion, proxyPublicAddr, reExecFromVersion) if err != nil { return trace.Wrap(err) } @@ -1858,19 +1862,21 @@ type benchKubeOptions struct { namespace string } -func serializeVersion(format string, proxyVersion string, proxyPublicAddress string) (string, error) { +func serializeVersion(format string, proxyVersion string, proxyPublicAddress string, reExecFromVersion string) (string, error) { versionInfo := struct { - Version string `json:"version"` - Gitref string `json:"gitref"` - Runtime string `json:"runtime"` - ProxyVersion string `json:"proxyVersion,omitempty"` - ProxyPublicAddress string `json:"proxyPublicAddress,omitempty"` + Version string `json:"version"` + Gitref string `json:"gitref"` + Runtime string `json:"runtime"` + ProxyVersion string `json:"proxyVersion,omitempty"` + ProxyPublicAddress string `json:"proxyPublicAddress,omitempty"` + ReExecutedFromVersion string `json:"reExecutedFromVersion,omitempty"` }{ teleport.Version, teleport.Gitref, runtime.Version(), proxyVersion, proxyPublicAddress, + reExecFromVersion, } var out []byte var err error diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 873aacca28822..ba8c787527130 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -4164,6 +4164,7 @@ func TestSerializeVersion(t *testing.T) { proxyVersion string proxyPublicAddress string + reExecFromVersion string }{ { name: "no proxy version provided", @@ -4180,12 +4181,21 @@ func TestSerializeVersion(t *testing.T) { `{"version": %q, "gitref": %q, "runtime": %q, "proxyVersion": %q, "proxyPublicAddress": %q}`, teleport.Version, teleport.Gitref, runtime.Version(), "1.33.7", "teleport.example.com:443"), }, + { + name: "re-exec version provided", + proxyVersion: "3.2.1", + proxyPublicAddress: "teleport.example.com:443", + reExecFromVersion: "1.2.3", + expected: fmt.Sprintf( + `{"version": %q, "gitref": %q, "runtime": %q, "proxyVersion": %q, "reExecutedFromVersion": %q, "proxyPublicAddress": %q}`, + teleport.Version, teleport.Gitref, runtime.Version(), "3.2.1", "1.2.3", "teleport.example.com:443"), + }, } for _, tC := range testCases { t.Run(tC.name, func(t *testing.T) { testSerialization(t, tC.expected, func(fmt string) (string, error) { - return serializeVersion(fmt, tC.proxyVersion, tC.proxyPublicAddress) + return serializeVersion(fmt, tC.proxyVersion, tC.proxyPublicAddress, tC.reExecFromVersion) }) }) }