istio

Форк
0
/
imagefetcher_test.go 
631 строка · 16.3 Кб
1
// Copyright Istio Authors
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package wasm
16

17
import (
18
	"archive/tar"
19
	"bytes"
20
	"compress/gzip"
21
	"crypto/sha256"
22
	"encoding/base64"
23
	"encoding/hex"
24
	"fmt"
25
	"io"
26
	"net/http/httptest"
27
	"net/url"
28
	"reflect"
29
	"strings"
30
	"testing"
31

32
	"github.com/google/go-containerregistry/pkg/authn"
33
	"github.com/google/go-containerregistry/pkg/crane"
34
	"github.com/google/go-containerregistry/pkg/name"
35
	"github.com/google/go-containerregistry/pkg/registry"
36
	v1 "github.com/google/go-containerregistry/pkg/v1"
37
	"github.com/google/go-containerregistry/pkg/v1/empty"
38
	"github.com/google/go-containerregistry/pkg/v1/mutate"
39
	"github.com/google/go-containerregistry/pkg/v1/partial"
40
	"github.com/google/go-containerregistry/pkg/v1/random"
41
	"github.com/google/go-containerregistry/pkg/v1/remote"
42
	"github.com/google/go-containerregistry/pkg/v1/types"
43
)
44

45
func TestImageFetcherOption_useDefaultKeyChain(t *testing.T) {
46
	cases := []struct {
47
		name string
48
		opt  ImageFetcherOption
49
		exp  bool
50
	}{
51
		{name: "default key chain", exp: true},
52
		{name: "use secret config", opt: ImageFetcherOption{PullSecret: []byte("secret")}},
53
		{name: "missing secret", opt: ImageFetcherOption{}, exp: true},
54
	}
55
	for _, c := range cases {
56
		t.Run(c.name, func(t *testing.T) {
57
			actual := c.opt.useDefaultKeyChain()
58
			if actual != c.exp {
59
				t.Errorf("useDefaultKeyChain got %v want %v", actual, c.exp)
60
			}
61
		})
62
	}
63
}
64

