...

Source file src/github.com/google/go-containerregistry/pkg/v1/remote/index_test.go

Documentation: github.com/google/go-containerregistry/pkg/v1/remote

     1  // Copyright 2018 Google LLC All Rights Reserved.
     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 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  		// NB: This succeeds! We don't care what the registry thinks.
   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  	// Test that index works as expected.
   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  	// Make sure caching the manifest works for index.
   225  	if manifestReqCount != 1 {
   226  		t.Errorf("RawManifest made %v requests, expected 1", manifestReqCount)
   227  	}
   228  
   229  	// Test that child works as expected.
   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  	// Make sure caching the manifest works for child.
   237  	if childReqCount != 1 {
   238  		t.Errorf("RawManifest made %v requests, expected 1", childReqCount)
   239  	}
   240  
   241  	// Try to fetch bogus children.
   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  // TestMatchesPlatform runs test cases on the matchesPlatform function which verifies
   253  // whether the given platform can run on the required platform by checking the
   254  // compatibility of architecture, OS, OS version, OS features, variant and features.
   255  func TestMatchesPlatform(t *testing.T) {
   256  	t.Parallel()
   257  	tests := []struct {
   258  		// want is the expected return value from matchesPlatform
   259  		// when the given platform is 'given' and the required platform is 'required'.
   260  		given    v1.Platform
   261  		required v1.Platform
   262  		want     bool
   263  	}{{ // The given & required platforms are identical. matchesPlatform expected to return true.
   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  		{ // OS and Architecture must exactly match. matchesPlatform expected to return false.
   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  		{ // OS version must exactly match
   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  		{ // OS Features must exactly match. matchesPlatform expected to return false.
   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  		{ // Variant must exactly match. matchesPlatform expected to return false.
   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  		{ // OS must exactly match, and is case sensative. matchesPlatform expected to return false.
   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  		{ // OSVersion and Variant are specified in given but not in required.
   378  			// matchesPlatform expected to return true.
   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  		{ // Ensure the optional field OSVersion & Variant match exactly if specified as required.
   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  		{ // Checking subset validity when required less features than given features.
   417  			// matchesPlatform expected to return true.
   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  		{ // Checking subset validity when required features are subset of given features.
   437  			// matchesPlatform expected to return true.
   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  		{ // Checking subset validity when some required features is not subset of given features.
   457  			// matchesPlatform expected to return false.
   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  		{ // Checking subset validity when OS features not required,
   477  			// and required features is indeed a subset of given features.
   478  			// matchesPlatform expected to return true.
   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