main bac21c6

removals.go RawHistoryFolder

1package deployment
2
3import (
4	"context"
5	"fmt"
6	"sort"
7
8	"github.com/bnema/zerowrap"
9
10	"github.com/bnema/gordon/internal/domain"
11)
12
13// reconcileRemovalsForDeploy journals and applies the removal of services
14// the new revision no longer declares. A failure marks the operation
15// failed and aborts before any new state is published.
16func (s *Service) reconcileRemovalsForDeploy(ctx context.Context, app string, active domain.AppActive, pinned []pinnedService, op *domain.AppOperation, result *DeployResult, log zerowrap.Logger) error {
17	removalSteps, removed, warnings, err := s.reconcileRemovedServices(ctx, app, active, pinned)
18	op.Warnings = append(op.Warnings, journalWarnings(warnings)...)
19	result.CleanupWarnings = append(result.CleanupWarnings, warnings...)
20	if err != nil {
21		op.Steps = append(op.Steps, removalSteps...)
22		op.Outcome = domain.AppOutcomeFailed
23		if saveErr := s.deps.State.SaveOperation(ctx, *op); saveErr != nil {
24			log.Warn().Err(saveErr).Msg("deployment: failed to record removal failure")
25		}
26		return err
27	}
28	if len(removalSteps) == 0 {
29		return nil
30	}
31	op.Steps = append(op.Steps, removalSteps...)
32	result.Removed = removed
33	if saveErr := s.deps.State.SaveOperation(ctx, *op); saveErr != nil {
34		log.Warn().Err(saveErr).Msg("deployment: failed to record service removals")
35	}
36	return nil
37}
38
39// reconcileRemovedServices withdraws and retires every ACTIVE service the
40// new revision no longer declares. Traffic is withdrawn first (fail
41// closed), then the exact container is inhibited, stopped, and removed
42// with its data retained, its backend claims released, and its ACTIVE
43// entry deleted and persisted before its recovery inhibition is cleared. A failure leaves the remaining services untouched and
44// stops the deploy before any new state is published.
45func (s *Service) reconcileRemovedServices(ctx context.Context, app string, active domain.AppActive, pinned []pinnedService) ([]domain.AppOperationStep, []string, []CleanupWarning, error) {
46	desired := make(map[string]struct{}, len(pinned))
47	for _, p := range pinned {
48		desired[p.name] = struct{}{}
49	}
50	var names []string
51	for name := range active.Services {
52		if _, ok := desired[name]; !ok {
53			names = append(names, name)
54		}
55	}
56	sort.Strings(names)
57
58	steps := make([]domain.AppOperationStep, 0, len(names))
59	var cleanupWarnings []CleanupWarning
60	for _, name := range names {
61		eff := active.Services[name]
62		step, warnings, err := s.retireRemovedService(ctx, app, name, eff)
63		steps = append(steps, step)
64		cleanupWarnings = append(cleanupWarnings, warnings...)
65		if err != nil {
66			return steps, names, cleanupWarnings, err
67		}
68		// Persist the removal before dropping the recovery inhibition: an
69		// ACTIVE record that still lists the service without its
70		// inhibition would let recovery recreate a removed service.
71		delete(active.Services, name)
72		if err := s.deps.State.SaveActive(ctx, active); err != nil {
73			return steps, names, cleanupWarnings, fmt.Errorf("deployment: persist service removal %q: %w", name, err)
74		}
75		if err := s.clearRecoveryInhibition(ctx, app, name, eff.Container); err != nil {
76			cleanupWarnings = append(cleanupWarnings, CleanupWarning{
77				Service: name, Leftover: eff.Container, Detail: "clear recovery inhibition: " + err.Error(),
78			})
79		}
80	}
81	if len(names) == 0 {
82		return nil, nil, nil, nil
83	}
84	return steps, names, cleanupWarnings, nil
85}
86
87// retireRemovedService withdraws one removed service and removes its exact
88// container, releasing its claims only after withdrawal succeeded.
89func (s *Service) retireRemovedService(ctx context.Context, app, name string, eff domain.AppEffectiveService) (domain.AppOperationStep, []CleanupWarning, error) {
90	step := domain.AppOperationStep{
91		ID:      "service." + name + ".remove",
92		State:   domain.AppStepPending,
93		Service: name,
94		Before:  eff.Container,
95	}
96	fail := func(err error) (domain.AppOperationStep, []CleanupWarning, error) {
97		step.State = domain.AppStepFailed
98		step.Error = err.Error()
99		return step, nil, err
100	}
101	if err := s.withdrawForRecovery(ctx, app, name); err != nil {
102		return fail(err)
103	}
104	if eff.Container != "" {
105		if err := s.inhibitRecovery(ctx, app, name, eff.Container, "removed", ""); err != nil {
106			return fail(err)
107		}
108		// The inhibition stays until the caller persisted the removal.
109		retired := s.retireContainer(ctx, app, retireOptions{
110			Service: name, Grace: serviceStopGrace(eff),
111		}, eff.Container)
112		if !retired.Gone {
113			return fail(fmt.Errorf("deployment: remove %s/%s container %s: %s", app, name, eff.Container, cleanupDetail(retired)))
114		}
115		step.State = domain.AppStepSucceeded
116		step.After = ""
117		return step, retired.Warnings, nil
118	}
119	step.State = domain.AppStepSucceeded
120	step.After = ""
121	return step, nil, nil
122}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close