65
func TestImageFetcher_Fetch(t *testing.T) {
66
	// Fetcher with anonymous auth.
67
	fetcher := ImageFetcher{fetchOpts: []remote.Option{remote.WithAuth(authn.Anonymous)}}
68

69
	// Set up a fake registry.
70
	s := httptest.NewServer(registry.New())
71
	defer s.Close()
72
	u, err := url.Parse(s.URL)
73
	if err != nil {
74
		t.Fatal(err)
75
	}
76

77
	t.Run("docker image", func(t *testing.T) {
78
		ref := fmt.Sprintf("%s/test/valid/docker", u.Host)
79
		exp := "this is wasm plugin"
80

81
		// Create docker layer.
82
		l, err := newMockLayer(types.DockerLayer,
83
			map[string][]byte{"plugin.wasm": []byte(exp)})
84
		if err != nil {
85
			t.Fatal(err)
86
		}
87
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
88
		if err != nil {
89
			t.Fatal(err)
90
		}
91

92
		// Set manifest type.
93
		manifest, err := img.Manifest()
94
		if err != nil {
95
			t.Fatal(err)
96
		}
97
		manifest.MediaType = types.DockerManifestSchema2
98

99
		// Push image to the registry.
100
		err = crane.Push(img, ref)
101
		if err != nil {
102
			t.Fatal(err)
103
		}
104

105
		// Fetch docker image with digest
106
		d, err := img.Digest()
107
		if err != nil {
108
			t.Fatal(err)
109
		}
110

111
		// Fetch OCI image.
112
		binaryFetcher, actualDiget, err := fetcher.PrepareFetch(ref)
113
		if err != nil {
114
			t.Fatal(err)
115
		}
116
		actual, err := binaryFetcher()
117
		if err != nil {
118
			t.Fatal(err)
119
		}
120
		if string(actual) != exp {
121
			t.Errorf("ImageFetcher.binaryFetcher got %s, but want '%s'", string(actual), exp)
122
		}
123
		if actualDiget != d.Hex {
124
			t.Errorf("ImageFetcher.binaryFetcher got digest %s, but want '%s'", actualDiget, d.Hex)
125
		}
126
	})
127

128
	t.Run("OCI standard", func(t *testing.T) {
129
		ref := fmt.Sprintf("%s/test/valid/oci_standard", u.Host)
130
		exp := "this is wasm plugin"
131

132
		// Create OCI compressed layer.
133
		l, err := newMockLayer(types.OCILayer,
134
			map[string][]byte{"plugin.wasm": []byte(exp)})
135
		if err != nil {
136
			t.Fatal(err)
137
		}
138
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
139
		if err != nil {
140
			t.Fatal(err)
141
		}
142
		img = mutate.MediaType(img, types.OCIManifestSchema1)
143

144
		// Push image to the registry.
145
		err = crane.Push(img, ref)
146
		if err != nil {
147
			t.Fatal(err)
148
		}
149

150
		// Fetch OCI image with digest
151
		d, err := img.Digest()
152
		if err != nil {
153
			t.Fatal(err)
154
		}
155

156
		// Fetch OCI image.
157
		binaryFetcher, actualDiget, err := fetcher.PrepareFetch(ref)
158
		if err != nil {
159
			t.Fatal(err)
160
		}
161
		actual, err := binaryFetcher()
162
		if err != nil {
163
			t.Fatal(err)
164
		}
165
		if string(actual) != exp {
166
			t.Errorf("ImageFetcher.binaryFetcher got %s, but want '%s'", string(actual), exp)
167
		}
168
		if actualDiget != d.Hex {
169
			t.Errorf("ImageFetcher.binaryFetcher got digest %s, but want '%s'", actualDiget, d.Hex)
170
		}
171
	})
172

173
	t.Run("OCI artifact", func(t *testing.T) {
174
		ref := fmt.Sprintf("%s/test/valid/oci_artifact", u.Host)
175

176
		// Create the image with custom media types.
177
		wasmLayer, err := random.Layer(1000, "application/vnd.module.wasm.content.layer.v1+wasm")
178
		if err != nil {
179
			t.Fatal(err)
180
		}
181
		configLayer, err := random.Layer(1000, "application/vnd.module.wasm.config.v1+json")
182
		if err != nil {
183
			t.Fatal(err)
184
		}
185
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: wasmLayer}, mutate.Addendum{Layer: configLayer})
186
		if err != nil {
187
			t.Fatal(err)
188
		}
189
		img = mutate.MediaType(img, types.OCIManifestSchema1)
190

191
		// Push image to the registry.
192
		err = crane.Push(img, ref)
193
		if err != nil {
194
			t.Fatal(err)
195
		}
196

197
		// Retrieve the wanted image content.
198
		wantReader, err := wasmLayer.Compressed()
199
		if err != nil {
200
			t.Fatal(err)
201
		}
202
		defer wantReader.Close()
203

204
		want, err := io.ReadAll(wantReader)
205
		if err != nil {
206
			t.Fatal(err)
207
		}
208

209
		// Fetch OCI image with digest
210
		d, err := img.Digest()
211
		if err != nil {
212
			t.Fatal(err)
213
		}
214

215
		// Fetch OCI image.
216
		binaryFetcher, actualDiget, err := fetcher.PrepareFetch(ref)
217
		if err != nil {
218
			t.Fatal(err)
219
		}
220
		actual, err := binaryFetcher()
221
		if err != nil {
222
			t.Fatal(err)
223
		}
224
		if !bytes.Equal(actual, want) {
225
			t.Errorf("ImageFetcher.binaryFetcher got %s, but want '%s'", string(actual), string(want))
226
		}
227
		if actualDiget != d.Hex {
228
			t.Errorf("ImageFetcher.binaryFetcher got digest %s, but want '%s'", actualDiget, d.Hex)
229
		}
230
	})
231

232
	t.Run("invalid image", func(t *testing.T) {
233
		ref := fmt.Sprintf("%s/test/invalid", u.Host)
234

235
		l, err := newMockLayer(types.OCIUncompressedLayer, map[string][]byte{"not-wasm.txt": []byte("a")})
236
		if err != nil {
237
			t.Fatal(err)
238
		}
239
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
240
		if err != nil {
241
			t.Fatal(err)
242
		}
243
		img = mutate.MediaType(img, types.OCIManifestSchema1)
244

245
		// Push image to the registry.
246
		err = crane.Push(img, ref)
247
		if err != nil {
248
			t.Fatal(err)
249
		}
250

251
		// Try to fetch.
252
		binaryFetcher, _, err := fetcher.PrepareFetch(ref)
253
		if err != nil {
254
			t.Fatal(err)
255
		}
256
		actual, err := binaryFetcher()
257
		if actual != nil {
258
			t.Errorf("ImageFetcher.binaryFetcher got %s, but want nil", string(actual))
259
		}
260

261
		expErr := `the given image is in invalid format as an OCI image: 2 errors occurred:
262
	* could not parse as compat variant: invalid media type application/vnd.oci.image.layer.v1.tar (expect application/vnd.oci.image.layer.v1.tar+gzip)
263
	* could not parse as oci variant: number of layers must be 2 but got 1`
264
		if actual := strings.TrimSpace(err.Error()); actual != expErr {
265
			t.Errorf("ImageFetcher.binaryFetcher get unexpected error '%v', but want '%v'", actual, expErr)
266
		}
267
	})
