From a8ae574479cf5f409401aae06cfa197d68c829ff Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Mon, 20 Oct 2025 12:03:49 -0700 Subject: [PATCH] Add OS and ARCH info for client tools managed updates (#59891) * Add OS and ARCH info for client tools managed updates * Optimized initial tsh/tctl installation in tests to perform it once and re-use * Add reading binary headers in order to identify OS and ARCH if it is not defined in configuration file. --- integration/autoupdate/tools/main_test.go | 24 ++++ integration/autoupdate/tools/updater_test.go | 108 +++++++++--------- .../autoupdate/tools/updater_tsh_test.go | 75 ++---------- lib/autoupdate/tools/config.go | 69 ++++++++--- lib/autoupdate/tools/migration.go | 9 +- lib/autoupdate/tools/updater.go | 20 ++-- 6 files changed, 162 insertions(+), 143 deletions(-) diff --git a/integration/autoupdate/tools/main_test.go b/integration/autoupdate/tools/main_test.go index 7d2427ff13d..8d5ad7c280e 100644 --- a/integration/autoupdate/tools/main_test.go +++ b/integration/autoupdate/tools/main_test.go @@ -41,6 +41,7 @@ import ( "github.com/gravitational/teleport/integration/autoupdate/tools/updater/tsh" "github.com/gravitational/teleport/integration/helpers/archive" "github.com/gravitational/teleport/lib/autoupdate" + "github.com/gravitational/teleport/lib/autoupdate/tools" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/modules/modulestest" "github.com/gravitational/teleport/lib/utils" @@ -63,6 +64,11 @@ var ( toolsDir string baseURL string + + // tshPath is the path to initial tsh binary version 1.0.0. + tshPath string + // tctlPath is the path to initial tctl binary version 1.0.0. + tctlPath string ) func TestMain(m *testing.M) { @@ -112,6 +118,24 @@ func TestMain(m *testing.M) { log.Fatalf("failed to set base URL environment variable: %v", err) } + // Initial fetch the updater binary un-archive and replace. + updater := tools.NewUpdater( + toolsDir, + testVersions[0], + tools.WithBaseURL(baseURL), + ) + if err := updater.Update(ctx, testVersions[0]); err != nil { + log.Fatalf("failed to update: %v", err) + } + tshPath, err = updater.ToolPath(tools.DefaultClientTools()[0], testVersions[0]) + if err != nil { + log.Fatalf("failed to get tsh path: %v", err) + } + tctlPath, err = updater.ToolPath(tools.DefaultClientTools()[1], testVersions[0]) + if err != nil { + log.Fatalf("failed to get tctl path: %v", err) + } + // Run tests after binary is built. code := m.Run() diff --git a/integration/autoupdate/tools/updater_test.go b/integration/autoupdate/tools/updater_test.go index dfd36081418..1a082e86c33 100644 --- a/integration/autoupdate/tools/updater_test.go +++ b/integration/autoupdate/tools/updater_test.go @@ -25,7 +25,9 @@ import ( "io" "os" "os/exec" + "path/filepath" "regexp" + "runtime" "strings" "testing" "time" @@ -51,20 +53,6 @@ func TestUpdate(t *testing.T) { t.Setenv(types.HomeEnvVar, t.TempDir()) ctx := context.Background() - // Fetch compiled test binary with updater logic and install to $TELEPORT_HOME. - updater := tools.NewUpdater( - toolsDir, - testVersions[0], - tools.WithBaseURL(baseURL), - ) - err := updater.Update(ctx, testVersions[0]) - require.NoError(t, err) - - tshPath, err := updater.ToolPath(tools.DefaultClientTools()[0], testVersions[0]) - require.NoError(t, err) - tctlPath, err := updater.ToolPath(tools.DefaultClientTools()[1], testVersions[0]) - require.NoError(t, err) - // Verify that the installed version is equal to requested one. cmd := exec.CommandContext(ctx, tctlPath, "version") out, err := cmd.Output() @@ -85,6 +73,59 @@ func TestUpdate(t *testing.T) { matchVersion(t, string(out), testVersions[1]) } +// TestUpdateDifferentOSArch verifies the update logic for matching operating system +// and architecture. If they differ from the current system, a new download must be +// initiated even when the same version is already installed. +func TestUpdateDifferentOSArch(t *testing.T) { + home := t.TempDir() + t.Setenv(types.HomeEnvVar, home) + ctx := context.Background() + + // Execute version command with setting the new version which must trigger update and + // re-execution of the same command after downloading requested version. + cmd := exec.CommandContext(ctx, tshPath, "version") + cmd.Env = append( + os.Environ(), + fmt.Sprintf("%s=%s", teleportToolsVersion, testVersions[1]), + ) + out, err := cmd.Output() + require.NoError(t, err) + matchVersion(t, string(out), testVersions[1]) + + configPath := filepath.Join(home, "bin") + + ctc, err := tools.GetToolsConfig(configPath) + require.NoError(t, err) + require.Len(t, ctc.Tools, 1) + require.Equal(t, runtime.GOOS, ctc.Tools[0].OS) + require.Equal(t, runtime.GOARCH, ctc.Tools[0].Arch) + + // Update the architecture to a non-existing value. + err = tools.UpdateToolsConfig(configPath, func(ctc *tools.ClientToolsConfig) error { + ctc.Tools[0].Arch = "unknown" + return nil + }) + require.NoError(t, err) + + // After executing the version command, we should not match the architecture of the + // previously installed tool version. Since the package does not match, we must + // re-download the package for the required architecture and re-execute. + cmd = exec.CommandContext(ctx, tshPath, "version") + cmd.Env = append( + os.Environ(), + fmt.Sprintf("%s=%s", teleportToolsVersion, testVersions[1]), + ) + out, err = cmd.Output() + require.NoError(t, err) + matchVersion(t, string(out), testVersions[1]) + + ctc, err = tools.GetToolsConfig(configPath) + require.NoError(t, err) + // The second call to the version command installs another package with the required + // OS and architecture, and we should then see two packages in the list. + require.Len(t, ctc.Tools, 2) +} + // TestParallelUpdate launches multiple updater commands in parallel while defining a new version. // The first process should acquire a lock and block execution for the other processes. After the // first update is complete, other processes should acquire the lock one by one and re-execute @@ -93,18 +134,6 @@ func TestParallelUpdate(t *testing.T) { t.Setenv(types.HomeEnvVar, t.TempDir()) ctx := context.Background() - // Initial fetch the updater binary un-archive and replace. - updater := tools.NewUpdater( - toolsDir, - testVersions[0], - tools.WithBaseURL(baseURL), - ) - err := updater.Update(ctx, testVersions[0]) - require.NoError(t, err) - - tshPath, err := updater.ToolPath(tools.DefaultClientTools()[0], testVersions[0]) - require.NoError(t, err) - tCtx, cancel := context.WithTimeout(ctx, time.Minute) t.Cleanup(cancel) @@ -121,7 +150,7 @@ func TestParallelUpdate(t *testing.T) { os.Environ(), fmt.Sprintf("%s=%s", teleportToolsVersion, testVersions[1]), ) - err = cmd.Start() + err := cmd.Start() require.NoError(t, err, "failed to start updater") go func(cmd *exec.Cmd) { @@ -153,18 +182,6 @@ func TestParallelUpdate(t *testing.T) { // TestUpdateInterruptSignal verifies the interrupt signal send to the process must stop downloading. func TestUpdateInterruptSignal(t *testing.T) { t.Setenv(types.HomeEnvVar, t.TempDir()) - ctx := context.Background() - - // Initial fetch the updater binary un-archive and replace. - updater := tools.NewUpdater( - toolsDir, - testVersions[0], - tools.WithBaseURL(baseURL), - ) - err := updater.Update(ctx, testVersions[0]) - require.NoError(t, err) - tshPath, err := updater.ToolPath(tools.DefaultClientTools()[0], testVersions[0]) - require.NoError(t, err) var output bytes.Buffer multiOut := io.MultiWriter(&output, os.Stdout) @@ -175,7 +192,7 @@ func TestUpdateInterruptSignal(t *testing.T) { os.Environ(), fmt.Sprintf("%s=%s", teleportToolsVersion, testVersions[1]), ) - err = cmd.Start() + err := cmd.Start() if err != nil { t.Log(output.String()) } @@ -233,17 +250,6 @@ func TestUpdateForOSSBuild(t *testing.T) { t.Setenv(updater.TestBuild, modules.BuildOSS) t.Setenv(autoupdate.BaseURLEnvVar, "") - // Fetch compiled test binary with updater logic and install to $TELEPORT_HOME. - updater := tools.NewUpdater( - toolsDir, - testVersions[0], - tools.WithBaseURL(baseURL), - ) - err := updater.Update(ctx, testVersions[0]) - require.NoError(t, err) - tshPath, err := updater.ToolPath(tools.DefaultClientTools()[0], testVersions[0]) - require.NoError(t, err) - // Verify that requested update is ignored by OSS build and version wasn't updated. cmd := exec.CommandContext(ctx, tshPath, "version") cmd.Env = append( diff --git a/integration/autoupdate/tools/updater_tsh_test.go b/integration/autoupdate/tools/updater_tsh_test.go index 7825c09832c..eafd687dd99 100644 --- a/integration/autoupdate/tools/updater_tsh_test.go +++ b/integration/autoupdate/tools/updater_tsh_test.go @@ -39,7 +39,6 @@ import ( "github.com/gravitational/teleport/api/types/autoupdate" "github.com/gravitational/teleport/integration/autoupdate/tools/updater" "github.com/gravitational/teleport/lib/auth" - "github.com/gravitational/teleport/lib/autoupdate/tools" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/service/servicecfg" @@ -59,21 +58,13 @@ import ( func TestAliasLoginWithUpdater(t *testing.T) { ctx := context.Background() - rootServer, homeDir, installDir := bootstrapTestServer(t) + rootServer, homeDir := bootstrapTestServer(t) setupManagedUpdates(t, rootServer.GetAuthServer(), autoupdate.ToolsUpdateModeEnabled, testVersions[1]) // Assign alias to the login command for test cluster. proxyAddr, err := rootServer.ProxyWebAddr() require.NoError(t, err) - // Fetch compiled test binary and install to tools dir [v1.0.0]. - updater := tools.NewUpdater(installDir, testVersions[0], tools.WithBaseURL(baseURL)) - require.NoError(t, updater.Update(ctx, testVersions[0])) - tshPath, err := updater.ToolPath("tsh", testVersions[0]) - require.NoError(t, err) - tctlPath, err := updater.ToolPath("tctl", testVersions[0]) - require.NoError(t, err) - configPath := filepath.Join(homeDir, client.TSHConfigPath) require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0700)) out, err := yaml.Marshal(client.TSHConfig{ @@ -119,18 +110,12 @@ func TestAliasLoginWithUpdater(t *testing.T) { func TestSequentialUpdate(t *testing.T) { ctx := context.Background() - rootServer, _, installDir := bootstrapTestServer(t) + rootServer, _ := bootstrapTestServer(t) // Assign alias to the login command for test cluster. proxyAddr, err := rootServer.ProxyWebAddr() require.NoError(t, err) - // Fetch compiled test binary and install to tools dir [v1.0.0]. - updater := tools.NewUpdater(installDir, testVersions[0], tools.WithBaseURL(baseURL)) - require.NoError(t, updater.Update(ctx, testVersions[0])) - tshPath, err := updater.ToolPath("tsh", testVersions[0]) - require.NoError(t, err) - for _, testVersion := range testVersions[1:] { // Set cluster version to be upgraded. setupManagedUpdates(t, rootServer.GetAuthServer(), autoupdate.ToolsUpdateModeEnabled, testVersion) @@ -162,18 +147,12 @@ func TestSequentialUpdate(t *testing.T) { func TestLoginWithUpdaterAndProfile(t *testing.T) { ctx := context.Background() - rootServer, _, installDir := bootstrapTestServer(t) + rootServer, _ := bootstrapTestServer(t) setupManagedUpdates(t, rootServer.GetAuthServer(), autoupdate.ToolsUpdateModeDisabled, testVersions[1]) proxyAddr, err := rootServer.ProxyWebAddr() require.NoError(t, err) - // Fetch compiled test binary and install to tools dir [v1.0.0]. - updater := tools.NewUpdater(installDir, testVersions[0], tools.WithBaseURL(baseURL)) - require.NoError(t, updater.Update(ctx, testVersions[0])) - tshPath, err := updater.ToolPath("tsh", testVersions[0]) - require.NoError(t, err) - // First login with set version during login process t.Setenv("TELEPORT_TOOLS_VERSION", testVersions[1]) cmd := exec.CommandContext(ctx, tshPath, @@ -205,18 +184,12 @@ func TestLoginWithUpdaterAndProfile(t *testing.T) { func TestLoginWithDisabledUpdateInProfile(t *testing.T) { ctx := context.Background() - rootServer, _, installDir := bootstrapTestServer(t) + rootServer, _ := bootstrapTestServer(t) setupManagedUpdates(t, rootServer.GetAuthServer(), autoupdate.ToolsUpdateModeDisabled, testVersions[1]) proxyAddr, err := rootServer.ProxyWebAddr() require.NoError(t, err) - // Fetch compiled test binary and install to tools dir [v1.0.0]. - updater := tools.NewUpdater(installDir, testVersions[0], tools.WithBaseURL(baseURL)) - require.NoError(t, updater.Update(ctx, testVersions[0])) - tshPath, err := updater.ToolPath("tsh", testVersions[0]) - require.NoError(t, err) - // Set env variable to forcibly request update on version command. t.Setenv("TELEPORT_TOOLS_VERSION", testVersions[1]) cmd := exec.CommandContext(ctx, tshPath, "version") @@ -257,18 +230,12 @@ func TestLoginWithDisabledUpdateInProfile(t *testing.T) { func TestLoginWithDisabledUpdateForcedByEnv(t *testing.T) { ctx := context.Background() - rootServer, _, installDir := bootstrapTestServer(t) + rootServer, _ := bootstrapTestServer(t) setupManagedUpdates(t, rootServer.GetAuthServer(), autoupdate.ToolsUpdateModeDisabled, testVersions[1]) proxyAddr, err := rootServer.ProxyWebAddr() require.NoError(t, err) - // Fetch compiled test binary and install to tools dir [v1.0.0]. - updater := tools.NewUpdater(installDir, testVersions[0], tools.WithBaseURL(baseURL)) - require.NoError(t, updater.Update(ctx, testVersions[0])) - tshPath, err := updater.ToolPath("tsh", testVersions[0]) - require.NoError(t, err) - // Second login has to update profile and disable further managed updates. cmd := exec.CommandContext(ctx, tshPath, "login", "--proxy", proxyAddr.String(), "--insecure", "--user", "alice", "--auth", constants.LocalConnector) @@ -304,18 +271,6 @@ func TestMigratedUpdateNotReExec(t *testing.T) { t.Setenv(types.HomeEnvVar, testToolsDir) ctx := context.Background() - // Fetch compiled test binary with updater logic and install to $TELEPORT_HOME. - updater := tools.NewUpdater( - testToolsDir, - testVersions[0], - tools.WithBaseURL(baseURL), - ) - err := updater.Update(ctx, testVersions[0]) - require.NoError(t, err) - - tshPath, err := updater.ToolPath("tsh", testVersions[0]) - require.NoError(t, err) - require.NoError(t, os.MkdirAll(filepath.Join(testToolsDir, "bin"), 0755)) require.NoError(t, os.WriteFile(filepath.Join(testToolsDir, "bin", "tsh"), []byte("#!/bin/sh\n echo 'Teleport v5.5.5 git'\n"), 0755)) @@ -335,24 +290,12 @@ func TestUpdateConfigReExecPath(t *testing.T) { t.Setenv(types.HomeEnvVar, t.TempDir()) ctx := context.Background() - rootServer, _, _ := bootstrapTestServer(t) + rootServer, _ := bootstrapTestServer(t) setupManagedUpdates(t, rootServer.GetAuthServer(), autoupdate.ToolsUpdateModeEnabled, testVersions[1]) proxyAddr, err := rootServer.ProxyWebAddr() require.NoError(t, err) - // Fetch compiled test binary with updater logic and install to $TELEPORT_HOME. - updater := tools.NewUpdater( - toolsDir, - testVersions[0], - tools.WithBaseURL(baseURL), - ) - err = updater.Update(ctx, testVersions[0]) - require.NoError(t, err) - - tshPath, err := updater.ToolPath("tsh", testVersions[0]) - require.NoError(t, err) - cmd := exec.CommandContext(ctx, tshPath, "login", "--proxy", proxyAddr.String(), "--insecure", "--user", "alice", "--auth", constants.LocalConnector) cmd.Env = os.Environ() @@ -379,12 +322,10 @@ func TestUpdateConfigReExecPath(t *testing.T) { require.Contains(t, string(out), `ProxyCommand "`+tshPath+`" `) } -func bootstrapTestServer(t *testing.T) (*service.TeleportProcess, string, string) { +func bootstrapTestServer(t *testing.T) (*service.TeleportProcess, string) { t.Helper() 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) @@ -425,7 +366,7 @@ func bootstrapTestServer(t *testing.T) (*service.TeleportProcess, string, string err = authService.UpsertPassword("alice", []byte(password)) require.NoError(t, err) - return rootServer, homeDir, installDir + return rootServer, homeDir } func setupManagedUpdates(t *testing.T, server *auth.Server, muMode string, muVersion string) { diff --git a/lib/autoupdate/tools/config.go b/lib/autoupdate/tools/config.go index 8bf1deefa65..b6eb7830936 100644 --- a/lib/autoupdate/tools/config.go +++ b/lib/autoupdate/tools/config.go @@ -20,17 +20,20 @@ package tools import ( "context" + "debug/buildinfo" "encoding/json" "errors" "log/slog" "maps" "os" "path/filepath" + "runtime" "slices" "strings" "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/lib/utils" ) @@ -66,9 +69,9 @@ type ClientToolsConfig struct { // AddTool adds a tool to the collection in the configuration, always placing it at the top. // The collection size is limited by the `defaultSizeStoredVersion` constant. -func (ctc *ClientToolsConfig) AddTool(tool Tool) { +func (ctc *ClientToolsConfig) AddTool(toolsDir string, tool Tool) { for _, t := range ctc.Tools { - if t.Version == tool.Version { + if t.IsEqual(toolsDir, tool.Version, tool.OS, tool.Arch) { maps.Copy(t.PathMap, tool.PathMap) return } @@ -94,9 +97,9 @@ func (ctc *ClientToolsConfig) SetConfig(proxy string, version string, disabled b } // SelectVersion lookups the version and re-order by last recently used. -func (ctc *ClientToolsConfig) SelectVersion(version string) *Tool { +func (ctc *ClientToolsConfig) SelectVersion(toolsDir, version, os, arch string) *Tool { for i, tool := range ctc.Tools { - if tool.Version == version { + if tool.IsEqual(toolsDir, version, os, arch) { ctc.Tools = append([]Tool{tool}, append(ctc.Tools[:i], ctc.Tools[i+1:]...)...) return &tool } @@ -105,9 +108,9 @@ func (ctc *ClientToolsConfig) SelectVersion(version string) *Tool { } // HasVersion check that specific version present in collection. -func (ctc *ClientToolsConfig) HasVersion(version string) bool { - return slices.ContainsFunc(ctc.Tools, func(s Tool) bool { - return version == s.Version +func (ctc *ClientToolsConfig) HasVersion(toolsDir, version, os, arch string) bool { + return slices.ContainsFunc(ctc.Tools, func(tool Tool) bool { + return tool.IsEqual(toolsDir, version, os, arch) }) } @@ -121,15 +124,19 @@ type ClusterConfig struct { type Tool struct { // Version is the version of the tools (tsh, tctl) as defined in the PathMap. Version string `json:"version"` + // OS is the operating system of the installed package. + OS string `json:"os"` + // Arch is architecture of the installed package. + Arch string `json:"arch"` // PathMap stores the relative path (within the tools directory) for each tool binary. // For example: {"tctl": "package-id/tctl"}. PathMap map[string]string `json:"path"` } // PackageNames returns the package names extracted from the tool path map. -func (c *Tool) PackageNames() []string { +func (t *Tool) PackageNames() []string { var packageNames []string - for _, path := range c.PathMap { + for _, path := range t.PathMap { dir := strings.SplitN(path, string(filepath.Separator), 2) if len(dir) > 0 { packageNames = append(packageNames, dir[0]) @@ -138,9 +145,45 @@ func (c *Tool) PackageNames() []string { return packageNames } -// getToolsConfig reads the configuration file for client tools managed updates, +// IsEqual verifies that specific tool matches version, operating system and architecture. +func (t *Tool) IsEqual(toolsDir, version, os, arch string) bool { + // If OS version is not defined in configuration we should check it by binary headers. + // TODO(vapopov): DELETE IN v21.0.0 - OS and Arch must be supported for all version. + if t.OS == "" { + path, ok := t.PathMap[DefaultClientTools()[0]] + if !ok { + return false + } + info, err := buildinfo.ReadFile(filepath.Join(toolsDir, path)) + if err != nil { + slog.WarnContext(context.Background(), "Failed to read build info.", "error", err) + return version == t.Version + } + var binOS, binArch string + for _, s := range info.Settings { + switch s.Key { + case "GOOS": + binOS = s.Value + case "GOARCH": + binArch = s.Value + } + } + if binOS == "" || binArch == "" { + return version == t.Version + } + // macOS binaries are always built with universal architecture support (arm64, amd64). + if runtime.GOOS == constants.DarwinOS { + return version == t.Version && os == binOS + } + return version == t.Version && os == binOS && arch == binArch + } + + return version == t.Version && os == t.OS && arch == t.Arch +} + +// GetToolsConfig reads the configuration file for client tools managed updates, // and acquires a filesystem lock until the configuration is read and deserialized. -func getToolsConfig(toolsDir string) (ctc *ClientToolsConfig, err error) { +func GetToolsConfig(toolsDir string) (ctc *ClientToolsConfig, err error) { unlock, err := utils.FSWriteLock(filepath.Join(toolsDir, lockFileName)) if err != nil { return nil, trace.Wrap(err) @@ -167,9 +210,9 @@ func getToolsConfig(toolsDir string) (ctc *ClientToolsConfig, err error) { return ctc, nil } -// updateToolsConfig creates or opens the configuration file for client tools managed updates, +// UpdateToolsConfig creates or opens the configuration file for client tools managed updates, // and acquires a filesystem lock until the configuration is written and closed. -func updateToolsConfig(toolsDir string, update func(ctc *ClientToolsConfig) error) (err error) { +func UpdateToolsConfig(toolsDir string, update func(ctc *ClientToolsConfig) error) (err error) { unlock, err := utils.FSWriteLock(filepath.Join(toolsDir, lockFileName)) if err != nil { return trace.Wrap(err) diff --git a/lib/autoupdate/tools/migration.go b/lib/autoupdate/tools/migration.go index d0b9fe94c31..9cce05f75d1 100644 --- a/lib/autoupdate/tools/migration.go +++ b/lib/autoupdate/tools/migration.go @@ -24,6 +24,7 @@ import ( "log/slog" "os" "path/filepath" + "runtime" "strings" "github.com/google/uuid" @@ -40,7 +41,7 @@ const ( // migrateV1AndUpdateConfig launches migration process and add migrated // tools to configuration file. func migrateV1AndUpdateConfig(toolsDir string, tools []string) error { - if err := updateToolsConfig(toolsDir, func(ctc *ClientToolsConfig) error { + if err := UpdateToolsConfig(toolsDir, func(ctc *ClientToolsConfig) error { migratedTools, err := migrateV1(toolsDir, tools) if err != nil { return trace.Wrap(err) @@ -50,7 +51,7 @@ func migrateV1AndUpdateConfig(toolsDir string, tools []string) error { } for _, tool := range migratedTools { - ctc.AddTool(tool) + ctc.AddTool(toolsDir, tool) } return nil }); err != nil { @@ -106,6 +107,8 @@ func migrateV1(toolsDir string, tools []string) (map[string]Tool, error) { } else { migratedTools[toolVersion] = Tool{ Version: toolVersion, + OS: runtime.GOOS, + Arch: runtime.GOARCH, PathMap: map[string]string{tool: filepath.Join(newPkg, relPath)}, } } @@ -129,6 +132,8 @@ func migrateV1(toolsDir string, tools []string) (map[string]Tool, error) { } migratedTools[toolVersion] = Tool{ Version: toolVersion, + OS: runtime.GOOS, + Arch: runtime.GOARCH, PathMap: map[string]string{tool: filepath.Join(newPkg, tool)}, } } diff --git a/lib/autoupdate/tools/updater.go b/lib/autoupdate/tools/updater.go index 36152e8b4ac..8dd271397c9 100644 --- a/lib/autoupdate/tools/updater.go +++ b/lib/autoupdate/tools/updater.go @@ -172,7 +172,7 @@ func (u *Updater) CheckLocal(ctx context.Context, profileName string) (resp *Upd // We should acquire and release the lock before checking the version // by executing the binary, as it might block tool execution until the version // check is completed, which can take several seconds. - ctc, err := getToolsConfig(u.toolsDir) + ctc, err := GetToolsConfig(u.toolsDir) if err != nil { return nil, trace.Wrap(err) } @@ -195,7 +195,7 @@ func (u *Updater) CheckLocal(ctx context.Context, profileName string) (resp *Upd return nil, trace.Wrap(err) } - if !ctc.HasVersion(toolsVersion) { + if !ctc.HasVersion(u.toolsDir, toolsVersion, runtime.GOOS, runtime.GOARCH) { if err := migrateV1AndUpdateConfig(u.toolsDir, u.tools); err != nil { // Execution should not be interrupted if migration fails. Instead, it's better to // re-download the version that was supposed to be migrated but failed for some reason. @@ -227,7 +227,7 @@ func (u *Updater) CheckRemote(ctx context.Context, proxyAddr string, insecure bo return &UpdateResponse{Version: "", ReExec: false}, nil // Requested version already the same as client version. case u.localVersion: - if err := updateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { + if err := UpdateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { ctc.SetConfig(proxyHost, requestedVersion, false) return nil }); err != nil { @@ -244,7 +244,7 @@ func (u *Updater) CheckRemote(ctx context.Context, proxyAddr string, insecure bo // If the environment variable is set during a remote check, // prioritize this version for the current host and use it as the default // for all commands under the current profile. - if err := updateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { + if err := UpdateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { ctc.SetConfig(proxyHost, requestedVersion, false) return nil }); err != nil { @@ -279,7 +279,7 @@ func (u *Updater) CheckRemote(ctx context.Context, proxyAddr string, insecure bo updateResp = &UpdateResponse{Version: resp.AutoUpdate.ToolsVersion, ReExec: true} } - if err := updateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { + if err := UpdateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { ctc.SetConfig(proxyHost, updateResp.Version, updateResp.Disabled) return nil }); err != nil { @@ -292,7 +292,7 @@ func (u *Updater) CheckRemote(ctx context.Context, proxyAddr string, insecure bo // Update acquires filesystem lock, downloads requested version package, unarchive, replace // existing one and cleanups the previous downloads with defined updater directory suffix. func (u *Updater) Update(ctx context.Context, toolsVersion string) error { - err := updateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { + err := UpdateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { // ignoreTools is the list of tools installed and tracked by the config. // They should be preserved during cleanup. If we have more than [defaultSizeStoredVersion] // versions, the updater will forget about the least used version. @@ -301,7 +301,7 @@ func (u *Updater) Update(ctx context.Context, toolsVersion string) error { // If the version of the running binary or the version downloaded to // tools directory is the same as the requested version of client tools, // nothing to be done, exit early. - if tool.Version == toolsVersion { + if tool.IsEqual(u.toolsDir, toolsVersion, runtime.GOOS, runtime.GOARCH) { return nil } ignoreTools = append(ignoreTools, tool.PackageNames()...) @@ -383,7 +383,7 @@ func (u *Updater) update(ctx context.Context, ctc *ClientToolsConfig, pkg packag for key, val := range toolsMap { toolsMap[key] = filepath.Join(pkgName, val) } - ctc.AddTool(Tool{Version: pkg.Version, PathMap: toolsMap}) + ctc.AddTool(u.toolsDir, Tool{Version: pkg.Version, OS: runtime.GOOS, Arch: runtime.GOARCH, PathMap: toolsMap}) return nil } @@ -391,8 +391,8 @@ func (u *Updater) update(ctx context.Context, ctc *ClientToolsConfig, pkg packag // ToolPath loads full path from config file to specific tool and version. func (u *Updater) ToolPath(toolName, toolVersion string) (path string, err error) { var tool *Tool - if err := updateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { - tool = ctc.SelectVersion(toolVersion) + if err := UpdateToolsConfig(u.toolsDir, func(ctc *ClientToolsConfig) error { + tool = ctc.SelectVersion(u.toolsDir, toolVersion, runtime.GOOS, runtime.GOARCH) return nil }); err != nil { return "", trace.Wrap(err)