Skip to content

Commit 50dad3e

Browse files
authored
[teleport-update] Improve clarity of error logs and address UX edge cases (#52777)
* Usability fixes * cancel jitter * root + fix logs * check extra case * cleanup * extra warning * tests * feedback * add newlines * adjust message * consistent error type
1 parent 722d917 commit 50dad3e

File tree

3 files changed

+117
-25
lines changed

3 files changed

+117
-25
lines changed

lib/autoupdate/agent/updater.go

+56-24
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,14 @@ func NewLocalUpdater(cfg LocalUpdaterConfig, ns *Namespace) (*Updater, error) {
115115
validator := Validator{Log: cfg.Log}
116116
debugClient := debug.NewClient(filepath.Join(ns.dataDir, debugSocketFileName))
117117
return &Updater{
118-
Log: cfg.Log,
119-
Pool: certPool,
120-
InsecureSkipVerify: cfg.InsecureSkipVerify,
121-
UpdateConfigFile: filepath.Join(ns.Dir(), updateConfigName),
122-
TeleportConfigFile: ns.configFile,
123-
DefaultProxyAddr: ns.defaultProxyAddr,
124-
DefaultPathDir: ns.defaultPathDir,
118+
Log: cfg.Log,
119+
Pool: certPool,
120+
InsecureSkipVerify: cfg.InsecureSkipVerify,
121+
UpdateConfigFile: filepath.Join(ns.Dir(), updateConfigName),
122+
TeleportConfigFile: ns.configFile,
123+
TeleportServiceName: filepath.Base(ns.serviceFile),
124+
DefaultProxyAddr: ns.defaultProxyAddr,
125+
DefaultPathDir: ns.defaultPathDir,
125126
Installer: &LocalInstaller{
126127
InstallDir: filepath.Join(ns.Dir(), versionsDirName),
127128
TargetServiceFile: ns.serviceFile,
@@ -204,6 +205,8 @@ type Updater struct {
204205
UpdateConfigFile string
205206
// TeleportConfigFile contains the path to Teleport's configuration.
206207
TeleportConfigFile string
208+
// TeleportServiceName contains the full name of the systemd service for Teleport
209+
TeleportServiceName string
207210
// DefaultProxyAddr contains Teleport's proxy address. This may differ from the updater's.
208211
DefaultProxyAddr string
209212
// DefaultPathDir contains the default path that Teleport binaries should be installed into.
@@ -274,6 +277,8 @@ var (
274277
ErrNoBinaries = errors.New("no binaries available to link")
275278
// ErrFilePresent is returned when a file is present.
276279
ErrFilePresent = errors.New("file present")
280+
// ErrNotInstalled is returned when Teleport is not installed.
281+
ErrNotInstalled = errors.New("not installed")
277282
)
278283

279284
// Process provides an API for interacting with a running Teleport process.
@@ -446,19 +451,40 @@ func (u *Updater) Remove(ctx context.Context, force bool) error {
446451
}
447452

448453
// Do not link system package installation if the installation we are removing
449-
// is not installed into /usr/local/bin.
454+
// is not installed into /usr/local/bin. In this case, we also need to make sure
455+
// it is clear we are not going to recover the package's systemd service if it
456+
// was overwritten.
450457
if filepath.Clean(cfg.Spec.Path) != filepath.Clean(defaultPathDir) {
451-
return u.removeWithoutSystem(ctx, cfg, force)
458+
if u.TeleportServiceName == serviceName {
459+
if !force {
460+
u.Log.ErrorContext(ctx, "Default Teleport systemd service would be removed, and --force was not passed.")
461+
u.Log.ErrorContext(ctx, "Refusing to remove Teleport from this system.")
462+
return trace.Errorf("unable to remove Teleport completely without --force")
463+
} else {
464+
u.Log.WarnContext(ctx, "Default Teleport systemd service will be removed since --force was passed.")
465+
u.Log.WarnContext(ctx, "Teleport will be removed from this system.")
466+
}
467+
}
468+
return u.removeWithoutSystem(ctx, cfg)
452469
}
453470
revert, err := u.Installer.LinkSystem(ctx)
454471
if errors.Is(err, ErrNoBinaries) {
455-
return u.removeWithoutSystem(ctx, cfg, force)
472+
if !force {
473+
u.Log.ErrorContext(ctx, "No packaged installation of Teleport was found, and --force was not passed.")
474+
u.Log.ErrorContext(ctx, "Refusing to remove Teleport from this system.")
475+
return trace.Errorf("unable to remove Teleport completely without --force")
476+
} else {
477+
u.Log.WarnContext(ctx, "No packaged installation of Teleport was found, and --force was passed.")
478+
u.Log.WarnContext(ctx, "Teleport will be removed from this system.")
479+
}
480+
return u.removeWithoutSystem(ctx, cfg)
456481
}
457482
if err != nil {
458483
return trace.Wrap(err, "failed to link")
459484
}
460485

461-
u.Log.InfoContext(ctx, "Updater-managed installation of Teleport detected. Restoring packaged version of Teleport before removing.")
486+
u.Log.InfoContext(ctx, "Updater-managed installation of Teleport detected.")
487+
u.Log.InfoContext(ctx, "Restoring packaged version of Teleport before removing.")
462488

463489
revertConfig := func(ctx context.Context) bool {
464490
if ok := revert(ctx); !ok {
@@ -504,7 +530,8 @@ func (u *Updater) Remove(ctx context.Context, force bool) error {
504530
u.Log.ErrorContext(ctx, "Reverting symlinks due to failed restart.")
505531
if ok := revertConfig(ctx); ok {
506532
if err := u.Process.Reload(ctx); err != nil && !errors.Is(err, ErrNotNeeded) {
507-
u.Log.ErrorContext(ctx, "Failed to reload Teleport after reverting. Installation likely broken.", errorKey, err)
533+
u.Log.ErrorContext(ctx, "Failed to reload Teleport after reverting.", errorKey, err)
534+
u.Log.ErrorContext(ctx, "Installation likely broken.")
508535
} else {
509536
u.Log.WarnContext(ctx, "Teleport updater detected an error with the new installation and successfully reverted it.")
510537
}
@@ -519,14 +546,9 @@ func (u *Updater) Remove(ctx context.Context, force bool) error {
519546
return nil
520547
}
521548

522-
func (u *Updater) removeWithoutSystem(ctx context.Context, cfg *UpdateConfig, force bool) error {
523-
if !force {
524-
u.Log.ErrorContext(ctx, "No packaged installation of Teleport was found, and --force was not passed. Refusing to remove Teleport from this system.")
525-
return trace.Errorf("unable to remove Teleport completely without --force")
526-
} else {
527-
u.Log.WarnContext(ctx, "No packaged installation of Teleport was found, and --force was passed. Teleport will be removed from this system.")
528-
}
529-
u.Log.InfoContext(ctx, "Updater-managed installation of Teleport detected. Attempting to unlink and remove.")
549+
func (u *Updater) removeWithoutSystem(ctx context.Context, cfg *UpdateConfig) error {
550+
u.Log.InfoContext(ctx, "Updater-managed installation of Teleport detected.")
551+
u.Log.InfoContext(ctx, "Attempting to unlink and remove.")
530552
ok, err := u.Process.IsActive(ctx)
531553
if err != nil && !errors.Is(err, ErrNotSupported) {
532554
return trace.Wrap(err)
@@ -558,6 +580,9 @@ func (u *Updater) Status(ctx context.Context) (Status, error) {
558580
if err := validateConfigSpec(&cfg.Spec, OverrideConfig{}); err != nil {
559581
return out, trace.Wrap(err)
560582
}
583+
if cfg.Spec.Proxy == "" {
584+
return out, ErrNotInstalled
585+
}
561586
out.UpdateSpec = cfg.Spec
562587
out.UpdateStatus = cfg.Status
563588

@@ -689,7 +714,11 @@ func (u *Updater) Update(ctx context.Context, now bool) error {
689714
u.Log.InfoContext(ctx, "Update available. Initiating update.", targetKey, target, activeKey, active)
690715
}
691716
if !now {
692-
time.Sleep(resp.Jitter)
717+
select {
718+
case <-time.After(resp.Jitter):
719+
case <-ctx.Done():
720+
return trace.Wrap(ctx.Err())
721+
}
693722
}
694723

695724
updateErr := u.update(ctx, cfg, target, false, resp.AGPL)
@@ -941,15 +970,18 @@ func (u *Updater) notices(ctx context.Context) error {
941970
}
942971
if !enabled && active {
943972
u.Log.WarnContext(ctx, "Teleport is installed and started, but not configured to start on boot.")
944-
u.Log.WarnContext(ctx, "After configuring teleport.yaml, you can enable it with: systemctl enable teleport")
973+
u.Log.WarnContext(ctx, "After configuring teleport.yaml, you must enable it.",
974+
"command", "systemctl enable "+u.TeleportServiceName)
945975
}
946976
if !active && enabled {
947977
u.Log.WarnContext(ctx, "Teleport is installed and enabled at boot, but not running.")
948-
u.Log.WarnContext(ctx, "After configuring teleport.yaml, you can start it with: systemctl start teleport")
978+
u.Log.WarnContext(ctx, "After configuring teleport.yaml, you must start it.",
979+
"command", "systemctl start "+u.TeleportServiceName)
949980
}
950981
if !active && !enabled {
951982
u.Log.WarnContext(ctx, "Teleport is installed, but not running or enabled at boot.")
952-
u.Log.WarnContext(ctx, "After configuring teleport.yaml, you can enable and start it with: systemctl enable teleport --now")
983+
u.Log.WarnContext(ctx, "After configuring teleport.yaml, you must enable and start.",
984+
"command", "systemctl enable --now "+u.TeleportServiceName)
953985
}
954986

955987
return nil

lib/autoupdate/agent/updater_test.go

+52
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,7 @@ func TestUpdater_Remove(t *testing.T) {
10581058
reloadErr error
10591059
processActive bool
10601060
force bool
1061+
serviceName string
10611062

10621063
unlinkedVersion string
10631064
teardownCalls int
@@ -1163,6 +1164,53 @@ func TestUpdater_Remove(t *testing.T) {
11631164
teardownCalls: 1,
11641165
force: true,
11651166
},
1167+
{
1168+
name: "no system links, process disabled, custom path, force",
1169+
cfg: &UpdateConfig{
1170+
Version: updateConfigVersion,
1171+
Kind: updateConfigKind,
1172+
Spec: UpdateSpec{
1173+
Path: "custom",
1174+
},
1175+
Status: UpdateStatus{
1176+
Active: NewRevision(version, 0),
1177+
},
1178+
},
1179+
unlinkedVersion: version,
1180+
teardownCalls: 1,
1181+
force: true,
1182+
},
1183+
{
1184+
name: "no system links, process disabled, custom path, no force",
1185+
cfg: &UpdateConfig{
1186+
Version: updateConfigVersion,
1187+
Kind: updateConfigKind,
1188+
Spec: UpdateSpec{
1189+
Path: "custom",
1190+
},
1191+
Status: UpdateStatus{
1192+
Active: NewRevision(version, 0),
1193+
},
1194+
},
1195+
errMatch: "unable to remove",
1196+
},
1197+
{
1198+
name: "no system links, process disabled, custom path, no force, custom service",
1199+
cfg: &UpdateConfig{
1200+
Version: updateConfigVersion,
1201+
Kind: updateConfigKind,
1202+
Spec: UpdateSpec{
1203+
Path: "custom",
1204+
},
1205+
Status: UpdateStatus{
1206+
Active: NewRevision(version, 0),
1207+
},
1208+
},
1209+
serviceName: "custom",
1210+
unlinkedVersion: version,
1211+
teardownCalls: 1,
1212+
force: true,
1213+
},
11661214
{
11671215
name: "active version",
11681216
cfg: &UpdateConfig{
@@ -1268,6 +1316,10 @@ func TestUpdater_Remove(t *testing.T) {
12681316
InsecureSkipVerify: true,
12691317
}, ns)
12701318
require.NoError(t, err)
1319+
updater.TeleportServiceName = serviceName
1320+
if tt.serviceName != "" {
1321+
updater.TeleportServiceName = tt.serviceName
1322+
}
12711323

12721324
// Create config file only if provided in test case
12731325
if tt.cfg != nil {

tool/teleport-update/main.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,14 @@ func Run(args []string) int {
184184
return 1
185185
}
186186

187-
// Set required umask for commands that write files to system directories as root, and warn loudly if it changes.
188187
switch command {
189188
case statusCmd.FullCommand(), versionCmd.FullCommand():
190189
default:
190+
if os.Geteuid() != 0 {
191+
plog.ErrorContext(ctx, "This command must be run as root. Try running with sudo.")
192+
return 1
193+
}
194+
// Set required umask for commands that write files to system directories as root, and warn loudly if it changes.
191195
autoupdate.SetRequiredUmask(ctx, plog)
192196
}
193197

@@ -460,6 +464,10 @@ func cmdStatus(ctx context.Context, ccfg *cliConfig) error {
460464
if err != nil {
461465
return trace.Wrap(err, "failed to get status")
462466
}
467+
if errors.Is(err, autoupdate.ErrNotInstalled) {
468+
plog.InfoContext(ctx, "Teleport is not installed by teleport-update with this suffix.")
469+
return nil
470+
}
463471
enc := yaml.NewEncoder(os.Stdout)
464472
return trace.Wrap(enc.Encode(status))
465473
}

0 commit comments

Comments
 (0)