268
}
269

270
func TestExtractDockerImage(t *testing.T) {
271
	t.Run("no layers", func(t *testing.T) {
272
		_, err := extractDockerImage(empty.Image)
273
		if err == nil || err.Error() != "number of layers must be greater than zero" {
274
			t.Fatal("extractDockerImage should fail due to empty image")
275
		}
276
	})
277

278
	t.Run("valid layers", func(t *testing.T) {
279
		previousLayer, err := newMockLayer(types.DockerLayer, nil)
280
		if err != nil {
281
			t.Fatal(err)
282
		}
283

284
		exp := "this is wasm binary"
285
		lastLayer, err := newMockLayer(types.DockerLayer, map[string][]byte{
286
			"plugin.wasm": []byte(exp),
287
		})
288
		if err != nil {
289
			t.Fatal(err)
290
		}
291

292
		tCases := map[string]int{
293
			"one layer":           0,
294
			"more than one layer": 1,
295
		}
296

297
		for name, numberOfPreviousLayers := range tCases {
298
			t.Run(name, func(t *testing.T) {
299
				img := empty.Image
300
				for i := 0; i < numberOfPreviousLayers; i++ {
301
					img, err = mutate.Append(img, mutate.Addendum{Layer: previousLayer})
302
					if err != nil {
303
						t.Fatal(err)
304
					}
305
				}
306

307
				img, err = mutate.Append(img, mutate.Addendum{Layer: lastLayer})
308
				if err != nil {
309
					t.Fatal(err)
310
				}
311
				actual, err := extractDockerImage(img)
312
				if err != nil {
313
					t.Fatalf("extractDockerImage failed: %v", err)
314
				}
315

316
				if string(actual) != exp {
317
					t.Fatalf("got %s, but want %s", string(actual), exp)
318
				}
319
			})
320
		}
321
	})
322

323
	t.Run("invalid media type", func(t *testing.T) {
324
		l, err := newMockLayer(types.DockerPluginConfig, nil)
325
		if err != nil {
326
			t.Fatal(err)
327
		}
328
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
329
		if err != nil {
330
			t.Fatal(err)
331
		}
332
		_, err = extractDockerImage(img)
333
		if err == nil || !strings.Contains(err.Error(), "invalid media type") {
334
			t.Fatal("extractDockerImage should fail due to invalid media type")
335
		}
336
	})
337
}
338

339
func TestExtractOCIStandardImage(t *testing.T) {
340
	t.Run("no layers", func(t *testing.T) {
341
		_, err := extractOCIStandardImage(empty.Image)
342
		if err == nil || err.Error() != "number of layers must be greater than zero" {
343
			t.Fatal("extractDockerImage should fail due to empty image")
344
		}
345
	})
346

347
	t.Run("valid layers", func(t *testing.T) {
348
		previousLayer, err := newMockLayer(types.DockerLayer, nil)
349
		if err != nil {
350
			t.Fatal(err)
351
		}
352

353
		exp := "this is wasm binary"
354
		lastLayer, err := newMockLayer(types.OCILayer, map[string][]byte{
355
			"plugin.wasm": []byte(exp),
356
		})
357
		if err != nil {
358
			t.Fatal(err)
359
		}
360

361
		tCases := map[string]int{
362
			"one layer":           0,
363
			"more than one layer": 1,
364
		}
365

366
		for name, numberOfPreviousLayers := range tCases {
367
			t.Run(name, func(t *testing.T) {
368
				img := empty.Image
369
				for i := 0; i < numberOfPreviousLayers; i++ {
370
					img, err = mutate.Append(img, mutate.Addendum{Layer: previousLayer})
371
					if err != nil {
372
						t.Fatal(err)
373
					}
374
				}
375

376
				img, err = mutate.Append(img, mutate.Addendum{Layer: lastLayer})
377
				if err != nil {
378
					t.Fatal(err)
379
				}
380
				actual, err := extractOCIStandardImage(img)
381
				if err != nil {
382
					t.Fatalf("extractOCIStandardImage failed: %v", err)
383
				}
384

385
				if string(actual) != exp {
386
					t.Fatalf("got %s, but want %s", string(actual), exp)
387
				}
388
			})
389
		}
390
	})
391

392
	t.Run("invalid media type", func(t *testing.T) {
393
		l, err := newMockLayer(types.DockerLayer, nil)
394
		if err != nil {
395
			t.Fatal(err)
396
		}
397
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
398
		if err != nil {
399
			t.Fatal(err)
400
		}
401
		_, err = extractOCIStandardImage(img)
402
		if err == nil || !strings.Contains(err.Error(), "invalid media type") {
403
			t.Fatal("extractOCIStandardImage should fail due to invalid media type")
404
		}
405
	})
406
}
407

