v2.31.0-dev16 8d0a092

migrate_env_test.go RawHistoryFolder

1package app
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"os/exec"
8	"path/filepath"
9	"testing"
10	"time"
11
12	"github.com/bnema/zerowrap"
13	"github.com/stretchr/testify/assert"
14	"github.com/stretchr/testify/require"
15
16	"github.com/bnema/gordon/internal/adapters/out/domainsecrets"
17	"github.com/bnema/gordon/internal/domain"
18)
19
20func passCmd(args ...string) error {
21	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
22	defer cancel()
23	return exec.CommandContext(ctx, "pass", args...).Run()
24}
25
26func requirePass(t *testing.T) {
27	if err := passCmd("version"); err != nil {
28		t.Skip("pass not available")
29	}
30	if err := passCmd("ls"); err != nil {
31		t.Skip("pass store not initialized")
32	}
33}
34
35func cleanupPassDomain(domainName string, keys []string) {
36	safeDomain, err := domain.SanitizeDomainForEnvFile(domainName)
37	if err != nil {
38		return
39	}
40
41	for _, key := range keys {
42		path := fmt.Sprintf("%s/%s/%s", domainsecrets.PassDomainSecretsPath, safeDomain, key)
43		_ = passCmd("rm", "-f", path)
44	}
45
46	manifestPath := fmt.Sprintf("%s/%s/.keys", domainsecrets.PassDomainSecretsPath, safeDomain)
47	_ = passCmd("rm", "-f", manifestPath)
48}
49
50func TestMigrateEnvFile(t *testing.T) {
51	requirePass(t)
52
53	tmpDir, err := os.MkdirTemp("", "migrate-env-test-*")
54	require.NoError(t, err)
55	defer os.RemoveAll(tmpDir)
56
57	logger := zerowrap.Default()
58	store, err := domainsecrets.NewPassStore(logger)
59	require.NoError(t, err)
60
61	domainName := fmt.Sprintf("test.%d.example.com", time.Now().UnixNano())
62	keys := []string{"API_KEY", "DB_PASSWORD"}
63	defer cleanupPassDomain(domainName, keys)
64
65	envContent := "API_KEY=secret123\nDB_PASSWORD=pass456\n"
66	envFileName := domainName + ".env"
67	envFilePath := filepath.Join(tmpDir, envFileName)
68	err = os.WriteFile(envFilePath, []byte(envContent), 0600)
69	require.NoError(t, err)
70
71	err = migrateEnvFile(tmpDir, envFileName, store, logger)
72	require.NoError(t, err)
73
74	_, err = os.Stat(envFilePath)
75	assert.True(t, os.IsNotExist(err), "original .env file should be removed")
76
77	_, err = os.Stat(envFilePath + ".migrated")
78	assert.True(t, os.IsNotExist(err), ".env.migrated plaintext should not be preserved by default")
79
80	passKeys, err := store.ListKeys(domainName)
81	require.NoError(t, err)
82	assert.ElementsMatch(t, keys, passKeys)
83
84	values, err := store.GetAll(domainName)
85	require.NoError(t, err)
86	assert.Equal(t, "secret123", values["API_KEY"])
87	assert.Equal(t, "pass456", values["DB_PASSWORD"])
88}
89
90func cleanupPassAttachment(_ *testing.T, containerName string, keys []string) {
91	for _, key := range keys {
92		path := fmt.Sprintf("%s/%s/%s", domainsecrets.PassAttachmentPath, containerName, key)
93		_ = passCmd("rm", "-f", path)
94	}
95
96	manifestPath := fmt.Sprintf("%s/%s/.keys", domainsecrets.PassAttachmentPath, containerName)
97	_ = passCmd("rm", "-f", manifestPath)
98}
99
100func TestMigrateAttachmentEnvFile(t *testing.T) {
101	requirePass(t)
102
103	tmpDir, err := os.MkdirTemp("", "migrate-attachment-env-test-*")
104	require.NoError(t, err)
105	defer os.RemoveAll(tmpDir)
106
107	logger := zerowrap.Default()
108	store, err := domainsecrets.NewPassStore(logger)
109	require.NoError(t, err)
110
111	containerName := fmt.Sprintf("gitea-postgres-%d", time.Now().UnixNano())
112	envFileName := "gordon-" + containerName + ".env"
113	// extractContainerNameFromAttachmentFile strips the .env suffix, so the
114	// stored name includes the "gordon-" prefix from the filename.
115	storedContainerName := extractContainerNameFromAttachmentFile(envFileName)
116	keys := []string{"POSTGRES_USER", "POSTGRES_PASSWORD"}
117	defer cleanupPassAttachment(t, storedContainerName, keys)
118
119	envContent := "POSTGRES_USER=gitea\nPOSTGRES_PASSWORD=secret123\n"
120	envFilePath := filepath.Join(tmpDir, envFileName)
121	err = os.WriteFile(envFilePath, []byte(envContent), 0600)
122	require.NoError(t, err)
123
124	err = migrateAttachmentEnvFile(tmpDir, envFileName, store, logger)
125	require.NoError(t, err)
126
127	_, err = os.Stat(envFilePath)
128	assert.True(t, os.IsNotExist(err), "original .env file should be removed")
129
130	_, err = os.Stat(envFilePath + ".migrated")
131	assert.True(t, os.IsNotExist(err), ".env.migrated plaintext should not be preserved by default")
132
133	values, err := store.GetAllAttachment(storedContainerName)
134	require.NoError(t, err)
135	assert.Equal(t, "gitea", values["POSTGRES_USER"])
136	assert.Equal(t, "secret123", values["POSTGRES_PASSWORD"])
137}
138
139func TestIsAttachmentEnvFile(t *testing.T) {
140	tests := []struct {
141		name     string
142		filename string
143		want     bool
144	}{
145		{
146			name:     "attachment env file",
147			filename: "gordon-gitea-postgres.env",
148			want:     true,
149		},
150		{
151			name:     "attachment env file with complex name",
152			filename: "gordon-git-example-com-gitea-postgres.env",
153			want:     true,
154		},
155		{
156			name:     "plain domain env file",
157			filename: "example.com.env",
158			want:     false,
159		},
160		{
161			name:     "migrated attachment file",
162			filename: "gordon-gitea-postgres.env.migrated",
163			want:     false,
164		},
165		{
166			name:     "non-env file",
167			filename: "config.yaml",
168			want:     false,
169		},
170		{
171			name:     "gordon prefix but no env suffix",
172			filename: "gordon-something",
173			want:     false,
174		},
175	}
176
177	for _, tt := range tests {
178		t.Run(tt.name, func(t *testing.T) {
179			got := isAttachmentEnvFile(tt.filename)
180			assert.Equal(t, tt.want, got)
181		})
182	}
183}
184
185func TestExtractContainerNameFromAttachmentFile(t *testing.T) {
186	tests := []struct {
187		name     string
188		filename string
189		want     string
190	}{
191		{
192			name:     "simple attachment file",
193			filename: "gordon-gitea-postgres.env",
194			want:     "gordon-gitea-postgres",
195		},
196		{
197			name:     "complex attachment file",
198			filename: "gordon-git-example-com-gitea-redis.env",
199			want:     "gordon-git-example-com-gitea-redis",
200		},
201		{
202			name:     "single word container",
203			filename: "gordon-redis.env",
204			want:     "gordon-redis",
205		},
206	}
207
208	for _, tt := range tests {
209		t.Run(tt.name, func(t *testing.T) {
210			got := extractContainerNameFromAttachmentFile(tt.filename)
211			assert.Equal(t, tt.want, got)
212		})
213	}
214}

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