Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

396 commit(s) on main

Turn off the disk reserve for test-spawned runners Keenan Nemetz · 2h ago c284cb2c
history blame raw 5.6 KiB
1package repo
2
3// The upgrade path: an instance that stored access rules in refs/vole/acl keeps them
4// when the rules move into the database. That has to be measured, because the failure
5// mode is silent — a repository with no rules denies everybody, admins included.
6
7import (
8 "os"
9 "path/filepath"
10 "testing"
11 "time"
12
13 "rodent.software/vole/core/git"
14 "rodent.software/vole/forge/access"
15)
16
17// withACLs returns a store whose rules live in a database beside the repositories, the
18// way the instance has them.
19func withACLs(t *testing.T) (*Store, *access.Store) {
20 t.Helper()
21 root := t.TempDir()
22 acls, err := access.Open(filepath.Join(root, "users.db"))
23 if err != nil {
24 t.Fatal(err)
25 }
26 acls.AuditPath = filepath.Join(root, "audit.log")
27 t.Cleanup(func() { acls.Close() })
28 return New(filepath.Join(root, "repos"), "", acls), acls
29}
30
31// legacyWriteACL does what the old code did: a commit on refs/vole/acl holding
32// access.json. It is how a pre-upgrade repository looks.
33func legacyWriteACL(t *testing.T, m *Meta, a *access.ACL) {
34 t.Helper()
35 g := m.Git()
36 raw, err := a.Bytes()
37 if err != nil {
38 t.Fatal(err)
39 }
40 blob, err := g.HashObject(raw)
41 if err != nil {
42 t.Fatal(err)
43 }
44 tree, err := g.MkTree([]git.Entry{{Mode: "100644", Type: "blob", SHA: blob, Name: ACLPath}})
45 if err != nil {
46 t.Fatal(err)
47 }
48 commit, err := g.CommitTree(tree, "", "acl: update by keenan")
49 if err != nil {
50 t.Fatal(err)
51 }
52 if err := g.UpdateRef(ACLRef, commit); err != nil {
53 t.Fatal(err)
54 }
55}
56
57func TestImportACLsTakesRulesFromTheOldRef(t *testing.T) {
58 t.Parallel()
59 st, acls := withACLs(t)
60 m, err := st.Create("demo", "01M5OWNER0000000000000000A", "")
61 if err != nil {
62 t.Fatal(err)
63 }
64 // Drop the row the creation wrote, so the repository looks pre-upgrade, and put the
65 // rules where they used to live.
66 if err := acls.Delete(m.ID); err != nil {
67 t.Fatal(err)
68 }
69 legacy := access.Empty("01M5OWNER0000000000000000A")
70 if err := legacy.SetEntry("01M5WRITER000000000000000B", "write"); err != nil {
71 t.Fatal(err)
72 }
73 legacy.SetRunners([]string{"01M5RUNNER000000000000000C"})
74 legacyWriteACL(t, m, legacy)
75
76 imported, err := st.ImportACLs()
77 if err != nil {
78 t.Fatal(err)
79 }
80 if imported != 1 {
81 t.Fatalf("imported %d repo(s), want 1", imported)
82 }
83 // The repository the caller is already holding has to see the rules too: the gate
84 // reads them from the same store.
85 got, err := m.ACL()
86 if err != nil {
87 t.Fatal(err)
88 }
89 if got.RoleFor("01M5WRITER000000000000000B") != access.Maintain || !got.CanReport("01M5RUNNER000000000000000C") {
90 t.Fatalf("rules did not survive the import: %+v", got)
91 }
92
93 // Idempotent: a second start takes nothing, and a database row is never overwritten
94 // by a stale ref.
95 again, err := st.ImportACLs()
96 if err != nil || again != 0 {
97 t.Fatalf("second import = %d, %v", again, err)
98 }
99}
100
101func TestImportACLsLeavesRepositoriesWithoutTheRefAlone(t *testing.T) {
102 t.Parallel()
103 st, acls := withACLs(t)
104 m, err := st.Create("demo", "01M5OWNER0000000000000000A", "")
105 if err != nil {
106 t.Fatal(err)
107 }
108 if err := acls.Delete(m.ID); err != nil {
109 t.Fatal(err)
110 }
111 if n, err := st.ImportACLs(); err != nil || n != 0 {
112 t.Fatalf("imported %d repo(s) from nothing: %v", n, err)
113 }
114 // No rules is deny-everything, which is what a repository with no ACL ref always
115 // meant — and it is why the import has to run before anybody pushes.
116 got, err := m.ACL()
117 if err != nil {
118 t.Fatal(err)
119 }
120 if got.RoleFor("01M5OWNER0000000000000000A") != access.None {
121 t.Fatalf("a repository with no rules granted something: %+v", got)
122 }
123}
124
125func TestDeletingARepositoryForgetsItsRules(t *testing.T) {
126 t.Parallel()
127 st, acls := withACLs(t)
128 m, err := st.Create("demo", "01M5OWNER0000000000000000A", "")
129 if err != nil {
130 t.Fatal(err)
131 }
132 if err := st.Delete("demo", "01M5OWNER0000000000000000A"); err != nil {
133 t.Fatal(err)
134 }
135 if got, err := acls.Get(m.ID); err != nil || got != nil {
136 t.Fatalf("rules outlived the repository: %+v, %v", got, err)
137 }
138}
139
140func TestDescriptionWritesAndClears(t *testing.T) {
141 t.Parallel()
142 st, _ := withACLs(t)
143 m, err := st.Create("demo", "01M5OWNER0000000000000000A", "first")
144 if err != nil {
145 t.Fatal(err)
146 }
147 if err := m.SetDescription(" second "); err != nil {
148 t.Fatal(err)
149 }
150 if m.Description != "second" {
151 t.Fatalf("in-memory description = %q", m.Description)
152 }
153 reopened, err := st.Open("demo")
154 if err != nil {
155 t.Fatal(err)
156 }
157 if reopened.Description != "second" {
158 t.Fatalf("reopened description = %q", reopened.Description)
159 }
160 if err := m.SetDescription(""); err != nil {
161 t.Fatal(err)
162 }
163 if _, err := os.Stat(filepath.Join(m.Dir, descFile)); !os.IsNotExist(err) {
164 t.Fatalf("cleared description left a file: %v", err)
165 }
166 reopened, err = st.Open("demo")
167 if err != nil {
168 t.Fatal(err)
169 }
170 if reopened.Description != "" {
171 t.Fatalf("cleared description came back as %q", reopened.Description)
172 }
173}
174
175// Public is the other cheap question host policy asks: does this repository have
176// an anonymous reader role? Private repository names must not reach a public
177// certificate log.
178func TestPublicRepositoriesAreTheOnlySitesEligibleForCertificates(t *testing.T) {
179 t.Parallel()
180 st, acls := withACLs(t)
181 if st.Public("missing") {
182 t.Fatal("a missing repository is public")
183 }
184 m, err := st.Create("demo", "01M5OWNER0000000000000000A", "")
185 if err != nil {
186 t.Fatal(err)
187 }
188 if st.Public("demo") {
189 t.Fatal("a new repository with no default role is public")
190 }
191 acl, err := m.ACL()
192 if err != nil {
193 t.Fatal(err)
194 }
195 acl.Default = "read"
196 if err := acls.Set(m.ID, acl, "01M5OWNER0000000000000000A", time.Now()); err != nil {
197 t.Fatal(err)
198 }
199 if !st.Public("demo") {
200 t.Fatal("a repository readable by default is not public")
201 }
202}
203