408
func newMockLayer(mediaType types.MediaType, contents map[string][]byte) (v1.Layer, error) {
409
	var b bytes.Buffer
410
	hasher := sha256.New()
411
	mw := io.MultiWriter(&b, hasher)
412
	tw := tar.NewWriter(mw)
413
	defer tw.Close()
414

415
	for filename, content := range contents {
416
		if err := tw.WriteHeader(&tar.Header{
417
			Name:     filename,
418
			Size:     int64(len(content)),
419
			Typeflag: tar.TypeReg,
420
		}); err != nil {
421
			return nil, err
422
		}
423
		if _, err := io.CopyN(tw, bytes.NewReader(content), int64(len(content))); err != nil {
424
			return nil, err
425
		}
426
	}
427
	return partial.UncompressedToLayer(
428
		&mockLayer{
429
			raw: b.Bytes(),
430
			diffID: v1.Hash{
431
				Algorithm: "sha256",
432
				Hex:       hex.EncodeToString(hasher.Sum(make([]byte, 0, hasher.Size()))),
433
			},
434
			mediaType: mediaType,
435
		},
436
	)
437
}
438

439
type mockLayer struct {
440
	raw       []byte
441
	diffID    v1.Hash
442
	mediaType types.MediaType
443
}
444

445
func (r *mockLayer) DiffID() (v1.Hash, error) { return v1.Hash{}, nil }
446
func (r *mockLayer) Uncompressed() (io.ReadCloser, error) {
447
	return io.NopCloser(bytes.NewBuffer(r.raw)), nil
448
}
449
func (r *mockLayer) MediaType() (types.MediaType, error) { return r.mediaType, nil }
450

451
func TestExtractOCIArtifactImage(t *testing.T) {
452
	t.Run("valid", func(t *testing.T) {
453
		// Create the image with custom media types.
454
		wasmLayer, err := random.Layer(1000, "application/vnd.module.wasm.content.layer.v1+wasm")
455
		if err != nil {
456
			t.Fatal(err)
457
		}
458
		configLayer, err := random.Layer(1000, "application/vnd.module.wasm.config.v1+json")
459
		if err != nil {
460
			t.Fatal(err)
461
		}
462
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: wasmLayer}, mutate.Addendum{Layer: configLayer})
463
		if err != nil {
464
			t.Fatal(err)
465
		}
466

467
		// Extract the binary.
468
		actual, err := extractOCIArtifactImage(img)
469
		if err != nil {
470
			t.Fatalf("extractOCIArtifactImage failed: %v", err)
471
		}
472

473
		// Retrieve the wanted image content.
474
		wantReader, err := wasmLayer.Compressed()
475
		if err != nil {
476
			t.Fatal(err)
477
		}
478
		defer wantReader.Close()
479
		want, err := io.ReadAll(wantReader)
480
		if err != nil {
481
			t.Fatal(err)
482
		}
483

484
		if !bytes.Equal(actual, want) {
485
			t.Errorf("extractOCIArtifactImage got %s, but want '%s'", string(actual), string(want))
486
		}
487
	})
488

489
	t.Run("invalid number of layers", func(t *testing.T) {
490
		l, err := random.Layer(1000, "application/vnd.module.wasm.content.layer.v1+wasm")
491
		if err != nil {
492
			t.Fatal(err)
493
		}
494
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
495
		if err != nil {
496
			t.Fatal(err)
497
		}
498
		_, err = extractOCIArtifactImage(img)
499
		if err == nil || !strings.Contains(err.Error(), "number of layers must be") {
500
			t.Fatal("extractOCIArtifactImage should fail due to invalid number of layers")
501
		}
502
	})
503

504
	t.Run("invalid media types", func(t *testing.T) {
505
		// Create the image with invalid media types.
506
		layer, err := random.Layer(1000, "aaa")
507
		if err != nil {
508
			t.Fatal(err)
509
		}
510
		img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: layer}, mutate.Addendum{Layer: layer})
