mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
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.
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user