Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[teleport-update] Add link subcommand #48712

Merged
merged 16 commits into from
Nov 15, 2024
380 changes: 312 additions & 68 deletions lib/autoupdate/agent/installer.go

Large diffs are not rendered by default.

309 changes: 279 additions & 30 deletions lib/autoupdate/agent/installer_test.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v1
kind: update_config
spec:
proxy: ""
group: ""
url_template: http://example.com
enabled: false
status:
active_version: ""
backup_version: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v1
kind: update_config
spec:
proxy: ""
group: ""
url_template: ""
enabled: false
status:
active_version: ""
backup_version: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: ""
kind: ""
spec:
proxy: ""
group: ""
url_template: ""
enabled: false
status:
active_version: ""
backup_version: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v1
kind: update_config
spec:
proxy: localhost
group: ""
url_template: http://example.com
enabled: true
status:
active_version: ""
backup_version: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v1
kind: update_config
spec:
proxy: localhost
group: ""
url_template: ""
enabled: true
status:
active_version: ""
backup_version: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: ""
kind: ""
spec:
proxy: localhost
group: ""
url_template: ""
enabled: false
status:
active_version: ""
backup_version: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v1
kind: update_config
spec:
proxy: localhost
group: ""
url_template: https://example.com
enabled: true
status:
active_version: old-version
backup_version: backup-version
10 changes: 10 additions & 0 deletions lib/autoupdate/agent/testdata/TestUpdater_Update/sync_fails.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: v1
kind: update_config
spec:
proxy: localhost
group: ""
url_template: https://example.com
enabled: true
status:
active_version: old-version
backup_version: backup-version
104 changes: 83 additions & 21 deletions lib/autoupdate/agent/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ import (
libutils "github.com/gravitational/teleport/lib/utils"
)

const (
// DefaultLinkDir is the default location where Teleport is linked.
DefaultLinkDir = "/usr/local"
// DefaultSystemDir is the location where packaged Teleport binaries and services are installed.
DefaultSystemDir = "/usr/local/teleport-system"
)

