Skip to content

Commit

Permalink
Disable client tools auto update disabled if there are no home dir (#…
Browse files Browse the repository at this point in the history
…49199)

Move updater to general tools package
  • Loading branch information
vapopov authored Nov 19, 2024
1 parent 5244eac commit ce71412
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 148 deletions.
1 change: 0 additions & 1 deletion integration/autoupdate/tools/updater/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func main() {
ctx, _ = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)

updater := tools.NewUpdater(
tools.DefaultClientTools(),
toolsDir,
version,
tools.WithBaseURL(baseURL),
Expand Down
3 changes: 0 additions & 3 deletions integration/autoupdate/tools/updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ func TestUpdate(t *testing.T) {

// Fetch compiled test binary with updater logic and install to $TELEPORT_HOME.
updater := tools.NewUpdater(
tools.DefaultClientTools(),
toolsDir,
testVersions[0],
tools.WithBaseURL(baseURL),
Expand Down Expand Up @@ -91,7 +90,6 @@ func TestParallelUpdate(t *testing.T) {

// Initial fetch the updater binary un-archive and replace.
updater := tools.NewUpdater(
tools.DefaultClientTools(),
toolsDir,
testVersions[0],
tools.WithBaseURL(baseURL),
Expand Down Expand Up @@ -165,7 +163,6 @@ func TestUpdateInterruptSignal(t *testing.T) {

// Initial fetch the updater binary un-archive and replace.
updater := tools.NewUpdater(
tools.DefaultClientTools(),
toolsDir,
testVersions[0],
tools.WithBaseURL(baseURL),
Expand Down
20 changes: 14 additions & 6 deletions lib/autoupdate/tools/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ func WithClient(client *http.Client) Option {
}
}

// WithTools defines custom list of the tools has to be installed by updater.
func WithTools(tools []string) Option {
return func(u *Updater) {
u.tools = tools
}
}

// Updater is updater implementation for the client tools auto updates.
type Updater struct {
toolsDir string
Expand All @@ -92,13 +99,14 @@ type Updater struct {
client *http.Client
}

// NewUpdater initializes the updater for client tools auto updates. We need to specify the list
// of tools (e.g., `tsh`, `tctl`) that should be updated, the tools directory path where we
// download, extract package archives with the new version, and replace symlinks (e.g., `$TELEPORT_HOME/bin`).
// The base URL of the CDN with Teleport packages and the `http.Client` can be customized via options.
func NewUpdater(tools []string, toolsDir string, localVersion string, options ...Option) *Updater {
// NewUpdater initializes the updater for client tools auto updates. We need to specify the tools directory
// path where we download, extract package archives with the new version, and replace symlinks
// (e.g., `$TELEPORT_HOME/bin`).
// The base URL of the CDN with Teleport packages, the `http.Client` and the list of tools (e.g., `tsh`, `tctl`)
// that must be updated can be customized via options.
func NewUpdater(toolsDir, localVersion string, options ...Option) *Updater {
updater := &Updater{
tools: tools,
tools: DefaultClientTools(),
toolsDir: toolsDir,
localVersion: localVersion,
baseURL: baseURL,
Expand Down
34 changes: 2 additions & 32 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ import (
"github.com/gravitational/teleport/lib/auth/touchid"
wancli "github.com/gravitational/teleport/lib/auth/webauthncli"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/autoupdate/tools"
libmfa "github.com/gravitational/teleport/lib/client/mfa"
"github.com/gravitational/teleport/lib/client/sso"
"github.com/gravitational/teleport/lib/client/terminal"
Expand All @@ -96,7 +95,7 @@ import (
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/agentconn"
logutils "github.com/gravitational/teleport/lib/utils/log"
"github.com/gravitational/teleport/lib/utils/signal"
"github.com/gravitational/teleport/tool/common/updater"
)

const (
Expand Down Expand Up @@ -710,38 +709,9 @@ func RetryWithRelogin(ctx context.Context, tc *TeleportClient, fn func() error,
return trace.Wrap(err)
}

// The user has typed a command like `tsh ssh ...` without being logged in,
// if the running binary needs to be updated, update and re-exec.
//
// If needed, download the new version of {tsh, tctl} and re-exec. Make
// sure to exit this process with the same exit code as the child process.
//
toolsDir, err := tools.Dir()
if err != nil {
if err := updater.CheckAndUpdateRemote(ctx, teleport.Version, tc.WebProxyAddr, tc.InsecureSkipVerify); err != nil {
return trace.Wrap(err)
}
updater := tools.NewUpdater(tools.DefaultClientTools(), toolsDir, teleport.Version)
toolsVersion, reExec, err := updater.CheckRemote(ctx, tc.WebProxyAddr, tc.InsecureSkipVerify)
if err != nil {
return trace.Wrap(err)
}
if reExec {
ctxUpdate, cancel := signal.GetSignalHandler().NotifyContext(context.Background())
defer cancel()
// Download the version of client tools required by the cluster.
err := updater.UpdateWithLock(ctxUpdate, toolsVersion)
if err != nil && !errors.Is(err, context.Canceled) {
utils.FatalError(err)
}
// Re-execute client tools with the correct version of client tools.
code, err := updater.Exec()
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Debugf("Failed to re-exec client tool: %v.", err)
os.Exit(code)
} else if err == nil {
os.Exit(code)
}
}

if opt.afterLoginHook != nil {
if err := opt.afterLoginHook(); err != nil {
Expand Down
112 changes: 112 additions & 0 deletions tool/common/updater/client_tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

package updater

import (
"context"
"errors"
"log/slog"
"os"

"github.com/gravitational/trace"

"github.com/gravitational/teleport/lib/autoupdate/tools"
stacksignal "github.com/gravitational/teleport/lib/utils/signal"
)

// 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
// package is downloaded, extracted to the client tools directory, and re-executed
// 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 {
toolsDir, err := tools.Dir()
if err != nil {
slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
return nil
}
updater := tools.NewUpdater(toolsDir, currentVersion)
// 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.
toolsVersion, reExec, err := updater.CheckLocal()
if err != nil {
return trace.Wrap(err)
}
if reExec {
return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion))
}

return nil
}

// CheckAndUpdateRemote verifies client tools version is set for update in cluster
// configuration by making the http request to `webapi/find` endpoint. The requested
// version is compared with the current client tools version. If they differ, the version
// package is downloaded, extracted to the client tools directory, and re-executed
// 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 {
toolsDir, err := tools.Dir()
if err != nil {
slog.WarnContext(ctx, "Client tools update is disabled", "error", err)
return nil
}
updater := tools.NewUpdater(toolsDir, currentVersion)
// The user has typed a command like `tsh ssh ...` without being logged in,
// if the running binary needs to be updated, update and re-exec.
//
// If needed, download the new version of client tools and re-exec. Make
// sure to exit this process with the same exit code as the child process.
toolsVersion, reExec, err := updater.CheckRemote(ctx, proxy, insecure)
if err != nil {
return trace.Wrap(err)
}
if reExec {
return trace.Wrap(updateAndReExec(ctx, updater, toolsVersion))
}

return nil
}

func updateAndReExec(ctx context.Context, updater *tools.Updater, toolsVersion string) error {
ctxUpdate, cancel := stacksignal.GetSignalHandler().NotifyContext(ctx)
defer cancel()
// Download 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(ctxUpdate, toolsVersion)
if err != nil && !errors.Is(err, context.Canceled) {
return trace.Wrap(err)
}

// Re-execute client tools with the correct version of client tools.
code, err := updater.Exec()
if err != nil && !errors.Is(err, os.ErrNotExist) {
slog.DebugContext(ctx, "Failed to re-exec client tool", "error", err)
os.Exit(code)
} else if err == nil {
os.Exit(code)
}

return nil
}
38 changes: 3 additions & 35 deletions tool/tctl/common/tctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import (
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/auth/state"
"github.com/gravitational/teleport/lib/auth/storage"
"github.com/gravitational/teleport/lib/autoupdate/tools"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/client/identityfile"
libmfa "github.com/gravitational/teleport/lib/client/mfa"
Expand All @@ -56,8 +55,8 @@ import (
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/hostid"
"github.com/gravitational/teleport/lib/utils/signal"
"github.com/gravitational/teleport/tool/common"
"github.com/gravitational/teleport/tool/common/updater"
)

const (
Expand Down Expand Up @@ -108,42 +107,11 @@ type CLICommand interface {
//
// distribution: name of the Teleport distribution
func Run(ctx context.Context, commands []CLICommand) {
// The user has typed a command like `tsh ssh ...` without being logged in,
// if the running binary needs to be updated, update and re-exec.
//
// If needed, download the new version of {tsh, tctl} and re-exec. Make
// sure to exit this process with the same exit code as the child process.
//
toolsDir, err := tools.Dir()
if err != nil {
utils.FatalError(err)
}
updater := tools.NewUpdater(tools.DefaultClientTools(), toolsDir, teleport.Version)
toolsVersion, reExec, err := updater.CheckLocal()
if err != nil {
if err := updater.CheckAndUpdateLocal(ctx, teleport.Version); err != nil {
utils.FatalError(err)
}
if reExec {
ctxUpdate, cancel := signal.GetSignalHandler().NotifyContext(ctx)
defer cancel()
// Download 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(ctxUpdate, toolsVersion)
if err != nil && !errors.Is(err, context.Canceled) {
utils.FatalError(err)
}
// Re-execute client tools with the correct version of client tools.
code, err := updater.Exec()
if err != nil && !errors.Is(err, os.ErrNotExist) {
log.Debugf("Failed to re-exec client tool: %v.", err)
os.Exit(code)
} else if err == nil {
os.Exit(code)
}
}

err = TryRun(commands, os.Args[1:])
err := TryRun(commands, os.Args[1:])
if err != nil {
var exitError *common.ExitCodeError
if errors.As(err, &exitError) {
Expand Down
Loading

0 comments on commit ce71412

Please sign in to comment.