diff --git a/lib/autoupdate/agent/updater.go b/lib/autoupdate/agent/updater.go index 2edd05f52cb45..a14e2abb1d709 100644 --- a/lib/autoupdate/agent/updater.go +++ b/lib/autoupdate/agent/updater.go @@ -283,6 +283,10 @@ type Process interface { // If validated, these overrides may be persisted to disk. type OverrideConfig struct { UpdateSpec + + // The fields below override the behavior of + // Updater.Install for a single run. + // ForceVersion to the specified version. ForceVersion string // ForceFlags in installed Teleport. @@ -444,7 +448,7 @@ func sameProxies(a, b string) bool { // Remove removes everything created by the updater for the given namespace. // Before attempting this, Remove attempts to gracefully recover the system-packaged version of Teleport (if present). // This function is idempotent. -func (u *Updater) Remove(ctx context.Context) error { +func (u *Updater) Remove(ctx context.Context, force bool) error { cfg, err := readConfig(u.UpdateConfigPath) if err != nil { return trace.Wrap(err, "failed to read %s", updateConfigName) @@ -465,6 +469,12 @@ func (u *Updater) Remove(ctx context.Context) error { revert, err := u.Installer.LinkSystem(ctx) if errors.Is(err, ErrNoBinaries) || errors.Is(err, ErrInvalid) { + if !force { + u.Log.ErrorContext(ctx, "No packaged installation of Teleport was found, and --force was not passed. Refusing to remove Teleport from this system.") + return trace.Errorf("unable to remove Teleport completely without --force") + } else { + u.Log.WarnContext(ctx, "No packaged installation of Teleport was found, and --force was passed. Teleport will be removed from this system.") + } u.Log.InfoContext(ctx, "Updater-managed installation of Teleport detected. Attempting to unlink and remove.") ok, err := isActiveOrEnabled(ctx, u.Process) if err != nil && !errors.Is(err, ErrNotSupported) { diff --git a/lib/autoupdate/agent/updater_test.go b/lib/autoupdate/agent/updater_test.go index 96a68a610e0db..80d9e3b442c45 100644 --- a/lib/autoupdate/agent/updater_test.go +++ b/lib/autoupdate/agent/updater_test.go @@ -917,6 +917,7 @@ func TestUpdater_Remove(t *testing.T) { syncErr error reloadErr error processEnabled bool + force bool unlinkedVersion string teardownCalls int @@ -939,7 +940,7 @@ func TestUpdater_Remove(t *testing.T) { teardownCalls: 1, }, { - name: "no system links, process enabled", + name: "no system links, process enabled, force", cfg: &UpdateConfig{ Version: updateConfigVersion, Kind: updateConfigKind, @@ -950,10 +951,11 @@ func TestUpdater_Remove(t *testing.T) { linkSystemErr: ErrNoBinaries, linkSystemCalls: 1, processEnabled: true, + force: true, errMatch: "refusing to remove", }, { - name: "no system links, process disabled", + name: "no system links, process disabled, force", cfg: &UpdateConfig{ Version: updateConfigVersion, Kind: updateConfigKind, @@ -965,9 +967,23 @@ func TestUpdater_Remove(t *testing.T) { linkSystemCalls: 1, unlinkedVersion: version, teardownCalls: 1, + force: true, }, { - name: "no system links, process disabled, no systemd", + name: "no system links, process disabled, no force", + cfg: &UpdateConfig{ + Version: updateConfigVersion, + Kind: updateConfigKind, + Status: UpdateStatus{ + Active: NewRevision(version, 0), + }, + }, + linkSystemErr: ErrNoBinaries, + linkSystemCalls: 1, + errMatch: "unable to remove", + }, + { + name: "no system links, process disabled, no systemd, force", cfg: &UpdateConfig{ Version: updateConfigVersion, Kind: updateConfigKind, @@ -980,6 +996,7 @@ func TestUpdater_Remove(t *testing.T) { isEnabledErr: ErrNotSupported, unlinkedVersion: version, teardownCalls: 1, + force: true, }, { name: "active version", @@ -1123,7 +1140,7 @@ func TestUpdater_Remove(t *testing.T) { } ctx := context.Background() - err = updater.Remove(ctx) + err = updater.Remove(ctx, tt.force) if tt.errMatch != "" { require.Error(t, err) assert.Contains(t, err.Error(), tt.errMatch) diff --git a/tool/teleport-update/main.go b/tool/teleport-update/main.go index 514c7a6cab7af..181816816bc7c 100644 --- a/tool/teleport-update/main.go +++ b/tool/teleport-update/main.go @@ -83,6 +83,8 @@ type cliConfig struct { UpdateNow bool // Reload reloads Teleport. Reload bool + // ForceUninstall allows Teleport to be completely removed. + ForceUninstall bool } func Run(args []string) int { @@ -157,6 +159,8 @@ func Run(args []string) int { statusCmd := app.Command("status", "Show Teleport agent auto-update status.") uninstallCmd := app.Command("uninstall", "Uninstall the updater-managed installation of Teleport. If the Teleport package is installed, it is restored as the primary installation.") + uninstallCmd.Flag("force", "Force complete uninstallation of Teleport, even if there is no packaged version of Teleport to revert to."). + Short('f').BoolVar(&ccfg.ForceUninstall) libutils.UpdateAppUsageTemplate(app, args) command, err := app.Parse(args) @@ -470,7 +474,7 @@ func cmdUninstall(ctx context.Context, ccfg *cliConfig) error { } }() - if err := updater.Remove(ctx); err != nil { + if err := updater.Remove(ctx, ccfg.ForceUninstall); err != nil { return trace.Wrap(err) } return nil