const (
// cdnURITemplate is the default template for the Teleport tgz download.
cdnURITemplate = "https://cdn.teleport.dev/teleport{{if .Enterprise}}-ent{{end}}-v{{.Version}}-{{.OS}}-{{.Arch}}{{if .FIPS}}-fips{{end}}-bin.tar.gz"
Expand All @@ -50,6 +57,14 @@ const (
reservedFreeDisk = 10_000_000 // 10 MB
)

// Log keys
const (
targetVersionKey = "target_version"
activeVersionKey = "active_version"
backupVersionKey = "backup_version"
errorKey = "error"
)

const (
// updateConfigName specifies the name of the file inside versionsDirName containing configuration for the teleport update.
updateConfigName = "update.yaml"
Expand All @@ -59,14 +74,6 @@ const (
updateConfigKind = "update_config"
)

// Log keys
const (
targetVersionKey = "target_version"
activeVersionKey = "active_version"
backupVersionKey = "backup_version"
errorKey = "error"
)

// UpdateConfig describes the update.yaml file schema.
type UpdateConfig struct {
// Version of the configuration file
Expand Down Expand Up @@ -124,7 +131,10 @@ func NewLocalUpdater(cfg LocalUpdaterConfig) (*Updater, error) {
cfg.Log = slog.Default()
}
if cfg.LinkDir == "" {
cfg.LinkDir = "/usr/local"
cfg.LinkDir = DefaultLinkDir
}
if cfg.SystemDir == "" {
cfg.SystemDir = DefaultSystemDir
}
if cfg.VersionsDir == "" {
cfg.VersionsDir = filepath.Join(libdefaults.DataDir, "versions")
Expand All @@ -135,12 +145,15 @@ func NewLocalUpdater(cfg LocalUpdaterConfig) (*Updater, error) {
InsecureSkipVerify: cfg.InsecureSkipVerify,
ConfigPath: filepath.Join(cfg.VersionsDir, updateConfigName),
Installer: &LocalInstaller{
InstallDir: cfg.VersionsDir,
LinkBinDir: filepath.Join(cfg.LinkDir, "bin"),
LinkServiceDir: filepath.Join(cfg.LinkDir, "lib", "systemd", "system"),
HTTP: client,
Log: cfg.Log,

InstallDir: cfg.VersionsDir,
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.
LinkServiceDir: filepath.Join("/", serviceDir),
SystemBinDir: filepath.Join(cfg.SystemDir, "bin"),
SystemServiceDir: filepath.Join(cfg.SystemDir, serviceDir),
HTTP: client,
Log: cfg.Log,
ReservedFreeTmpDisk: reservedFreeDisk,
ReservedFreeInstallDisk: reservedFreeDisk,
},
Expand All @@ -165,6 +178,8 @@ type LocalUpdaterConfig struct {
VersionsDir string
// LinkDir for installing Teleport (usually /usr/local).
LinkDir string
// SystemDir for package-installed Teleport installations (usually /usr/local/teleport-system).
SystemDir string
}

// Updater implements the agent-local logic for Teleport agent auto-updates.
Expand All @@ -188,11 +203,22 @@ type Installer interface {
// Install the Teleport agent at version from the download template.
// Install must be idempotent.
Install(ctx context.Context, version, template string, flags InstallFlags) error
// Link the Teleport agent at the specified version into the system location.
// Link the Teleport agent at the specified version of Teleport into the linking locations.
// The revert function must restore the previous linking, returning false on any failure.
// Link must be idempotent.
// Link's revert function must be idempotent.
// Link must be idempotent. Link's revert function must be idempotent.
Link(ctx context.Context, version string) (revert func(context.Context) bool, err error)
// LinkSystem links the system installation of Teleport into the linking locations.
// The revert function must restore the previous linking, returning false on any failure.
// LinkSystem must be idempotent. LinkSystem's revert function must be idempotent.
LinkSystem(ctx context.Context) (revert func(context.Context) bool, err error)
// TryLink links the specified version of Teleport into the linking locations.
// Unlike Link, TryLink will fail if existing links to other locations are present.
// TryLink must be idempotent.
TryLink(ctx context.Context, version string) error
// TryLinkSystem links the system installation of Teleport into the linking locations.
// Unlike LinkSystem, TryLinkSystem will fail if existing links to other locations are present.
// TryLinkSystem must be idempotent.
TryLinkSystem(ctx context.Context) error
// List the installed versions of Teleport.
List(ctx context.Context) (versions []string, err error)
// Remove the Teleport agent at version.
Expand Down Expand Up @@ -480,8 +506,9 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s
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 {
u.Log.WarnContext(ctx, "Teleport updater encountered a configuration error and successfully reverted the installation.")
}
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)
}
Expand All @@ -502,8 +529,9 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, targetVersion s
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 a configuration error and successfully reverted the installation.")
}
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)
}
Expand Down Expand Up @@ -558,7 +586,7 @@ func readConfig(path string) (*UpdateConfig, error) {
// writeConfig writes UpdateConfig to a file atomically, ensuring the file cannot be corrupted.
func writeConfig(filename string, cfg *UpdateConfig) error {
opts := []renameio.Option{
renameio.WithPermissions(0755),
renameio.WithPermissions(configFileMode),
renameio.WithExistingPermissions(),
}
t, err := renameio.NewPendingFile(filename, opts...)
Expand Down Expand Up @@ -589,3 +617,37 @@ func validateConfigSpec(spec *UpdateSpec, override OverrideConfig) error {
}
return nil
}

// 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 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 {
cfg, err := readConfig(u.ConfigPath)
if err != nil {
return trace.Errorf("failed to read %s: %w", updateConfigName, err)
}
if err := validateConfigSpec(&cfg.Spec, OverrideConfig{}); err != nil {
return trace.Wrap(err)
}
activeVersion := cfg.Status.ActiveVersion
if cfg.Spec.Enabled {
u.Log.InfoContext(ctx, "Automatic updates enabled. Skipping system package link.", activeVersionKey, activeVersion)
return nil
}
// If an active version is set, but auto-updates is disabled, try to link the system installation in case the config is stale.
// If any links are present, this will return ErrLinked and not create any system links.
// This state is important to log as a warning,
if err := u.Installer.TryLinkSystem(ctx); errors.Is(err, ErrLinked) {
u.Log.WarnContext(ctx, "Automatic updates disabled, but a non-package version of Teleport is linked.", activeVersionKey, activeVersion)
return nil
} else if err != nil {
return trace.Errorf("failed to link system package installation: %w", err)
}
// TODO(sclevine): only if systemd files change
if err := u.Process.Sync(ctx); err != nil {
return trace.Errorf("failed to validate configuration for packaged installation of Teleport: %w", err)
}
u.Log.InfoContext(ctx, "Successfully linked system package installation.")
return nil
}
Loading
Loading