511
		if err != nil {
512
			t.Fatal(err)
513
		}
514

515
		_, err = extractOCIArtifactImage(img)
516
		if err == nil || !strings.Contains(err.Error(),
517
			"could not find the layer of type application/vnd.module.wasm.content.layer.v1+wasm") {
518
			t.Fatal("extractOCIArtifactImage should fail due to invalid number of layers")
519
		}
520
	})
521
}
522

523
func TestExtractWasmPluginBinary(t *testing.T) {
524
	t.Run("ok", func(t *testing.T) {
525
		buf := bytes.NewBuffer(nil)
526
		gz := gzip.NewWriter(buf)
527
		tw := tar.NewWriter(gz)
528

529
		exp := "hello"
530
		if err := tw.WriteHeader(&tar.Header{
531
			Name: "plugin.wasm",
532
			Size: int64(len(exp)),
533
		}); err != nil {
534
			t.Fatal(err)
535
		}
536

537
		if _, err := io.WriteString(tw, exp); err != nil {
538
			t.Fatal(err)
539
		}
540

541
		tw.Close()
542
		gz.Close()
543

544
		actual, err := extractWasmPluginBinary(buf)
545
		if err != nil {
546
			t.Errorf("extractWasmPluginBinary failed: %v", err)
547
		}
548

549
		if string(actual) != exp {
550
			t.Errorf("extractWasmPluginBinary got %v, but want %v", string(actual), exp)
551
		}
552
	})
553

554
	t.Run("ok with relative path prefix", func(t *testing.T) {
555
		buf := bytes.NewBuffer(nil)
556
		gz := gzip.NewWriter(buf)
557
		tw := tar.NewWriter(gz)
558

559
		exp := "hello"
560
		if err := tw.WriteHeader(&tar.Header{
561
			Name: "./plugin.wasm",
562
			Size: int64(len(exp)),
563
		}); err != nil {
564
			t.Fatal(err)
565
		}
566

567
		if _, err := io.WriteString(tw, exp); err != nil {
568
			t.Fatal(err)
569
		}
570

571
		tw.Close()
572
		gz.Close()
573

574
		actual, err := extractWasmPluginBinary(buf)
575
		if err != nil {
576
			t.Errorf("extractWasmPluginBinary failed: %v", err)
577
		}
578

579
		if string(actual) != exp {
580
			t.Errorf("extractWasmPluginBinary got %v, but want %v", string(actual), exp)
581
		}
582
	})
583

584
	t.Run("not found", func(t *testing.T) {
585
		buf := bytes.NewBuffer(nil)
586
		gz := gzip.NewWriter(buf)
587
		tw := tar.NewWriter(gz)
588
		if err := tw.WriteHeader(&tar.Header{
589
			Name: "non-wasm.txt",
590
			Size: int64(1),
591
		}); err != nil {
592
			t.Fatal(err)
593
		}
594
		if _, err := tw.Write([]byte{1}); err != nil {
595
			t.Fatal(err)
596
		}
597
		tw.Close()
598
		gz.Close()
599
		_, err := extractWasmPluginBinary(buf)
600
		if err == nil || !strings.Contains(err.Error(), "not found") {
601
			t.Errorf("extractWasmPluginBinary must fail with not found")
602
		}
603
	})
604
}
605

606
func TestWasmKeyChain(t *testing.T) {
607
	dockerjson := fmt.Sprintf(`{"auths": {"test.io": {"auth": %q}}}`, encode("foo", "bar"))
608
	keyChain := wasmKeyChain{data: []byte(dockerjson)}
609
	testRegistry, _ := name.NewRegistry("test.io", name.WeakValidation)
610
	keyChain.Resolve(testRegistry)
611
	auth, err := keyChain.Resolve(testRegistry)
612
	if err != nil {
613
		t.Fatalf("Resolve() = %v", err)
614
	}
615
	got, err := auth.Authorization()
616
	if err != nil {
617
		t.Fatal(err)
618
	}
619
	want := &authn.AuthConfig{
620
		Username: "foo",
621
		Password: "bar",
622
	}
623
	if !reflect.DeepEqual(got, want) {
624
		t.Errorf("got %+v, want %+v", got, want)
625
	}
626
}
627

628
func encode(user, pass string) string {
629
	delimited := fmt.Sprintf("%s:%s", user, pass)
630
	return base64.StdEncoding.EncodeToString([]byte(delimited))
631
}
632

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.