1
// Copyright Istio Authors
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
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"
45
func TestImageFetcherOption_useDefaultKeyChain(t *testing.T) {
48
opt ImageFetcherOption
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},
55
for _, c := range cases {
56
t.Run(c.name, func(t *testing.T) {
57
actual := c.opt.useDefaultKeyChain()
59
t.Errorf("useDefaultKeyChain got %v want %v", actual, c.exp)
65
func TestImageFetcher_Fetch(t *testing.T) {
66
// Fetcher with anonymous auth.
67
fetcher := ImageFetcher{fetchOpts: []remote.Option{remote.WithAuth(authn.Anonymous)}}
69
// Set up a fake registry.
70
s := httptest.NewServer(registry.New())
72
u, err := url.Parse(s.URL)
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"
81
// Create docker layer.
82
l, err := newMockLayer(types.DockerLayer,
83
map[string][]byte{"plugin.wasm": []byte(exp)})
87
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
93
manifest, err := img.Manifest()
97
manifest.MediaType = types.DockerManifestSchema2
99
// Push image to the registry.
100
err = crane.Push(img, ref)
105
// Fetch docker image with digest
106
d, err := img.Digest()
112
binaryFetcher, actualDiget, err := fetcher.PrepareFetch(ref)
116
actual, err := binaryFetcher()
120
if string(actual) != exp {
121
t.Errorf("ImageFetcher.binaryFetcher got %s, but want '%s'", string(actual), exp)
123
if actualDiget != d.Hex {
124
t.Errorf("ImageFetcher.binaryFetcher got digest %s, but want '%s'", actualDiget, d.Hex)
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"
132
// Create OCI compressed layer.
133
l, err := newMockLayer(types.OCILayer,
134
map[string][]byte{"plugin.wasm": []byte(exp)})
138
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
142
img = mutate.MediaType(img, types.OCIManifestSchema1)
144
// Push image to the registry.
145
err = crane.Push(img, ref)
150
// Fetch OCI image with digest
151
d, err := img.Digest()
157
binaryFetcher, actualDiget, err := fetcher.PrepareFetch(ref)
161
actual, err := binaryFetcher()
165
if string(actual) != exp {
166
t.Errorf("ImageFetcher.binaryFetcher got %s, but want '%s'", string(actual), exp)
168
if actualDiget != d.Hex {
169
t.Errorf("ImageFetcher.binaryFetcher got digest %s, but want '%s'", actualDiget, d.Hex)
173
t.Run("OCI artifact", func(t *testing.T) {
174
ref := fmt.Sprintf("%s/test/valid/oci_artifact", u.Host)
176
// Create the image with custom media types.
177
wasmLayer, err := random.Layer(1000, "application/vnd.module.wasm.content.layer.v1+wasm")
181
configLayer, err := random.Layer(1000, "application/vnd.module.wasm.config.v1+json")
185
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: wasmLayer}, mutate.Addendum{Layer: configLayer})
189
img = mutate.MediaType(img, types.OCIManifestSchema1)
191
// Push image to the registry.
192
err = crane.Push(img, ref)
197
// Retrieve the wanted image content.
198
wantReader, err := wasmLayer.Compressed()
202
defer wantReader.Close()
204
want, err := io.ReadAll(wantReader)
209
// Fetch OCI image with digest
210
d, err := img.Digest()
216
binaryFetcher, actualDiget, err := fetcher.PrepareFetch(ref)
220
actual, err := binaryFetcher()
224
if !bytes.Equal(actual, want) {
225
t.Errorf("ImageFetcher.binaryFetcher got %s, but want '%s'", string(actual), string(want))
227
if actualDiget != d.Hex {
228
t.Errorf("ImageFetcher.binaryFetcher got digest %s, but want '%s'", actualDiget, d.Hex)
232
t.Run("invalid image", func(t *testing.T) {
233
ref := fmt.Sprintf("%s/test/invalid", u.Host)
235
l, err := newMockLayer(types.OCIUncompressedLayer, map[string][]byte{"not-wasm.txt": []byte("a")})
239
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
243
img = mutate.MediaType(img, types.OCIManifestSchema1)
245
// Push image to the registry.
246
err = crane.Push(img, ref)
252
binaryFetcher, _, err := fetcher.PrepareFetch(ref)
256
actual, err := binaryFetcher()
258
t.Errorf("ImageFetcher.binaryFetcher got %s, but want nil", string(actual))
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)
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")
278
t.Run("valid layers", func(t *testing.T) {
279
previousLayer, err := newMockLayer(types.DockerLayer, nil)
284
exp := "this is wasm binary"
285
lastLayer, err := newMockLayer(types.DockerLayer, map[string][]byte{
286
"plugin.wasm": []byte(exp),
292
tCases := map[string]int{
294
"more than one layer": 1,
297
for name, numberOfPreviousLayers := range tCases {
298
t.Run(name, func(t *testing.T) {
300
for i := 0; i < numberOfPreviousLayers; i++ {
301
img, err = mutate.Append(img, mutate.Addendum{Layer: previousLayer})
307
img, err = mutate.Append(img, mutate.Addendum{Layer: lastLayer})
311
actual, err := extractDockerImage(img)
313
t.Fatalf("extractDockerImage failed: %v", err)
316
if string(actual) != exp {
317
t.Fatalf("got %s, but want %s", string(actual), exp)
323
t.Run("invalid media type", func(t *testing.T) {
324
l, err := newMockLayer(types.DockerPluginConfig, nil)
328
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
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")
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")
347
t.Run("valid layers", func(t *testing.T) {
348
previousLayer, err := newMockLayer(types.DockerLayer, nil)
353
exp := "this is wasm binary"
354
lastLayer, err := newMockLayer(types.OCILayer, map[string][]byte{
355
"plugin.wasm": []byte(exp),
361
tCases := map[string]int{
363
"more than one layer": 1,
366
for name, numberOfPreviousLayers := range tCases {
367
t.Run(name, func(t *testing.T) {
369
for i := 0; i < numberOfPreviousLayers; i++ {
370
img, err = mutate.Append(img, mutate.Addendum{Layer: previousLayer})
376
img, err = mutate.Append(img, mutate.Addendum{Layer: lastLayer})
380
actual, err := extractOCIStandardImage(img)
382
t.Fatalf("extractOCIStandardImage failed: %v", err)
385
if string(actual) != exp {
386
t.Fatalf("got %s, but want %s", string(actual), exp)
392
t.Run("invalid media type", func(t *testing.T) {
393
l, err := newMockLayer(types.DockerLayer, nil)
397
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
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")
408
func newMockLayer(mediaType types.MediaType, contents map[string][]byte) (v1.Layer, error) {
410
hasher := sha256.New()
411
mw := io.MultiWriter(&b, hasher)
412
tw := tar.NewWriter(mw)
415
for filename, content := range contents {
416
if err := tw.WriteHeader(&tar.Header{
418
Size: int64(len(content)),
419
Typeflag: tar.TypeReg,
423
if _, err := io.CopyN(tw, bytes.NewReader(content), int64(len(content))); err != nil {
427
return partial.UncompressedToLayer(
432
Hex: hex.EncodeToString(hasher.Sum(make([]byte, 0, hasher.Size()))),
434
mediaType: mediaType,
439
type mockLayer struct {
442
mediaType types.MediaType
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
449
func (r *mockLayer) MediaType() (types.MediaType, error) { return r.mediaType, nil }
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")
458
configLayer, err := random.Layer(1000, "application/vnd.module.wasm.config.v1+json")
462
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: wasmLayer}, mutate.Addendum{Layer: configLayer})
467
// Extract the binary.
468
actual, err := extractOCIArtifactImage(img)
470
t.Fatalf("extractOCIArtifactImage failed: %v", err)
473
// Retrieve the wanted image content.
474
wantReader, err := wasmLayer.Compressed()
478
defer wantReader.Close()
479
want, err := io.ReadAll(wantReader)
484
if !bytes.Equal(actual, want) {
485
t.Errorf("extractOCIArtifactImage got %s, but want '%s'", string(actual), string(want))
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")
494
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: l})
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")
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")
510
img, err := mutate.Append(empty.Image, mutate.Addendum{Layer: layer}, mutate.Addendum{Layer: layer})
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")
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)
530
if err := tw.WriteHeader(&tar.Header{
532
Size: int64(len(exp)),
537
if _, err := io.WriteString(tw, exp); err != nil {
544
actual, err := extractWasmPluginBinary(buf)
546
t.Errorf("extractWasmPluginBinary failed: %v", err)
549
if string(actual) != exp {
550
t.Errorf("extractWasmPluginBinary got %v, but want %v", string(actual), exp)
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)
560
if err := tw.WriteHeader(&tar.Header{
561
Name: "./plugin.wasm",
562
Size: int64(len(exp)),
567
if _, err := io.WriteString(tw, exp); err != nil {
574
actual, err := extractWasmPluginBinary(buf)
576
t.Errorf("extractWasmPluginBinary failed: %v", err)
579
if string(actual) != exp {
580
t.Errorf("extractWasmPluginBinary got %v, but want %v", string(actual), exp)
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",
594
if _, err := tw.Write([]byte{1}); err != nil {
599
_, err := extractWasmPluginBinary(buf)
600
if err == nil || !strings.Contains(err.Error(), "not found") {
601
t.Errorf("extractWasmPluginBinary must fail with not found")
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)
613
t.Fatalf("Resolve() = %v", err)
615
got, err := auth.Authorization()
619
want := &authn.AuthConfig{
623
if !reflect.DeepEqual(got, want) {
624
t.Errorf("got %+v, want %+v", got, want)
628
func encode(user, pass string) string {
629
delimited := fmt.Sprintf("%s:%s", user, pass)
630
return base64.StdEncoding.EncodeToString([]byte(delimited))