mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
[teleport-update] Add systemd setup (#49174)
* service and timer * comments * feedback * feedback
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/google/renameio/v2"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
updateServiceTemplate = `# teleport-update
|
||||
[Unit]
|
||||
Description=Teleport auto-update service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart={{.LinkDir}}/bin/teleport-update update
|
||||
`
|
||||
updateTimerTemplate = `# teleport-update
|
||||
[Unit]
|
||||
Description=Teleport auto-update timer unit
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=1m
|
||||
OnUnitActiveSec=5m
|
||||
RandomizedDelaySec=1m
|
||||
|
||||
[Install]
|
||||
WantedBy=teleport.service
|
||||
`
|
||||
)
|
||||
|
||||
// Setup installs service and timer files for the teleport-update binary.
|
||||
// Afterwords, Setup reloads systemd and enables the timer with --now.
|
||||
func Setup(ctx context.Context, log *slog.Logger, linkDir, dataDir string) error {
|
||||
err := writeConfigFiles(linkDir, dataDir)
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to write teleport-update systemd config files: %w", err)
|
||||
}
|
||||
svc := &SystemdService{
|
||||
ServiceName: "teleport-update.timer",
|
||||
Log: log,
|
||||
}
|
||||
if err := svc.Sync(ctx); err != nil {
|
||||
return trace.Errorf("failed to sync systemd config: %w", err)
|
||||
}
|
||||
if err := svc.Enable(ctx, true); err != nil {
|
||||
return trace.Errorf("failed to enable teleport-update systemd timer: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeConfigFiles(linkDir, dataDir string) error {
|
||||
servicePath := filepath.Join(linkDir, serviceDir, updateServiceName)
|
||||
err := writeTemplate(servicePath, updateServiceTemplate, linkDir, dataDir)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
timerPath := filepath.Join(linkDir, serviceDir, updateTimerName)
|
||||
err = writeTemplate(timerPath, updateTimerTemplate, linkDir, dataDir)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeTemplate(path, t, linkDir, dataDir string) error {
|
||||
dir, file := filepath.Split(path)
|
||||
if err := os.MkdirAll(dir, systemDirMode); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
opts := []renameio.Option{
|
||||
renameio.WithPermissions(configFileMode),
|
||||
renameio.WithExistingPermissions(),
|
||||
}
|
||||
f, err := renameio.NewPendingFile(path, opts...)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
defer f.Cleanup()
|
||||
|
||||
tmpl, err := template.New(file).Parse(t)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
err = tmpl.Execute(f, struct {
|
||||
LinkDir string
|
||||
DataDir string
|
||||
}{linkDir, dataDir})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return trace.Wrap(f.CloseAtomicallyReplace())
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
libdefaults "github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils/golden"
|
||||
)
|
||||
|
||||
func TestWriteConfigFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
linkDir := t.TempDir()
|
||||
dataDir := t.TempDir()
|
||||
err := writeConfigFiles(linkDir, dataDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, p := range []string{
|
||||
filepath.Join(linkDir, serviceDir, updateServiceName),
|
||||
filepath.Join(linkDir, serviceDir, updateTimerName),
|
||||
} {
|
||||
t.Run(filepath.Base(p), func(t *testing.T) {
|
||||
data, err := os.ReadFile(p)
|
||||
require.NoError(t, err)
|
||||
data = replaceValues(data, map[string]string{
|
||||
DefaultLinkDir: linkDir,
|
||||
libdefaults.DataDir: dataDir,
|
||||
})
|
||||
if golden.ShouldSet() {
|
||||
golden.Set(t, data)
|
||||
}
|
||||
require.Equal(t, string(golden.Get(t)), string(data))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func replaceValues(data []byte, m map[string]string) []byte {
|
||||
for k, v := range m {
|
||||
data = bytes.ReplaceAll(data, []byte(v),
|
||||
[]byte(k))
|
||||
}
|
||||
return data
|
||||
}
|
||||
@@ -55,11 +55,15 @@ const (
|
||||
systemDirMode = 0755
|
||||
)
|
||||
|
||||
var (
|
||||
const (
|
||||
// serviceDir contains the relative path to the Teleport SystemD service dir.
|
||||
serviceDir = filepath.Join("lib", "systemd", "system")
|
||||
serviceDir = "lib/systemd/system"
|
||||
// serviceName contains the name of the Teleport SystemD service file.
|
||||
serviceName = "teleport.service"
|
||||
// updateServiceName contains the name of the Teleport Update Systemd service
|
||||
updateServiceName = "teleport-update.service"
|
||||
// updateTimerName contains the name of the Teleport Update Systemd timer
|
||||
updateTimerName = "teleport-update.timer"
|
||||
)
|
||||
|
||||
// LocalInstaller manages the creation and removal of installations
|
||||
@@ -431,7 +435,7 @@ func (li *LocalInstaller) Link(ctx context.Context, version string) (revert func
|
||||
return revert, nil
|
||||
}
|
||||
|
||||
// LinkSystem links the system (package) version into the system LinkBinDir and LinkServiceDir.
|
||||
// LinkSystem links the system (package) version into LinkBinDir and LinkServiceDir.
|
||||
// The revert function restores the previous linking.
|
||||
// See Installer interface for additional specs.
|
||||
func (li *LocalInstaller) LinkSystem(ctx context.Context) (revert func(context.Context) bool, err error) {
|
||||
@@ -539,7 +543,7 @@ func (li *LocalInstaller) forceLinks(ctx context.Context, binDir, svcDir string)
|
||||
dst := filepath.Join(li.LinkServiceDir, serviceName)
|
||||
orig, err := forceCopy(dst, src, maxServiceFileSize)
|
||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||
return revert, trace.Errorf("failed to create file for %s: %w", serviceName, err)
|
||||
return revert, trace.Errorf("failed to write file %s: %w", serviceName, err)
|
||||
}
|
||||
if orig != nil {
|
||||
revertFiles = append(revertFiles, *orig)
|
||||
@@ -782,13 +786,5 @@ func (li *LocalInstaller) isLinked(versionDir string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
linkData, err := readFileN(filepath.Join(li.LinkServiceDir, serviceName), maxServiceFileSize)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
versionData, err := readFileN(filepath.Join(versionDir, serviceDir, serviceName), maxServiceFileSize)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return bytes.Equal(linkData, versionData), nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -252,6 +252,24 @@ func (s SystemdService) Sync(ctx context.Context) error {
|
||||
if code != 0 {
|
||||
return trace.Errorf("unable to reload systemd configuration")
|
||||
}
|
||||
s.Log.InfoContext(ctx, "Systemd configuration synced.", unitKey, s.ServiceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enable the systemd service.
|
||||
func (s SystemdService) Enable(ctx context.Context, now bool) error {
|
||||
if err := s.checkSystem(ctx); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
args := []string{"enable", s.ServiceName}
|
||||
if now {
|
||||
args = append(args, "--now")
|
||||
}
|
||||
code := s.systemctl(ctx, slog.LevelError, args...)
|
||||
if code != 0 {
|
||||
return trace.Errorf("unable to enable systemd service")
|
||||
}
|
||||
s.Log.InfoContext(ctx, "Service enabled.", unitKey, s.ServiceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# teleport-update
|
||||
[Unit]
|
||||
Description=Teleport auto-update service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/teleport-update update
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# teleport-update
|
||||
[Unit]
|
||||
Description=Teleport auto-update timer unit
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=1m
|
||||
OnUnitActiveSec=5m
|
||||
RandomizedDelaySec=1m
|
||||
|
||||
[Install]
|
||||
WantedBy=teleport.service
|
||||
+102
-37
@@ -27,7 +27,9 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -36,6 +38,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/gravitational/teleport/api/client/webclient"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
libdefaults "github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
libutils "github.com/gravitational/teleport/lib/utils"
|
||||
@@ -46,6 +49,10 @@ const (
|
||||
DefaultLinkDir = "/usr/local"
|
||||
// DefaultSystemDir is the location where packaged Teleport binaries and services are installed.
|
||||
DefaultSystemDir = "/usr/local/teleport-system"
|
||||
// VersionsDirName specifies the name of the subdirectory inside the Teleport data dir for storing Teleport versions.
|
||||
VersionsDirName = "versions"
|
||||
// BinaryName specifies the name of the updater binary.
|
||||
BinaryName = "teleport-update"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -136,16 +143,20 @@ func NewLocalUpdater(cfg LocalUpdaterConfig) (*Updater, error) {
|
||||
if cfg.SystemDir == "" {
|
||||
cfg.SystemDir = DefaultSystemDir
|
||||
}
|
||||
if cfg.VersionsDir == "" {
|
||||
cfg.VersionsDir = filepath.Join(libdefaults.DataDir, "versions")
|
||||
if cfg.DataDir == "" {
|
||||
cfg.DataDir = libdefaults.DataDir
|
||||
}
|
||||
installDir := filepath.Join(cfg.DataDir, VersionsDirName)
|
||||
if err := os.MkdirAll(installDir, systemDirMode); err != nil {
|
||||
return nil, trace.Errorf("failed to create install directory: %w", err)
|
||||
}
|
||||
return &Updater{
|
||||
Log: cfg.Log,
|
||||
Pool: certPool,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
ConfigPath: filepath.Join(cfg.VersionsDir, updateConfigName),
|
||||
ConfigPath: filepath.Join(installDir, updateConfigName),
|
||||
Installer: &LocalInstaller{
|
||||
InstallDir: cfg.VersionsDir,
|
||||
InstallDir: installDir,
|
||||
LinkBinDir: filepath.Join(cfg.LinkDir, "bin"),
|
||||
// For backwards-compatibility with symlinks created by package-based installs, we always
|
||||
// link into /lib/systemd/system, even though, e.g., /usr/local/lib/systemd/system would work.
|
||||
@@ -162,6 +173,28 @@ func NewLocalUpdater(cfg LocalUpdaterConfig) (*Updater, error) {
|
||||
PIDPath: "/run/teleport.pid",
|
||||
Log: cfg.Log,
|
||||
},
|
||||
Setup: func(ctx context.Context) error {
|
||||
name := filepath.Join(cfg.LinkDir, "bin", BinaryName)
|
||||
if cfg.SelfSetup && runtime.GOOS == constants.LinuxOS {
|
||||
name = "/proc/self/exe"
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, name,
|
||||
"--data-dir", cfg.DataDir,
|
||||
"--link-dir", cfg.LinkDir,
|
||||
"setup")
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
cfg.Log.InfoContext(ctx, "Executing new teleport-update binary to update configuration.")
|
||||
defer cfg.Log.InfoContext(ctx, "Finished executing new teleport-update binary.")
|
||||
err := cmd.Run()
|
||||
if cmd.ProcessState.ExitCode() == CodeNotSupported {
|
||||
return ErrNotSupported
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
},
|
||||
Revert: func(ctx context.Context) error {
|
||||
return trace.Wrap(Setup(ctx, cfg.Log, cfg.LinkDir, cfg.DataDir))
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -175,12 +208,14 @@ type LocalUpdaterConfig struct {
|
||||
// DownloadTimeout is a timeout for file download requests.
|
||||
// Defaults to no timeout.
|
||||
DownloadTimeout time.Duration
|
||||
// VersionsDir for installing Teleport (usually /var/lib/teleport/versions).
|
||||
VersionsDir string
|
||||
// DataDir for Teleport (usually /var/lib/teleport).
|
||||
DataDir string
|
||||
// LinkDir for installing Teleport (usually /usr/local).
|
||||
LinkDir string
|
||||
// SystemDir for package-installed Teleport installations (usually /usr/local/teleport-system).
|
||||
SystemDir string
|
||||
// SelfSetup mode for using the current version of the teleport-update to setup the update service.
|
||||
SelfSetup bool
|
||||
}
|
||||
|
||||
// Updater implements the agent-local logic for Teleport agent auto-updates.
|
||||
@@ -197,6 +232,10 @@ type Updater struct {
|
||||
Installer Installer
|
||||
// Process manages a running instance of Teleport.
|
||||
Process Process
|
||||
// Setup installs the Teleport updater service using the linked installation.
|
||||
Setup func(ctx context.Context) error
|
||||
// Revert installs the Teleport updater service using the running installation.
|
||||
Revert func(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Installer provides an API for installing Teleport agents.
|
||||
@@ -237,6 +276,11 @@ var (
|
||||
ErrNotSupported = errors.New("not supported on this platform")
|
||||
)
|
||||
|
||||
const (
|
||||
// CodeNotSupported is returned when the operation is not supported on the platform.
|
||||
CodeNotSupported = 3
|
||||
)
|
||||
|
||||
// Process provides an API for interacting with a running Teleport process.
|
||||
type Process interface {
|
||||
// Reload must reload the Teleport process as gracefully as possible.
|
||||
@@ -337,6 +381,8 @@ func (u *Updater) Enable(ctx context.Context, override OverrideConfig) error {
|
||||
return trace.Errorf("agent version not available from Teleport cluster")
|
||||
}
|
||||
|
||||
u.Log.InfoContext(ctx, "Initiating initial update.", targetVersionKey, targetVersion, activeVersionKey, cfg.Status.ActiveVersion)
|
||||
|
||||
if err := u.update(ctx, cfg, targetVersion, flags); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -477,7 +523,7 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s
|
||||
}
|
||||
}
|
||||
|
||||
// Install the desired version (or validate existing installation)
|
||||
// Install and link the desired version (or validate existing installation)
|
||||
|
||||
template := cfg.Spec.URLTemplate
|
||||
if template == "" {
|
||||
@@ -487,6 +533,12 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to install: %w", err)
|
||||
}
|
||||
|
||||
// TODO(sclevine): if the target version has fewer binaries, this will
|
||||
// leave old binaries linked. This may prevent the installation from
|
||||
// being removed. To fix this, we should look for orphaned binaries
|
||||
// and remove them, or alternatively, attempt to remove extra versions.
|
||||
|
||||
revert, err := u.Installer.Link(ctx, targetVersion)
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to link: %w", err)
|
||||
@@ -495,22 +547,31 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s
|
||||
// If we fail to revert after this point, the next update/enable will
|
||||
// fix the link to restore the active version.
|
||||
|
||||
// Sync process configuration after linking.
|
||||
|
||||
if err := u.Process.Sync(ctx); err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return trace.Errorf("sync canceled")
|
||||
}
|
||||
// If sync fails, we may have left the host in a bad state, so we revert linking and re-Sync.
|
||||
u.Log.ErrorContext(ctx, "Reverting symlinks due to invalid configuration.")
|
||||
revertConfig := func(ctx context.Context) bool {
|
||||
if ok := revert(ctx); !ok {
|
||||
u.Log.ErrorContext(ctx, "Failed to revert Teleport symlinks. Installation likely broken.")
|
||||
} else if err := u.Process.Sync(ctx); err != nil {
|
||||
u.Log.ErrorContext(ctx, "Failed to sync configuration after failed restart.", errorKey, err)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
if err := u.Revert(ctx); err != nil {
|
||||
u.Log.ErrorContext(ctx, "Failed to revert configuration after failed restart.", errorKey, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Setup teleport-updater configuration and sync systemd.
|
||||
|
||||
err = u.Setup(ctx)
|
||||
if errors.Is(err, ErrNotSupported) {
|
||||
u.Log.WarnContext(ctx, "Not syncing systemd configuration because systemd is not running.")
|
||||
} else if errors.Is(err, context.Canceled) {
|
||||
return trace.Errorf("sync canceled")
|
||||
} else if err != nil {
|
||||
// If sync fails, we may have left the host in a bad state, so we revert linking and re-Sync.
|
||||
u.Log.ErrorContext(ctx, "Reverting symlinks due to invalid configuration.")
|
||||
if ok := revertConfig(ctx); ok {
|
||||
u.Log.WarnContext(ctx, "Teleport updater encountered a configuration error and successfully reverted the installation.")
|
||||
}
|
||||
|
||||
return trace.Errorf("failed to validate configuration for new version %q of Teleport: %w", targetVersion, err)
|
||||
}
|
||||
|
||||
@@ -518,22 +579,23 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s
|
||||
|
||||
if cfg.Status.ActiveVersion != targetVersion {
|
||||
u.Log.InfoContext(ctx, "Target version successfully installed.", targetVersionKey, targetVersion)
|
||||
if err := u.Process.Reload(ctx); err != nil && !errors.Is(err, ErrNotNeeded) {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return trace.Errorf("reload canceled")
|
||||
}
|
||||
// If reloading Teleport at the new version fails, revert, resync, and reload.
|
||||
u.Log.ErrorContext(ctx, "Reverting symlinks due to failed restart.")
|
||||
if ok := revert(ctx); !ok {
|
||||
u.Log.ErrorContext(ctx, "Failed to revert Teleport symlinks to older version. Installation likely broken.")
|
||||
} else if err := u.Process.Sync(ctx); err != nil {
|
||||
u.Log.ErrorContext(ctx, "Invalid configuration found after reverting Teleport to older version. Installation likely broken.", errorKey, err)
|
||||
} else if err := u.Process.Reload(ctx); err != nil && !errors.Is(err, ErrNotNeeded) {
|
||||
u.Log.ErrorContext(ctx, "Failed to revert Teleport to older version. Installation likely broken.", errorKey, err)
|
||||
} else {
|
||||
u.Log.WarnContext(ctx, "Teleport updater encountered an error during the update and successfully reverted the installation.")
|
||||
}
|
||||
err = u.Process.Reload(ctx)
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return trace.Errorf("reload canceled")
|
||||
}
|
||||
if err != nil &&
|
||||
!errors.Is(err, ErrNotNeeded) && // no output if restart not needed
|
||||
!errors.Is(err, ErrNotSupported) { // already logged above for Sync
|
||||
|
||||
// If reloading Teleport at the new version fails, revert and reload.
|
||||
u.Log.ErrorContext(ctx, "Reverting symlinks due to failed restart.")
|
||||
if ok := revertConfig(ctx); ok {
|
||||
if err := u.Process.Reload(ctx); err != nil && !errors.Is(err, ErrNotNeeded) {
|
||||
u.Log.ErrorContext(ctx, "Failed to reload Teleport after reverting. Installation likely broken.", errorKey, err)
|
||||
} else {
|
||||
u.Log.WarnContext(ctx, "Teleport updater encountered a configuration error and successfully reverted the installation.")
|
||||
}
|
||||
}
|
||||
return trace.Errorf("failed to start new version %q of Teleport: %w", targetVersion, err)
|
||||
}
|
||||
cfg.Status.BackupVersion = cfg.Status.ActiveVersion
|
||||
@@ -554,7 +616,6 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s
|
||||
if n := len(versions); n > 2 {
|
||||
u.Log.WarnContext(ctx, "More than 2 versions of Teleport installed. Version directory may need cleanup to save space.", "count", n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -609,7 +670,11 @@ func validateConfigSpec(spec *UpdateSpec, override OverrideConfig) error {
|
||||
if override.Group != "" {
|
||||
spec.Group = override.Group
|
||||
}
|
||||
if override.URLTemplate != "" {
|
||||
switch override.URLTemplate {
|
||||
case "":
|
||||
case "default":
|
||||
spec.URLTemplate = ""
|
||||
default:
|
||||
spec.URLTemplate = override.URLTemplate
|
||||
}
|
||||
if spec.URLTemplate != "" &&
|
||||
@@ -620,7 +685,7 @@ func validateConfigSpec(spec *UpdateSpec, override OverrideConfig) error {
|
||||
}
|
||||
|
||||
// LinkPackage creates links from the system (package) installation of Teleport, if they are needed.
|
||||
// LinkPackage returns nils and warns if an auto-updates version is already linked, but auto-updates is disabled.
|
||||
// LinkPackage returns nil and warns if an auto-updates version is already linked, but auto-updates is disabled.
|
||||
// LinkPackage returns an error only if an unknown version of Teleport is present (e.g., manually copied files).
|
||||
// This function is idempotent.
|
||||
func (u *Updater) LinkPackage(ctx context.Context) error {
|
||||
|
||||
@@ -83,7 +83,13 @@ func TestUpdater_Disable(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "update.yaml")
|
||||
cfgPath := filepath.Join(dir, VersionsDirName, "update.yaml")
|
||||
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
DataDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create config file only if provided in test case
|
||||
if tt.cfg != nil {
|
||||
@@ -92,11 +98,7 @@ func TestUpdater_Disable(t *testing.T) {
|
||||
err = os.WriteFile(cfgPath, b, 0600)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
VersionsDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = updater.Disable(context.Background())
|
||||
if tt.errMatch != "" {
|
||||
require.Error(t, err)
|
||||
@@ -131,7 +133,7 @@ func TestUpdater_Update(t *testing.T) {
|
||||
flags InstallFlags
|
||||
inWindow bool
|
||||
installErr error
|
||||
syncErr error
|
||||
setupErr error
|
||||
reloadErr error
|
||||
|
||||
removedVersion string
|
||||
@@ -139,9 +141,9 @@ func TestUpdater_Update(t *testing.T) {
|
||||
installedTemplate string
|
||||
linkedVersion string
|
||||
requestGroup string
|
||||
syncCalls int
|
||||
reloadCalls int
|
||||
revertCalls int
|
||||
setupCalls int
|
||||
errMatch string
|
||||
}{
|
||||
{
|
||||
@@ -164,8 +166,8 @@ func TestUpdater_Update(t *testing.T) {
|
||||
installedTemplate: "https://example.com",
|
||||
linkedVersion: "16.3.0",
|
||||
requestGroup: "group",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "updates disabled during window",
|
||||
@@ -293,8 +295,8 @@ func TestUpdater_Update(t *testing.T) {
|
||||
installedTemplate: "https://example.com",
|
||||
linkedVersion: "16.3.0",
|
||||
removedVersion: "backup-version",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "backup version kept when no change",
|
||||
@@ -336,8 +338,8 @@ func TestUpdater_Update(t *testing.T) {
|
||||
installedTemplate: "https://example.com",
|
||||
linkedVersion: "16.3.0",
|
||||
removedVersion: "backup-version",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "invalid metadata",
|
||||
@@ -345,7 +347,7 @@ func TestUpdater_Update(t *testing.T) {
|
||||
errMatch: "invalid",
|
||||
},
|
||||
{
|
||||
name: "sync fails",
|
||||
name: "setup fails",
|
||||
cfg: &UpdateConfig{
|
||||
Version: updateConfigVersion,
|
||||
Kind: updateConfigKind,
|
||||
@@ -359,16 +361,16 @@ func TestUpdater_Update(t *testing.T) {
|
||||
},
|
||||
},
|
||||
inWindow: true,
|
||||
syncErr: errors.New("sync error"),
|
||||
setupErr: errors.New("setup error"),
|
||||
|
||||
installedVersion: "16.3.0",
|
||||
installedTemplate: "https://example.com",
|
||||
linkedVersion: "16.3.0",
|
||||
removedVersion: "backup-version",
|
||||
syncCalls: 2,
|
||||
reloadCalls: 0,
|
||||
revertCalls: 1,
|
||||
errMatch: "sync error",
|
||||
setupCalls: 1,
|
||||
errMatch: "setup error",
|
||||
},
|
||||
{
|
||||
name: "reload fails",
|
||||
@@ -391,9 +393,9 @@ func TestUpdater_Update(t *testing.T) {
|
||||
installedTemplate: "https://example.com",
|
||||
linkedVersion: "16.3.0",
|
||||
removedVersion: "backup-version",
|
||||
syncCalls: 2,
|
||||
reloadCalls: 2,
|
||||
revertCalls: 1,
|
||||
setupCalls: 1,
|
||||
errMatch: "reload error",
|
||||
},
|
||||
}
|
||||
@@ -419,7 +421,13 @@ func TestUpdater_Update(t *testing.T) {
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "update.yaml")
|
||||
cfgPath := filepath.Join(dir, VersionsDirName, "update.yaml")
|
||||
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
DataDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create config file only if provided in test case
|
||||
if tt.cfg != nil {
|
||||
@@ -430,19 +438,16 @@ func TestUpdater_Update(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
VersionsDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var (
|
||||
installedVersion string
|
||||
installedTemplate string
|
||||
linkedVersion string
|
||||
removedVersion string
|
||||
installedFlags InstallFlags
|
||||
revertCalls int
|
||||
revertFuncCalls int
|
||||
setupCalls int
|
||||
revertSetupCalls int
|
||||
reloadCalls int
|
||||
)
|
||||
updater.Installer = &testInstaller{
|
||||
FuncInstall: func(_ context.Context, version, template string, flags InstallFlags) error {
|
||||
@@ -454,7 +459,7 @@ func TestUpdater_Update(t *testing.T) {
|
||||
FuncLink: func(_ context.Context, version string) (revert func(context.Context) bool, err error) {
|
||||
linkedVersion = version
|
||||
return func(_ context.Context) bool {
|
||||
revertCalls++
|
||||
revertFuncCalls++
|
||||
return true
|
||||
}, nil
|
||||
},
|
||||
@@ -466,20 +471,20 @@ func TestUpdater_Update(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
var (
|
||||
syncCalls int
|
||||
reloadCalls int
|
||||
)
|
||||
updater.Process = &testProcess{
|
||||
FuncSync: func(_ context.Context) error {
|
||||
syncCalls++
|
||||
return tt.syncErr
|
||||
},
|
||||
FuncReload: func(_ context.Context) error {
|
||||
reloadCalls++
|
||||
return tt.reloadErr
|
||||
},
|
||||
}
|
||||
updater.Setup = func(_ context.Context) error {
|
||||
setupCalls++
|
||||
return tt.setupErr
|
||||
}
|
||||
updater.Revert = func(_ context.Context) error {
|
||||
revertSetupCalls++
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err = updater.Update(ctx)
|
||||
@@ -495,9 +500,10 @@ func TestUpdater_Update(t *testing.T) {
|
||||
require.Equal(t, tt.removedVersion, removedVersion)
|
||||
require.Equal(t, tt.flags, installedFlags)
|
||||
require.Equal(t, tt.requestGroup, requestedGroup)
|
||||
require.Equal(t, tt.syncCalls, syncCalls)
|
||||
require.Equal(t, tt.reloadCalls, reloadCalls)
|
||||
require.Equal(t, tt.revertCalls, revertCalls)
|
||||
require.Equal(t, tt.revertCalls, revertSetupCalls)
|
||||
require.Equal(t, tt.revertCalls, revertFuncCalls)
|
||||
require.Equal(t, tt.setupCalls, setupCalls)
|
||||
|
||||
if tt.cfg == nil {
|
||||
_, err := os.Stat(cfgPath)
|
||||
@@ -594,7 +600,13 @@ func TestUpdater_LinkPackage(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "update.yaml")
|
||||
cfgPath := filepath.Join(dir, VersionsDirName, "update.yaml")
|
||||
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
DataDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create config file only if provided in test case
|
||||
if tt.cfg != nil {
|
||||
@@ -604,12 +616,6 @@ func TestUpdater_LinkPackage(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
VersionsDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var tryLinkSystemCalls int
|
||||
updater.Installer = &testInstaller{
|
||||
FuncTryLinkSystem: func(_ context.Context) error {
|
||||
@@ -648,7 +654,7 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
userCfg OverrideConfig
|
||||
flags InstallFlags
|
||||
installErr error
|
||||
syncErr error
|
||||
setupErr error
|
||||
reloadErr error
|
||||
|
||||
removedVersion string
|
||||
@@ -656,9 +662,9 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedTemplate string
|
||||
linkedVersion string
|
||||
requestGroup string
|
||||
syncCalls int
|
||||
reloadCalls int
|
||||
revertCalls int
|
||||
setupCalls int
|
||||
errMatch string
|
||||
}{
|
||||
{
|
||||
@@ -679,8 +685,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedTemplate: "https://example.com",
|
||||
linkedVersion: "16.3.0",
|
||||
requestGroup: "group",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "config from user",
|
||||
@@ -704,8 +710,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedVersion: "new-version",
|
||||
installedTemplate: "https://example.com/new",
|
||||
linkedVersion: "new-version",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "already enabled",
|
||||
@@ -723,8 +729,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedVersion: "16.3.0",
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "insecure URL",
|
||||
@@ -764,8 +770,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedVersion: "16.3.0",
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 0,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "backup version removed on install",
|
||||
@@ -782,8 +788,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
removedVersion: "backup-version",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "backup version kept for validation",
|
||||
@@ -800,8 +806,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
removedVersion: "",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 0,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "config does not exist",
|
||||
@@ -809,8 +815,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedVersion: "16.3.0",
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "FIPS and Enterprise flags",
|
||||
@@ -818,8 +824,8 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedVersion: "16.3.0",
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
syncCalls: 1,
|
||||
reloadCalls: 1,
|
||||
setupCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "invalid metadata",
|
||||
@@ -827,16 +833,16 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
errMatch: "invalid",
|
||||
},
|
||||
{
|
||||
name: "sync fails",
|
||||
syncErr: errors.New("sync error"),
|
||||
name: "setup fails",
|
||||
setupErr: errors.New("setup error"),
|
||||
|
||||
installedVersion: "16.3.0",
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
syncCalls: 2,
|
||||
reloadCalls: 0,
|
||||
revertCalls: 1,
|
||||
errMatch: "sync error",
|
||||
setupCalls: 1,
|
||||
errMatch: "setup error",
|
||||
},
|
||||
{
|
||||
name: "reload fails",
|
||||
@@ -845,9 +851,9 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
installedVersion: "16.3.0",
|
||||
installedTemplate: cdnURITemplate,
|
||||
linkedVersion: "16.3.0",
|
||||
syncCalls: 2,
|
||||
reloadCalls: 2,
|
||||
revertCalls: 1,
|
||||
setupCalls: 1,
|
||||
errMatch: "reload error",
|
||||
},
|
||||
}
|
||||
@@ -855,7 +861,13 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "update.yaml")
|
||||
cfgPath := filepath.Join(dir, VersionsDirName, "update.yaml")
|
||||
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
DataDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create config file only if provided in test case
|
||||
if tt.cfg != nil {
|
||||
@@ -886,19 +898,16 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
tt.userCfg.Proxy = strings.TrimPrefix(server.URL, "https://")
|
||||
}
|
||||
|
||||
updater, err := NewLocalUpdater(LocalUpdaterConfig{
|
||||
InsecureSkipVerify: true,
|
||||
VersionsDir: dir,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var (
|
||||
installedVersion string
|
||||
installedTemplate string
|
||||
linkedVersion string
|
||||
removedVersion string
|
||||
installedFlags InstallFlags
|
||||
revertCalls int
|
||||
revertFuncCalls int
|
||||
reloadCalls int
|
||||
setupCalls int
|
||||
revertSetupCalls int
|
||||
)
|
||||
updater.Installer = &testInstaller{
|
||||
FuncInstall: func(_ context.Context, version, template string, flags InstallFlags) error {
|
||||
@@ -910,7 +919,7 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
FuncLink: func(_ context.Context, version string) (revert func(context.Context) bool, err error) {
|
||||
linkedVersion = version
|
||||
return func(_ context.Context) bool {
|
||||
revertCalls++
|
||||
revertFuncCalls++
|
||||
return true
|
||||
}, nil
|
||||
},
|
||||
@@ -922,20 +931,20 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
var (
|
||||
syncCalls int
|
||||
reloadCalls int
|
||||
)
|
||||
updater.Process = &testProcess{
|
||||
FuncSync: func(_ context.Context) error {
|
||||
syncCalls++
|
||||
return tt.syncErr
|
||||
},
|
||||
FuncReload: func(_ context.Context) error {
|
||||
reloadCalls++
|
||||
return tt.reloadErr
|
||||
},
|
||||
}
|
||||
updater.Setup = func(_ context.Context) error {
|
||||
setupCalls++
|
||||
return tt.setupErr
|
||||
}
|
||||
updater.Revert = func(_ context.Context) error {
|
||||
revertSetupCalls++
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err = updater.Enable(ctx, tt.userCfg)
|
||||
@@ -951,9 +960,10 @@ func TestUpdater_Enable(t *testing.T) {
|
||||
require.Equal(t, tt.removedVersion, removedVersion)
|
||||
require.Equal(t, tt.flags, installedFlags)
|
||||
require.Equal(t, tt.requestGroup, requestedGroup)
|
||||
require.Equal(t, tt.syncCalls, syncCalls)
|
||||
require.Equal(t, tt.reloadCalls, reloadCalls)
|
||||
require.Equal(t, tt.revertCalls, revertCalls)
|
||||
require.Equal(t, tt.revertCalls, revertSetupCalls)
|
||||
require.Equal(t, tt.revertCalls, revertFuncCalls)
|
||||
require.Equal(t, tt.setupCalls, setupCalls)
|
||||
|
||||
if tt.cfg == nil && err != nil {
|
||||
_, err := os.Stat(cfgPath)
|
||||
|
||||
@@ -21,6 +21,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -58,10 +59,8 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
// versionsDirName specifies the name of the subdirectory inside of the Teleport data dir for storing Teleport versions.
|
||||
versionsDirName = "versions"
|
||||
// lockFileName specifies the name of the file inside versionsDirName containing the flock lock preventing concurrent updater execution.
|
||||
lockFileName = ".lock"
|
||||
// lockFileName specifies the name of the file containing the flock lock preventing concurrent updater execution.
|
||||
lockFileName = ".update-lock"
|
||||
)
|
||||
|
||||
var plog = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentUpdater)
|
||||
@@ -84,6 +83,8 @@ type cliConfig struct {
|
||||
DataDir string
|
||||
// LinkDir for linking binaries and systemd services
|
||||
LinkDir string
|
||||
// SelfSetup mode for using the current version of the teleport-update to setup the update service.
|
||||
SelfSetup bool
|
||||
}
|
||||
|
||||
func Run(args []string) error {
|
||||
@@ -91,7 +92,7 @@ func Run(args []string) error {
|
||||
ctx := context.Background()
|
||||
ctx, _ = signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
app := libutils.InitCLIParser("teleport-update", appHelp).Interspersed(false)
|
||||
app := libutils.InitCLIParser(autoupdate.BinaryName, appHelp).Interspersed(false)
|
||||
app.Flag("debug", "Verbose logging to stdout.").
|
||||
Short('d').BoolVar(&ccfg.Debug)
|
||||
app.Flag("data-dir", "Teleport data directory. Access to this directory should be limited.").
|
||||
@@ -103,7 +104,7 @@ func Run(args []string) error {
|
||||
|
||||
app.HelpFlag.Short('h')
|
||||
|
||||
versionCmd := app.Command("version", "Print the version of your teleport-updater binary.")
|
||||
versionCmd := app.Command("version", fmt.Sprintf("Print the version of your %s binary.", autoupdate.BinaryName))
|
||||
|
||||
enableCmd := app.Command("enable", "Enable agent auto-updates and perform initial update.")
|
||||
enableCmd.Flag("proxy", "Address of the Teleport Proxy.").
|
||||
@@ -114,13 +115,20 @@ func Run(args []string) error {
|
||||
Short('t').Envar(templateEnvVar).StringVar(&ccfg.URLTemplate)
|
||||
enableCmd.Flag("force-version", "Force the provided version instead of querying it from the Teleport cluster.").
|
||||
Short('f').Envar(updateVersionEnvVar).Hidden().StringVar(&ccfg.ForceVersion)
|
||||
enableCmd.Flag("self-setup", "Use the current teleport-update binary to create systemd service config for auto-updates.").
|
||||
Short('s').Hidden().BoolVar(&ccfg.SelfSetup)
|
||||
// TODO(sclevine): add force-fips and force-enterprise as hidden flags
|
||||
|
||||
disableCmd := app.Command("disable", "Disable agent auto-updates.")
|
||||
|
||||
updateCmd := app.Command("update", "Update agent to the latest version, if a new version is available.")
|
||||
updateCmd.Flag("self-setup", "Use the current teleport-update binary to create systemd service config for auto-updates.").
|
||||
Short('s').Hidden().BoolVar(&ccfg.SelfSetup)
|
||||
|
||||
linkCmd := app.Command("link", "Link the system installation of Teleport from the Teleport package, if auto-updates is disabled.")
|
||||
linkCmd := app.Command("link-package", "Link the system installation of Teleport from the Teleport package, if auto-updates is disabled.")
|
||||
|
||||
setupCmd := app.Command("setup", "Write configuration files that run the update subcommand on a timer.").
|
||||
Hidden()
|
||||
|
||||
libutils.UpdateAppUsageTemplate(app, args)
|
||||
command, err := app.Parse(args)
|
||||
@@ -143,6 +151,8 @@ func Run(args []string) error {
|
||||
err = cmdUpdate(ctx, &ccfg)
|
||||
case linkCmd.FullCommand():
|
||||
err = cmdLink(ctx, &ccfg)
|
||||
case setupCmd.FullCommand():
|
||||
err = cmdSetup(ctx, &ccfg)
|
||||
case versionCmd.FullCommand():
|
||||
modules.GetModules().PrintVersion()
|
||||
default:
|
||||
@@ -172,12 +182,17 @@ func setupLogger(debug bool, format string) error {
|
||||
|
||||
// cmdDisable disables updates.
|
||||
func cmdDisable(ctx context.Context, ccfg *cliConfig) error {
|
||||
versionsDir := filepath.Join(ccfg.DataDir, versionsDirName)
|
||||
if err := os.MkdirAll(versionsDir, 0755); err != nil {
|
||||
return trace.Errorf("failed to create versions directory: %w", err)
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
DataDir: ccfg.DataDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
SelfSetup: ccfg.SelfSetup,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
|
||||
unlock, err := libutils.FSWriteLock(filepath.Join(versionsDir, lockFileName))
|
||||
unlock, err := libutils.FSWriteLock(filepath.Join(ccfg.DataDir, lockFileName))
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to grab concurrent execution lock: %w", err)
|
||||
}
|
||||
@@ -186,15 +201,6 @@ func cmdDisable(ctx context.Context, ccfg *cliConfig) error {
|
||||
plog.DebugContext(ctx, "Failed to close lock file", "error", err)
|
||||
}
|
||||
}()
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
VersionsDir: versionsDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
if err := updater.Disable(ctx); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -203,13 +209,19 @@ func cmdDisable(ctx context.Context, ccfg *cliConfig) error {
|
||||
|
||||
// cmdEnable enables updates and triggers an initial update.
|
||||
func cmdEnable(ctx context.Context, ccfg *cliConfig) error {
|
||||
versionsDir := filepath.Join(ccfg.DataDir, versionsDirName)
|
||||
if err := os.MkdirAll(versionsDir, 0755); err != nil {
|
||||
return trace.Errorf("failed to create versions directory: %w", err)
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
DataDir: ccfg.DataDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
SelfSetup: ccfg.SelfSetup,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
|
||||
// Ensure enable can't run concurrently.
|
||||
unlock, err := libutils.FSWriteLock(filepath.Join(versionsDir, lockFileName))
|
||||
unlock, err := libutils.FSWriteLock(filepath.Join(ccfg.DataDir, lockFileName))
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to grab concurrent execution lock: %w", err)
|
||||
}
|
||||
@@ -218,16 +230,6 @@ func cmdEnable(ctx context.Context, ccfg *cliConfig) error {
|
||||
plog.DebugContext(ctx, "Failed to close lock file", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
VersionsDir: versionsDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
if err := updater.Enable(ctx, ccfg.OverrideConfig); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -236,13 +238,18 @@ func cmdEnable(ctx context.Context, ccfg *cliConfig) error {
|
||||
|
||||
// cmdUpdate updates Teleport to the version specified by cluster reachable at the proxy address.
|
||||
func cmdUpdate(ctx context.Context, ccfg *cliConfig) error {
|
||||
versionsDir := filepath.Join(ccfg.DataDir, versionsDirName)
|
||||
if err := os.MkdirAll(versionsDir, 0755); err != nil {
|
||||
return trace.Errorf("failed to create versions directory: %w", err)
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
DataDir: ccfg.DataDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
SelfSetup: ccfg.SelfSetup,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
|
||||
// Ensure update can't run concurrently.
|
||||
unlock, err := libutils.FSWriteLock(filepath.Join(versionsDir, lockFileName))
|
||||
unlock, err := libutils.FSWriteLock(filepath.Join(ccfg.DataDir, lockFileName))
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to grab concurrent execution lock: %w", err)
|
||||
}
|
||||
@@ -252,15 +259,6 @@ func cmdUpdate(ctx context.Context, ccfg *cliConfig) error {
|
||||
}
|
||||
}()
|
||||
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
VersionsDir: versionsDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
if err := updater.Update(ctx); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -269,10 +267,19 @@ func cmdUpdate(ctx context.Context, ccfg *cliConfig) error {
|
||||
|
||||
// cmdLink creates system package links if no version is linked and auto-updates is disabled.
|
||||
func cmdLink(ctx context.Context, ccfg *cliConfig) error {
|
||||
versionsDir := filepath.Join(ccfg.DataDir, versionsDirName)
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
DataDir: ccfg.DataDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
SelfSetup: ccfg.SelfSetup,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
|
||||
// Skip operation and warn if the updater is currently running.
|
||||
unlock, err := libutils.FSTryReadLock(filepath.Join(versionsDir, lockFileName))
|
||||
unlock, err := libutils.FSTryReadLock(filepath.Join(ccfg.DataDir, lockFileName))
|
||||
if errors.Is(err, libutils.ErrUnsuccessfulLockTry) {
|
||||
plog.WarnContext(ctx, "Updater is currently running. Skipping package linking.")
|
||||
return nil
|
||||
@@ -285,17 +292,22 @@ func cmdLink(ctx context.Context, ccfg *cliConfig) error {
|
||||
plog.DebugContext(ctx, "Failed to close lock file", "error", err)
|
||||
}
|
||||
}()
|
||||
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
|
||||
VersionsDir: versionsDir,
|
||||
LinkDir: ccfg.LinkDir,
|
||||
SystemDir: autoupdate.DefaultSystemDir,
|
||||
Log: plog,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup updater: %w", err)
|
||||
}
|
||||
|
||||
if err := updater.LinkPackage(ctx); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmdSetup writes configuration files that are needed to run teleport-update update.
|
||||
func cmdSetup(ctx context.Context, ccfg *cliConfig) error {
|
||||
err := autoupdate.Setup(ctx, plog, ccfg.LinkDir, ccfg.DataDir)
|
||||
if errors.Is(err, autoupdate.ErrNotSupported) {
|
||||
plog.WarnContext(ctx, "Not enabling systemd service because systemd is not running.")
|
||||
os.Exit(autoupdate.CodeNotSupported)
|
||||
}
|
||||
if err != nil {
|
||||
return trace.Errorf("failed to setup teleport-update service: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user