1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package remote
16
17 import (
18 "bytes"
19 "context"
20 "fmt"
21 "net/http"
22 "net/http/httptest"
23 "net/url"
24 "testing"
25
26 "github.com/google/go-cmp/cmp"
27 v1 "github.com/google/go-containerregistry/pkg/v1"
28 "github.com/google/go-containerregistry/pkg/v1/random"
29 "github.com/google/go-containerregistry/pkg/v1/types"
30 )
31
32 func randomIndex(t *testing.T) v1.ImageIndex {
33 rnd, err := random.Index(1024, 1, 3)
34 if err != nil {
35 t.Fatalf("random.Index() = %v", err)
36 }
37 return rnd
38 }
39
40 func mustIndexManifest(t *testing.T, idx v1.ImageIndex) *v1.IndexManifest {
41 m, err := idx.IndexManifest()
42 if err != nil {
43 t.Fatalf("IndexManifest() = %v", err)
44 }
45 return m
46 }
47
48 func mustChild(t *testing.T, idx v1.ImageIndex, h v1.Hash) v1.Image {
49 img, err := idx.Image(h)
50 if err != nil {
51 t.Fatalf("Image(%s) = %v", h, err)
52 }
53 return img
54 }
55
56 func mustMediaType(t *testing.T, tag withMediaType) types.MediaType {
57 mt, err := tag.MediaType()
58 if err != nil {
59 t.Fatalf("MediaType() = %v", err)
60 }
61 return mt
62 }
63
64 func mustHash(t *testing.T, s string) v1.Hash {
65 h, err := v1.NewHash(s)
66 if err != nil {
67 t.Fatalf("NewHash() = %v", err)
68 }
69 return h
70 }
71
72 func TestIndexRawManifestDigests(t *testing.T) {
73 idx := randomIndex(t)
74 expectedRepo := "foo/bar"
75
76 cases := []struct {
77 name string
78 ref string
79 responseBody []byte
80 contentDigest string
81 wantErr bool
82 }{{
83 name: "normal pull, by tag",
84 ref: "latest",
85 responseBody: mustRawManifest(t, idx),
86 contentDigest: mustDigest(t, idx).String(),
87 wantErr: false,
88 }, {
89 name: "normal pull, by digest",
90 ref: mustDigest(t, idx).String(),
91 responseBody: mustRawManifest(t, idx),
92 contentDigest: mustDigest(t, idx).String(),
93 wantErr: false,
94 }, {
95 name: "right content-digest, wrong body, by digest",
96 ref: mustDigest(t, idx).String(),
97 responseBody: []byte("not even json"),
98 contentDigest: mustDigest(t, idx).String(),
99 wantErr: true,
100 }, {
101 name: "right body, wrong content-digest, by tag",
102 ref: "latest",
103 responseBody: mustRawManifest(t, idx),
104 contentDigest: bogusDigest,
105 wantErr: false,
106 }, {
107
108 name: "right body, wrong content-digest, by digest",
109 ref: mustDigest(t, idx).String(),
110 responseBody: mustRawManifest(t, idx),
111 contentDigest: bogusDigest,
112 wantErr: false,
113 }}
114
115 for _, tc := range cases {
116 t.Run(tc.name, func(t *testing.T) {
117 manifestPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, tc.ref)
118 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
119 switch r.URL.Path {
120 case manifestPath:
121 if r.Method != http.MethodGet {
122 t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
123 }
124
125 w.Header().Set("Docker-Content-Digest", tc.contentDigest)
126 w.Write(tc.responseBody)
127 default:
128 t.Fatalf("Unexpected path: %v", r.URL.Path)
129 }
130 }))
131 defer server.Close()
132 u, err := url.Parse(server.URL)
133 if err != nil {
134 t.Fatalf("url.Parse(%v) = %v", server.URL, err)
135 }
136
137 ref, err := newReference(u.Host, expectedRepo, tc.ref)
138 if err != nil {
139 t.Fatalf("url.Parse(%v, %v, %v) = %v", u.Host, expectedRepo, tc.ref, err)
140 }
141
142 rmt := remoteIndex{
143 ref: ref,
144 ctx: context.Background(),
145 fetcher: fetcher{
146 target: ref.Context(),
147 client: http.DefaultClient,
148 },
149 }
150
151 if _, err := rmt.RawManifest(); (err != nil) != tc.wantErr {
152 t.Errorf("RawManifest() wrong error: %v, want %v: %v\n", (err != nil), tc.wantErr, err)
153 }
154 })
155 }
156 }
157
158 func TestIndex(t *testing.T) {
159 idx := randomIndex(t)
160 expectedRepo := "foo/bar"
161 manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
162 childDigest := mustIndexManifest(t, idx).Manifests[0].Digest
163 child := mustChild(t, idx, childDigest)
164 childPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, childDigest)
165 configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, child))
166 manifestReqCount := 0
167 childReqCount := 0
168
169 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
170 switch r.URL.Path {
171 case "/v2/":
172 w.WriteHeader(http.StatusOK)
173 case manifestPath:
174 manifestReqCount++
175 if r.Method != http.MethodGet {
176 t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
177 }
178 w.Header().Set("Content-Type", string(mustMediaType(t, idx)))
179 w.Write(mustRawManifest(t, idx))
180 case childPath:
181 childReqCount++
182 if r.Method != http.MethodGet {
183 t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
184 }
185 w.Write(mustRawManifest(t, child))
186 case configPath:
187 if r.Method != http.MethodGet {
188 t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
189 }
190 w.Write(mustRawConfigFile(t, child))
191 default:
192 t.Fatalf("Unexpected path: %v", r.URL.Path)
193 }
194 }))
195 defer server.Close()
196 u, err := url.Parse(server.URL)
197 if err != nil {
198 t.Fatalf("url.Parse(%v) = %v", server.URL, err)
199 }
200
201 tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
202 rmt, err := Index(tag, WithTransport(http.DefaultTransport))
203 if err != nil {
204 t.Errorf("Index() = %v", err)
205 }
206 rmtChild, err := rmt.Image(childDigest)
207 if err != nil {
208 t.Errorf("remoteIndex.Image(%s) = %v", childDigest, err)
209 }
210
211
212 if got, want := mustRawManifest(t, rmt), mustRawManifest(t, idx); !bytes.Equal(got, want) {
213 t.Errorf("RawManifest() = %v, want %v", got, want)
214 }
215 if diff := cmp.Diff(mustIndexManifest(t, idx), mustIndexManifest(t, rmt)); diff != "" {
216 t.Errorf("IndexManifest() (-want +got) = %v", diff)
217 }
218 if got, want := mustMediaType(t, rmt), mustMediaType(t, idx); got != want {
219 t.Errorf("MediaType() = %v, want %v", got, want)
220 }
221 if got, want := mustDigest(t, rmt), mustDigest(t, idx); got != want {
222 t.Errorf("Digest() = %v, want %v", got, want)
223 }
224
225 if manifestReqCount != 1 {
226 t.Errorf("RawManifest made %v requests, expected 1", manifestReqCount)
227 }
228
229
230 if got, want := mustRawManifest(t, rmtChild), mustRawManifest(t, child); !bytes.Equal(got, want) {
231 t.Errorf("RawManifest() = %v, want %v", got, want)
232 }
233 if got, want := mustRawConfigFile(t, rmtChild), mustRawConfigFile(t, child); !bytes.Equal(got, want) {
234 t.Errorf("RawConfigFile() = %v, want %v", got, want)
235 }
236
237 if childReqCount != 1 {
238 t.Errorf("RawManifest made %v requests, expected 1", childReqCount)
239 }
240
241
242 bogusHash := mustHash(t, bogusDigest)
243
244 if _, err := rmt.Image(bogusHash); err == nil {
245 t.Errorf("remoteIndex.Image(bogusDigest) err = %v, wanted err", err)
246 }
247 if _, err := rmt.ImageIndex(bogusHash); err == nil {
248 t.Errorf("remoteIndex.ImageIndex(bogusDigest) err = %v, wanted err", err)
249 }
250 }
251
252
253
254
255 func TestMatchesPlatform(t *testing.T) {
256 t.Parallel()
257 tests := []struct {
258
259
260 given v1.Platform
261 required v1.Platform
262 want bool
263 }{{
264 given: v1.Platform{
265 Architecture: "amd64",
266 OS: "linux",
267 OSVersion: "10.0.10586",
268 OSFeatures: []string{"win32k"},
269 Variant: "armv6l",
270 Features: []string{"sse4"},
271 },
272 required: v1.Platform{
273 Architecture: "amd64",
274 OS: "linux",
275 OSVersion: "10.0.10586",
276 OSFeatures: []string{"win32k"},
277 Variant: "armv6l",
278 Features: []string{"sse4"},
279 },
280 want: true,
281 },
282 {
283 given: v1.Platform{
284 Architecture: "arm",
285 OS: "linux",
286 OSVersion: "10.0.10586",
287 OSFeatures: []string{"win64k"},
288 Variant: "armv6l",
289 Features: []string{"sse4"},
290 },
291 required: v1.Platform{
292 Architecture: "amd64",
293 OS: "linux",
294 OSVersion: "10.0.10586",
295 OSFeatures: []string{"win32k"},
296 Variant: "armv6l",
297 Features: []string{"sse4"},
298 },
299 want: false,
300 },
301 {
302 given: v1.Platform{
303 Architecture: "amd64",
304 OS: "linux",
305 OSVersion: "10.0.10586",
306 OSFeatures: []string{"win64k"},
307 Variant: "armv6l",
308 Features: []string{"sse4"},
309 },
310 required: v1.Platform{
311 Architecture: "amd64",
312 OS: "linux",
313 OSVersion: "10.0.10587",
314 OSFeatures: []string{"win64k"},
315 Variant: "armv6l",
316 Features: []string{"sse4"},
317 },
318 want: false,
319 },
320 {
321 given: v1.Platform{
322 Architecture: "arm",
323 OS: "linux",
324 OSVersion: "10.0.10586",
325 OSFeatures: []string{"win64k"},
326 Variant: "armv6l",
327 Features: []string{"sse4"},
328 },
329 required: v1.Platform{
330 Architecture: "arm",
331 OS: "linux",
332 OSVersion: "10.0.10586",
333 OSFeatures: []string{"win32k"},
334 Variant: "armv6l",
335 Features: []string{"sse4"},
336 },
337 want: false,
338 },
339 {
340 given: v1.Platform{
341 Architecture: "amd64",
342 OS: "linux",
343 OSVersion: "10.0.10586",
344 OSFeatures: []string{"win64k"},
345 Variant: "armv6l",
346 Features: []string{"sse4"},
347 },
348 required: v1.Platform{
349 Architecture: "amd64",
350 OS: "linux",
351 OSVersion: "10.0.10586",
352 OSFeatures: []string{"win64k"},
353 Variant: "armv7l",
354 Features: []string{"sse4"},
355 },
356 want: false,
357 },
358 {
359 given: v1.Platform{
360 Architecture: "arm",
361 OS: "linux",
362 OSVersion: "10.0.10586",
363 OSFeatures: []string{"win64k"},
364 Variant: "armv6l",
365 Features: []string{"sse4"},
366 },
367 required: v1.Platform{
368 Architecture: "arm",
369 OS: "LinuX",
370 OSVersion: "10.0.10586",
371 OSFeatures: []string{"win64k"},
372 Variant: "armv6l",
373 Features: []string{"sse4"},
374 },
375 want: false,
376 },
377 {
378
379 given: v1.Platform{
380 Architecture: "arm",
381 OS: "linux",
382 OSVersion: "10.0.10586",
383 OSFeatures: []string{"win64k"},
384 Variant: "armv6l",
385 Features: []string{"sse4"},
386 },
387 required: v1.Platform{
388 Architecture: "arm",
389 OS: "linux",
390 OSVersion: "",
391 OSFeatures: []string{"win64k"},
392 Variant: "",
393 Features: []string{"sse4"},
394 },
395 want: true,
396 },
397 {
398 given: v1.Platform{
399 Architecture: "amd64",
400 OS: "linux",
401 OSVersion: "",
402 OSFeatures: []string{},
403 Variant: "",
404 Features: []string{},
405 },
406 required: v1.Platform{
407 Architecture: "amd64",
408 OS: "linux",
409 OSVersion: "10.0.10586",
410 OSFeatures: []string{"win32k"},
411 Variant: "armv6l",
412 Features: []string{"sse4"},
413 },
414 want: false,
415 },
416 {
417
418 given: v1.Platform{
419 Architecture: "",
420 OS: "linux",
421 OSVersion: "10.0.10586",
422 OSFeatures: []string{"win32k"},
423 Variant: "armv6l",
424 Features: []string{"sse4"},
425 },
426 required: v1.Platform{
427 Architecture: "",
428 OS: "linux",
429 OSVersion: "",
430 OSFeatures: []string{},
431 Variant: "",
432 Features: []string{},
433 },
434 want: true,
435 },
436 {
437
438 given: v1.Platform{
439 Architecture: "arm",
440 OS: "linux",
441 OSVersion: "10.0.10586",
442 OSFeatures: []string{"win64k", "f1", "f2"},
443 Variant: "",
444 Features: []string{"sse4", "f1"},
445 },
446 required: v1.Platform{
447 Architecture: "arm",
448 OS: "linux",
449 OSVersion: "10.0.10586",
450 OSFeatures: []string{"win64k"},
451 Variant: "",
452 Features: []string{"sse4"},
453 },
454 want: true,
455 },
456 {
457
458 given: v1.Platform{
459 Architecture: "arm",
460 OS: "linux",
461 OSVersion: "10.0.10586",
462 OSFeatures: []string{"win64k", "f1", "f2"},
463 Variant: "",
464 Features: []string{"sse4", "f1"},
465 },
466 required: v1.Platform{
467 Architecture: "arm",
468 OS: "linux",
469 OSVersion: "10.0.10586",
470 OSFeatures: []string{"win64k"},
471 Variant: "",
472 Features: []string{"sse4", "f2"},
473 },
474 want: false,
475 },
476 {
477
478
479 given: v1.Platform{
480 Architecture: "arm",
481 OS: "linux",
482 OSVersion: "10.0.10586",
483 OSFeatures: []string{"win64k", "f1", "f2"},
484 Variant: "armv6l",
485 Features: []string{"sse4"},
486 },
487 required: v1.Platform{
488 Architecture: "arm",
489 OS: "linux",
490 OSVersion: "10.0.10586",
491 OSFeatures: []string{},
492 Variant: "armv6l",
493 Features: []string{"sse4"},
494 },
495 want: true,
496 },
497 }
498
499 for _, test := range tests {
500 got := matchesPlatform(test.given, test.required)
501 if got != test.want {
502 t.Errorf("matchesPlatform(%v, %v); got %v, want %v", test.given, test.required, got, test.want)
503 }
504 }
505 }
506
View as plain text