carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostfiles
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "github.com/carlosframework/platform/internal/release"
13)
14
15// writeFile is sync_test.go's write, which is unix-only.
16func writeFile(t *testing.T, dir, rel, body string) {
17 t.Helper()
18 full := filepath.Join(dir, rel)
19 if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
20 t.Fatal(err)
21 }
22 if err := os.WriteFile(full, []byte(body), 0o640); err != nil {
23 t.Fatal(err)
24 }
25}
26
27// pfx2 is the next generation of pfx's incarnation.
28const pfx2 = "instances/shop.test/abc/files/g2/"
29
30// publishG1 publishes two files and a platform item under pfx.
31func publishG1(t *testing.T, s release.Store) {
32 t.Helper()
33 p := open(t, s)
34 stage(t, p, "instance.key", "k1")
35 stage(t, p, "uploads/a/b.txt", "hello")
36 if err := p.StagePlatform(context.Background(), "env", []byte("A=1")); err != nil {
37 t.Fatal(err)
38 }
39 commit(t, p)
40}
41
42func TestEntriesCarryTheirOriginQualifiedObjectKey(t *testing.T) {
43 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
44 publishG1(t, s)
45 m, _, err := Load(ctx, s, pfx)
46 if err != nil {
47 t.Fatal(err)
48 }
49 if m.V != 2 {
50 t.Fatalf("manifest version = %d, want 2", m.V)
51 }
52 e := m.Entries["instance.key"]
53 if want := pfx + objectKey(e.Seq, e.SHA256); e.Object != want {
54 t.Fatalf("object = %q, want %q", e.Object, want)
55 }
56 if _, _, err := s.Get(ctx, e.Object); err != nil {
57 t.Fatalf("the object is not at its key: %v", err)
58 }
59}
60
61func TestACopiedManifestRestoresTheSameBytesWithoutCopyingObjects(t *testing.T) {
62 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
63 publishG1(t, s)
64 m, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil)
65 if err != nil {
66 t.Fatal(err)
67 }
68 if keys, _ := s.List(ctx, pfx2+"objects/"); len(keys) != 0 {
69 t.Fatalf("the copy wrote objects: %v", keys)
70 }
71 src, _, _ := Load(ctx, s, pfx)
72 if m.Seq != src.Seq || len(m.Entries) != len(src.Entries) {
73 t.Fatalf("copy = %+v, source = %+v", m, src)
74 }
75 dir := t.TempDir()
76 if _, _, err := Restore(ctx, s, pfx2, dir, []string{pfx, pfx2}); err != nil {
77 t.Fatal(err)
78 }
79 if read(t, dir, "instance.key") != "k1" || read(t, dir, "uploads/a/b.txt") != "hello" {
80 t.Fatal("restored the wrong bytes")
81 }
82 got, err := ReadPlatform(ctx, s, pfx2, m, "env", []string{pfx, pfx2})
83 if err != nil || string(got) != "A=1" {
84 t.Fatalf("ReadPlatform = %q, %v", got, err)
85 }
86
87 // The new generation's publisher continues the sequence. A changed file
88 // is a new object under g2, an unchanged one keeps its g1 reference.
89 p, err := Open(ctx, s, pfx2)
90 if err != nil {
91 t.Fatal(err)
92 }
93 stage(t, p, "instance.key", "k1")
94 stage(t, p, "uploads/a/b.txt", "changed")
95 if seq := commit(t, p); seq != src.Seq+1 {
96 t.Fatalf("seq = %d, want %d", seq, src.Seq+1)
97 }
98 m2, _, _ := Load(ctx, s, pfx2)
99 if !strings.HasPrefix(m2.Entries["instance.key"].Object, pfx) || !strings.HasPrefix(m2.Entries["uploads/a/b.txt"].Object, pfx2) {
100 t.Fatalf("entries = %+v", m2.Entries)
101 }
102 dir2 := t.TempDir()
103 if _, _, err := Restore(ctx, s, pfx2, dir2, []string{pfx, pfx2}); err != nil {
104 t.Fatal(err)
105 }
106 if read(t, dir2, "instance.key") != "k1" || read(t, dir2, "uploads/a/b.txt") != "changed" {
107 t.Fatal("restored the wrong bytes after the second generation published")
108 }
109 // The source generation is untouched.
110 if again, _, _ := Load(ctx, s, pfx); again.Seq != src.Seq {
111 t.Fatalf("source seq moved to %d", again.Seq)
112 }
113}
114
115func TestRestoreRefusesAnEntryOutsideTheAllowedOriginsAndTouchesNothing(t *testing.T) {
116 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
117 publishG1(t, s)
118 m, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil)
119 if err != nil {
120 t.Fatal(err)
121 }
122 for name, origins := range map[string][]string{
123 "default (own prefix only)": nil,
124 "another generation only": {"instances/shop.test/abc/files/g0/", pfx2},
125 } {
126 dir := t.TempDir()
127 if err := os.WriteFile(filepath.Join(dir, "instance.key"), []byte("before"), 0o600); err != nil {
128 t.Fatal(err)
129 }
130 if _, _, err := Restore(ctx, s, pfx2, dir, origins); err == nil || !strings.Contains(err.Error(), "origin") {
131 t.Fatalf("%s: Restore err = %v, want an origin refusal", name, err)
132 }
133 if read(t, dir, "instance.key") != "before" {
134 t.Fatalf("%s: Restore wrote before refusing", name)
135 }
136 if _, err := os.Stat(filepath.Join(dir, "uploads")); !os.IsNotExist(err) {
137 t.Fatalf("%s: Restore created uploads/", name)
138 }
139 if _, err := ReadPlatform(ctx, s, pfx2, m, "env", origins); err == nil {
140 t.Fatalf("%s: ReadPlatform read an object outside its origins", name)
141 }
142 }
143}
144
145func TestRestoreRefusesATamperedObjectKey(t *testing.T) {
146 rel := func(e Entry) string { return objectKey(e.Seq, e.SHA256) }
147 cases := map[string]func(e Entry) string{
148 "wrong seq": func(e Entry) string { return pfx + objectKey(e.Seq+1, e.SHA256) },
149 "wrong sha": func(e Entry) string { return pfx + objectKey(e.Seq, strings.Repeat("0", 64)) },
150 "relative (version 1)": rel,
151 "dot-dot to a sibling gen": func(e Entry) string { return pfx + "../g9/" + rel(e) },
152 "dot-dot inside": func(e Entry) string { return pfx + "objects/../" + rel(e) },
153 "absolute": func(e Entry) string { return "/" + pfx + rel(e) },
154 "another host's prefix": func(e Entry) string { return "instances/other.test/abc/files/g1/" + rel(e) },
155 "nested under the origin": func(e Entry) string { return pfx + "x/" + rel(e) },
156 "double slash": func(e Entry) string { return strings.TrimSuffix(pfx, "/") + "//" + rel(e) },
157 "empty": func(Entry) string { return "" },
158 }
159 for name, key := range cases {
160 for _, platform := range []bool{false, true} {
161 s, dir := putHostile(t, func(m *Manifest) {
162 if platform {
163 e := m.Platform["item"]
164 e.Object = key(e)
165 m.Platform["item"] = e
166 return
167 }
168 e := m.Entries["a"]
169 e.Object = key(e)
170 m.Entries["a"] = e
171 })
172 if _, _, err := Restore(context.Background(), s, pfx, dir, nil); err == nil {
173 t.Fatalf("%s (platform %v): Restore accepted the manifest", name, platform)
174 }
175 if got := read(t, dir, "gone"); got != "still here" {
176 t.Fatalf("%s (platform %v): gone = %q, want it untouched", name, platform, got)
177 }
178 if _, err := os.Stat(filepath.Join(dir, "a")); !os.IsNotExist(err) {
179 t.Fatalf("%s (platform %v): a was written", name, platform)
180 }
181 }
182 }
183}
184
185func TestRestoreRefusesAMalformedOrigin(t *testing.T) {
186 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
187 publishG1(t, s)
188 for _, o := range []string{"", "/", "instances/shop.test/abc/files/g1", "/instances/shop.test/abc/files/g1/",
189 "instances/shop.test/abc/files/../files/g1/", "instances//shop.test/", "./instances/"} {
190 if _, _, err := Restore(ctx, s, pfx, t.TempDir(), []string{o}); err == nil {
191 t.Fatalf("Restore accepted origin %q", o)
192 }
193 }
194}
195
196func TestAManifestOfAnotherVersionIsRefused(t *testing.T) {
197 for _, v := range []int{0, 1, 3} {
198 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
199 body, _ := json.Marshal(map[string]any{"v": v, "seq": 0, "entries": map[string]any{}, "platform": map[string]any{}})
200 if _, err := s.Put(ctx, ManifestKey(pfx), body, ""); err != nil {
201 t.Fatal(err)
202 }
203 if _, _, err := Load(ctx, s, pfx); err == nil || !strings.Contains(err.Error(), "version") {
204 t.Fatalf("v%d: Load err = %v", v, err)
205 }
206 if _, err := Open(ctx, s, pfx); err == nil {
207 t.Fatalf("v%d: Open accepted it", v)
208 }
209 if _, _, err := Restore(ctx, s, pfx, t.TempDir(), nil); err == nil {
210 t.Fatalf("v%d: Restore accepted it", v)
211 }
212 if _, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil); err == nil {
213 t.Fatalf("v%d: CopyManifest accepted it", v)
214 }
215 }
216}
217
218func TestCopyManifestIsCreateOnlyAndRetrySafe(t *testing.T) {
219 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
220 publishG1(t, s)
221 if _, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil); err != nil {
222 t.Fatal(err)
223 }
224 // A repeat whose first response was lost finds its own bytes: success.
225 if _, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil); err != nil {
226 t.Fatalf("a repeated copy: %v", err)
227 }
228 // Once g2 has moved on, a copy is refused and changes nothing.
229 p, err := Open(ctx, s, pfx2)
230 if err != nil {
231 t.Fatal(err)
232 }
233 stage(t, p, "new", "n")
234 commit(t, p)
235 before, _, _ := s.Get(ctx, ManifestKey(pfx2))
236 if _, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil); !errors.Is(err, ErrManifestExists) {
237 t.Fatalf("err = %v, want ErrManifestExists", err)
238 }
239 after, _, _ := s.Get(ctx, ManifestKey(pfx2))
240 if string(before) != string(after) {
241 t.Fatal("a refused copy changed the target")
242 }
243}
244
245func TestCopyManifestRefusesAMissingOrInvalidSourceAndWritesNothing(t *testing.T) {
246 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
247 if _, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil); !errors.Is(err, os.ErrNotExist) {
248 t.Fatalf("absent source: err = %v, want os.ErrNotExist", err)
249 }
250 // g2 copied from g1 references g1; copying g2 on to g3 with only g2
251 // admitted is refused.
252 publishG1(t, s)
253 if _, err := CopyManifest(ctx, s, pfx, pfx2, 0, nil); err != nil {
254 t.Fatal(err)
255 }
256 const pfx3 = "instances/shop.test/abc/files/g3/"
257 if _, err := CopyManifest(ctx, s, pfx2, pfx3, 0, nil); err == nil || !strings.Contains(err.Error(), "origin") {
258 t.Fatalf("err = %v, want an origin refusal", err)
259 }
260 if _, _, err := s.Get(ctx, ManifestKey(pfx3)); !errors.Is(err, os.ErrNotExist) {
261 t.Fatalf("a refused copy wrote a manifest: %v", err)
262 }
263 if _, err := CopyManifest(ctx, s, pfx2, pfx3, 0, []string{pfx, pfx2}); err != nil {
264 t.Fatalf("with g1 admitted: %v", err)
265 }
266 for _, bad := range [][2]string{{pfx, pfx}, {pfx, "instances/shop.test/abc/files/g4"}, {"nope", pfx2}} {
267 if _, err := CopyManifest(ctx, s, bad[0], bad[1], 0, nil); err == nil {
268 t.Fatalf("CopyManifest(%q, %q) accepted", bad[0], bad[1])
269 }
270 }
271}
272
273func TestPublishCommitsTheDirectoryAsOneRevision(t *testing.T) {
274 ctx, s, src := context.Background(), release.NewDirStore(t.TempDir()), t.TempDir()
275 writeFile(t, src, "instance.key", "k1")
276 writeFile(t, src, "uploads/a.txt", "a")
277 writeFile(t, src, "skip/me", "x")
278 skip := func(rel string, isDir bool) bool { return rel == "skip" }
279 seq, st, err := Publish(ctx, s, pfx, src, skip)
280 if err != nil {
281 t.Fatal(err)
282 }
283 if seq != 2 || st.Published != 2 {
284 t.Fatalf("seq = %d, stats = %+v", seq, st)
285 }
286 dir := t.TempDir()
287 m, _, err := Restore(ctx, s, pfx, dir, nil)
288 if err != nil {
289 t.Fatal(err)
290 }
291 if m.Seq != seq || read(t, dir, "instance.key") != "k1" || read(t, dir, "uploads/a.txt") != "a" {
292 t.Fatalf("m = %+v", m)
293 }
294 if _, ok := m.Entries["skip/me"]; ok {
295 t.Fatal("a skipped path was published")
296 }
297 // Publishing again with nothing changed is the same revision.
298 if again, _, err := Publish(ctx, s, pfx, src, skip); err != nil || again != seq {
299 t.Fatalf("again = %d, %v", again, err)
300 }
301 if _, _, err := Publish(ctx, s, pfx, filepath.Join(src, "absent"), nil); err == nil {
302 t.Fatal("published a missing directory")
303 }
304}
305
306// expectSeq pins the copy to the source revision the caller admitted: a
307// source that has moved on is refused, on a first attempt or a retry, and
308// nothing is written.
309func TestCopyManifestRefusesASourceThatIsNotAtTheExpectedSeq(t *testing.T) {
310 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
311 publishG1(t, s)
312 src, _, _ := Load(ctx, s, pfx)
313 if _, err := CopyManifest(ctx, s, pfx, pfx2, src.Seq+1, nil); !errors.Is(err, ErrSourceMoved) {
314 t.Fatalf("err = %v, want ErrSourceMoved", err)
315 }
316 if _, _, err := s.Get(ctx, ManifestKey(pfx2)); !errors.Is(err, os.ErrNotExist) {
317 t.Fatalf("a refused copy wrote a manifest: %v", err)
318 }
319 m, err := CopyManifest(ctx, s, pfx, pfx2, src.Seq, nil)
320 if err != nil || m.Seq != src.Seq {
321 t.Fatalf("m.Seq = %d, err = %v", m.Seq, err)
322 }
323 if _, err := CopyManifest(ctx, s, pfx, pfx2, src.Seq, nil); err != nil {
324 t.Fatalf("a repeat at the same seq: %v", err)
325 }
326 // The source commits again: a retry pinned to the old seq is refused,
327 // and the target keeps the pinned copy.
328 p := open(t, s)
329 stage(t, p, "later", "x")
330 commit(t, p)
331 before, _, _ := s.Get(ctx, ManifestKey(pfx2))
332 if _, err := CopyManifest(ctx, s, pfx, pfx2, src.Seq, nil); !errors.Is(err, ErrSourceMoved) {
333 t.Fatalf("err = %v, want ErrSourceMoved", err)
334 }
335 if after, _, _ := s.Get(ctx, ManifestKey(pfx2)); string(before) != string(after) {
336 t.Fatal("a refused copy changed the target")
337 }
338}
339
340func TestRestoreAndReadPlatformRefuseAMalformedPrefix(t *testing.T) {
341 ctx, s := context.Background(), release.NewDirStore(t.TempDir())
342 publishG1(t, s)
343 m, _, _ := Load(ctx, s, pfx)
344 for _, bad := range []string{"", "instances/shop.test/abc/files/g1", "/" + pfx, "instances/../" + pfx, "instances//x/"} {
345 // Admitting pfx explicitly means only the prefix check can refuse.
346 if _, _, err := Restore(ctx, s, bad, t.TempDir(), []string{pfx}); err == nil {
347 t.Fatalf("Restore accepted prefix %q", bad)
348 }
349 if _, err := ReadPlatform(ctx, s, bad, m, "env", []string{pfx}); err == nil {
350 t.Fatalf("ReadPlatform accepted prefix %q", bad)
351 }
352 }
353}
354