v3.1.0 d676137

store_retire_test.go RawHistoryFolder

1package appstate_test
2
3import (
4	"context"
5	"testing"
6
7	"github.com/stretchr/testify/assert"
8	"github.com/stretchr/testify/require"
9
10	"github.com/bnema/gordon/internal/domain"
11)
12
13// TestStore_RetireAppEndsIncarnation proves removing an app archives its
14// ownership, clears its operational state, and detaches the name so a
15// reapply allocates a new incarnation that cannot inherit the old
16// secrets or volumes.
17func TestStore_RetireAppEndsIncarnation(t *testing.T) {
18	store := newTestStore(t)
19	ctx := context.Background()
20
21	require.NoError(t, store.SaveOwnership(ctx, domain.AppOwnership{
22		App: "blog", ID: "app-old",
23		Volumes: []domain.AppOwnedVolume{{
24			Name: "data", Service: "web",
25			RuntimeName: "gordon-blog--web--vol--data",
26			State:       domain.AppResourceAttached,
27		}},
28		Secrets: []domain.AppOwnedSecret{{
29			Service: "web", Env: "DATABASE_URL", Name: "database-url",
30			Path: "gordon/apps/app-old/blog/web/database-url", State: domain.AppResourceAttached,
31		}},
32		Images: []domain.AppOwnedImage{{
33			Service: "web", Reference: "registry.example.com/blog/web:1.4.2",
34			Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
35			State:  domain.AppResourceAttached,
36		}},
37	}))
38	require.NoError(t, store.SaveActive(ctx, domain.AppActive{
39		App: "blog",
40		Services: map[string]domain.AppEffectiveService{
41			"web": {Container: "c-old", EffectiveRevision: "rev-old"},
42		},
43	}))
44	require.NoError(t, store.SaveIntent(ctx, domain.AppStopIntent{App: "blog", Stopped: true}))
45
46	require.NoError(t, store.RetireApp(ctx, "blog"))
47
48	ownership, err := store.LoadOwnership(ctx, "blog")
49	require.NoError(t, err)
50	assert.Empty(t, ownership.ID, "the name must not retain the old incarnation UUID")
51	assert.Empty(t, ownership.Volumes)
52	assert.Empty(t, ownership.Secrets)
53
54	_, ok, err := store.LoadActive(ctx, "blog")
55	require.NoError(t, err)
56	assert.False(t, ok, "active state must be cleared")
57
58	_, ok, err = store.LoadDesired(ctx, "blog")
59	require.NoError(t, err)
60	assert.False(t, ok, "desired state must be cleared")
61
62	intent, err := store.LoadIntent(ctx, "blog")
63	require.NoError(t, err)
64	assert.False(t, intent.Stopped, "intent must be cleared")
65
66	// Reapply: the first write after retirement allocates a new incarnation.
67	require.NoError(t, store.SaveOwnership(ctx, domain.AppOwnership{App: "blog"}))
68	reused, err := store.LoadOwnership(ctx, "blog")
69	require.NoError(t, err)
70	require.NotEmpty(t, reused.ID)
71	assert.NotEqual(t, "app-old", reused.ID, "a reused name must get a new incarnation")
72
73	// The retired incarnation's resources remain protected (prune reads the
74	// archived record), so they are retained rather than adopted or deleted.
75	snapshot, err := store.ProtectionSnapshot(ctx)
76	require.NoError(t, err)
77	assert.True(t, containsVolumeClaim(snapshot.VolumeClaims, "gordon-blog--web--vol--data", domain.VolumeClaimRetained),
78		"retained volumes must stay protected after retirement")
79	claim, ok := snapshot.ImageClaimFor("registry.example.com/blog/web:1.4.2")
80	if !ok {
81		t.Fatalf("retired image claim missing from %+v", snapshot.ImageClaims)
82	}
83	assert.Equal(t, domain.VolumeClaimReleased, claim.State, "a removed app releases its pinned images")
84}
85
86func TestStore_RetireAppIsAtomicOnEmptyApp(t *testing.T) {
87	store := newTestStore(t)
88	require.NoError(t, store.RetireApp(context.Background(), "never-deployed"))
89
90	ownership, err := store.LoadOwnership(context.Background(), "never-deployed")
91	require.NoError(t, err)
92	assert.Empty(t, ownership.ID)
93}
94
95// TestProtectionSnapshot_OperationDigestRootCarriesRepository proves an
96// unfinished operation's digest root is repository-qualified so prune can
97// walk that manifest's closure.
98func TestProtectionSnapshot_OperationDigestRootCarriesRepository(t *testing.T) {
99	ctx := context.Background()
100	store := newTestStore(t)
101	const manifestDigest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
102
103	require.NoError(t, store.SaveOperation(ctx, domain.AppOperation{
104		Op: "op-open", Kind: "deploy", App: "blog",
105		Steps: []domain.AppOperationStep{{
106			ID: "service.web.replace", State: domain.AppStepPending,
107			Digest: manifestDigest,
108			Image:  "registry.example.com/blog/web:1.4.2",
109		}},
110	}))
111
112	snapshot, err := store.ProtectionSnapshot(ctx)
113	require.NoError(t, err)
114
115	var digestRoot *domain.ProtectionRoot
116	for i := range snapshot.Roots {
117		if snapshot.Roots[i].Ref == manifestDigest {
118			digestRoot = &snapshot.Roots[i]
119		}
120	}
121	if digestRoot == nil {
122		t.Fatalf("operation digest root missing from %+v", snapshot.Roots)
123	}
124	assert.Equal(t, "blog/web", digestRoot.Repository)
125}
126
127func containsVolumeClaim(claims []domain.VolumeClaim, name string, state domain.VolumeClaimState) bool {
128	for _, claim := range claims {
129		if claim.Name == name && claim.State == state {
130			return true
131		}
132	}
133	return false
134}

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