...

Source file src/github.com/google/go-containerregistry/pkg/v1/remote/image_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  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"net/url"
    26  	"path"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/google/go-cmp/cmp"
    31  	"github.com/google/go-containerregistry/pkg/authn"
    32  	"github.com/google/go-containerregistry/pkg/logs"
    33  	"github.com/google/go-containerregistry/pkg/name"
    34  	"github.com/google/go-containerregistry/pkg/registry"
    35  	v1 "github.com/google/go-containerregistry/pkg/v1"
    36  	"github.com/google/go-containerregistry/pkg/v1/mutate"
    37  	"github.com/google/go-containerregistry/pkg/v1/partial"
    38  	"github.com/google/go-containerregistry/pkg/v1/random"
    39  	"github.com/google/go-containerregistry/pkg/v1/types"
    40  	"github.com/google/go-containerregistry/pkg/v1/validate"
    41  )
    42  
    43  const bogusDigest = "sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
    44  
    45  type withDigest interface {
    46  	Digest() (v1.Hash, error)
    47  }
    48  
    49  func mustDigest(t *testing.T, img withDigest) v1.Hash {
    50  	h, err := img.Digest()
    51  	if err != nil {
    52  		t.Fatalf("Digest() = %v", err)
    53  	}
    54  	return h
    55  }
    56  
    57  func mustManifest(t *testing.T, img v1.Image) *v1.Manifest {
    58  	m, err := img.Manifest()
    59  	if err != nil {
    60  		t.Fatalf("Manifest() = %v", err)
    61  	}
    62  	return m
    63  }
    64  
    65  func mustRawManifest(t *testing.T, img Taggable) []byte {
    66  	m, err := img.RawManifest()
    67  	if err != nil {
    68  		t.Fatalf("RawManifest() = %v", err)
    69  	}
    70  	return m
    71  }
    72  
    73  func mustRawConfigFile(t *testing.T, img v1.Image) []byte {
    74  	c, err := img.RawConfigFile()
    75  	if err != nil {
    76  		t.Fatalf("RawConfigFile() = %v", err)
    77  	}
    78  	return c
    79  }
    80  
    81  func randomImage(t *testing.T) v1.Image {
    82  	rnd, err := random.Image(1024, 1)
    83  	if err != nil {
    84  		t.Fatalf("random.Image() = %v", err)
    85  	}
    86  	return rnd
    87  }
    88  
    89  func newReference(host, repo, ref string) (name.Reference, error) {
    90  	tag, err := name.NewTag(fmt.Sprintf("%s/%s:%s", host, repo, ref), name.WeakValidation)
    91  	if err == nil {
    92  		return tag, nil
    93  	}
    94  	return name.NewDigest(fmt.Sprintf("%s/%s@%s", host, repo, ref), name.WeakValidation)
    95  }
    96  
    97  // TODO(jonjohnsonjr): Make this real.
    98  func TestMediaType(t *testing.T) {
    99  	img := remoteImage{}
   100  	got, err := img.MediaType()
   101  	if err != nil {
   102  		t.Fatalf("MediaType() = %v", err)
   103  	}
   104  	want := types.DockerManifestSchema2
   105  	if got != want {
   106  		t.Errorf("MediaType() = %v, want %v", got, want)
   107  	}
   108  }
   109  
   110  func TestRawManifestDigests(t *testing.T) {
   111  	img := randomImage(t)
   112  	expectedRepo := "foo/bar"
   113  
   114  	cases := []struct {
   115  		name          string
   116  		ref           string
   117  		responseBody  []byte
   118  		contentDigest string
   119  		wantErr       bool
   120  	}{{
   121  		name:          "normal pull, by tag",
   122  		ref:           "latest",
   123  		responseBody:  mustRawManifest(t, img),
   124  		contentDigest: mustDigest(t, img).String(),
   125  		wantErr:       false,
   126  	}, {
   127  		name:          "normal pull, by digest",
   128  		ref:           mustDigest(t, img).String(),
   129  		responseBody:  mustRawManifest(t, img),
   130  		contentDigest: mustDigest(t, img).String(),
   131  		wantErr:       false,
   132  	}, {
   133  		name:          "right content-digest, wrong body, by digest",
   134  		ref:           mustDigest(t, img).String(),
   135  		responseBody:  []byte("not even json"),
   136  		contentDigest: mustDigest(t, img).String(),
   137  		wantErr:       true,
   138  	}, {
   139  		name:          "right body, wrong content-digest, by tag",
   140  		ref:           "latest",
   141  		responseBody:  mustRawManifest(t, img),
   142  		contentDigest: bogusDigest,
   143  		wantErr:       false,
   144  	}, {
   145  		// NB: This succeeds! We don't care what the registry thinks.
   146  		name:          "right body, wrong content-digest, by digest",
   147  		ref:           mustDigest(t, img).String(),
   148  		responseBody:  mustRawManifest(t, img),
   149  		contentDigest: bogusDigest,
   150  		wantErr:       false,
   151  	}}
   152  
   153  	for _, tc := range cases {
   154  		t.Run(tc.name, func(t *testing.T) {
   155  			manifestPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, tc.ref)
   156  			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   157  				switch r.URL.Path {
   158  				case manifestPath:
   159  					if r.Method != http.MethodGet {
   160  						t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   161  					}
   162  
   163  					w.Header().Set("Docker-Content-Digest", tc.contentDigest)
   164  					w.Write(tc.responseBody)
   165  				default:
   166  					t.Fatalf("Unexpected path: %v", r.URL.Path)
   167  				}
   168  			}))
   169  			defer server.Close()
   170  			u, err := url.Parse(server.URL)
   171  			if err != nil {
   172  				t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   173  			}
   174  
   175  			ref, err := newReference(u.Host, expectedRepo, tc.ref)
   176  			if err != nil {
   177  				t.Fatalf("url.Parse(%v, %v, %v) = %v", u.Host, expectedRepo, tc.ref, err)
   178  			}
   179  
   180  			rmt := remoteImage{
   181  				ref: ref,
   182  				ctx: context.Background(),
   183  				fetcher: fetcher{
   184  					target: ref.Context(),
   185  					client: http.DefaultClient,
   186  				},
   187  			}
   188  
   189  			if _, err := rmt.RawManifest(); (err != nil) != tc.wantErr {
   190  				t.Errorf("RawManifest() wrong error: %v, want %v: %v\n", (err != nil), tc.wantErr, err)
   191  			}
   192  		})
   193  	}
   194  }
   195  
   196  func TestRawManifestNotFound(t *testing.T) {
   197  	expectedRepo := "foo/bar"
   198  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   199  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   200  		switch r.URL.Path {
   201  		case manifestPath:
   202  			if r.Method != http.MethodGet {
   203  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   204  			}
   205  			w.WriteHeader(http.StatusNotFound)
   206  		default:
   207  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   208  		}
   209  	}))
   210  	defer server.Close()
   211  	u, err := url.Parse(server.URL)
   212  	if err != nil {
   213  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   214  	}
   215  
   216  	ref := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   217  	img := remoteImage{
   218  		ref: ref,
   219  		ctx: context.Background(),
   220  		fetcher: fetcher{
   221  			target: ref.Context(),
   222  			client: http.DefaultClient,
   223  		},
   224  	}
   225  
   226  	if _, err := img.RawManifest(); err == nil {
   227  		t.Error("RawManifest() = nil; wanted error")
   228  	}
   229  }
   230  
   231  func TestRawConfigFileNotFound(t *testing.T) {
   232  	img := randomImage(t)
   233  	expectedRepo := "foo/bar"
   234  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   235  	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, img))
   236  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   237  		switch r.URL.Path {
   238  		case configPath:
   239  			if r.Method != http.MethodGet {
   240  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   241  			}
   242  			w.WriteHeader(http.StatusNotFound)
   243  		case manifestPath:
   244  			if r.Method != http.MethodGet {
   245  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   246  			}
   247  			w.Write(mustRawManifest(t, img))
   248  		default:
   249  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   250  		}
   251  	}))
   252  	defer server.Close()
   253  	u, err := url.Parse(server.URL)
   254  	if err != nil {
   255  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   256  	}
   257  
   258  	ref := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   259  	rmt := remoteImage{
   260  		ref: ref,
   261  		ctx: context.Background(),
   262  		fetcher: fetcher{
   263  			target: ref.Context(),
   264  			client: http.DefaultClient,
   265  		},
   266  	}
   267  
   268  	if _, err := rmt.RawConfigFile(); err == nil {
   269  		t.Error("RawConfigFile() = nil; wanted error")
   270  	}
   271  }
   272  
   273  func TestAcceptHeaders(t *testing.T) {
   274  	img := randomImage(t)
   275  	expectedRepo := "foo/bar"
   276  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   277  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   278  		switch r.URL.Path {
   279  		case manifestPath:
   280  			if r.Method != http.MethodGet {
   281  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   282  			}
   283  			wantAccept := strings.Join([]string{
   284  				string(types.DockerManifestSchema2),
   285  				string(types.OCIManifestSchema1),
   286  			}, ",")
   287  			if got, want := r.Header.Get("Accept"), wantAccept; got != want {
   288  				t.Errorf("Accept header; got %v, want %v", got, want)
   289  			}
   290  			w.Write(mustRawManifest(t, img))
   291  		default:
   292  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   293  		}
   294  	}))
   295  	defer server.Close()
   296  	u, err := url.Parse(server.URL)
   297  	if err != nil {
   298  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   299  	}
   300  	ref := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   301  	rmt := &remoteImage{
   302  		ref: ref,
   303  		ctx: context.Background(),
   304  		fetcher: fetcher{
   305  			target: ref.Context(),
   306  			client: http.DefaultClient,
   307  		},
   308  	}
   309  	manifest, err := rmt.RawManifest()
   310  	if err != nil {
   311  		t.Errorf("RawManifest() = %v", err)
   312  	}
   313  	if got, want := manifest, mustRawManifest(t, img); !bytes.Equal(got, want) {
   314  		t.Errorf("RawManifest() = %v, want %v", got, want)
   315  	}
   316  }
   317  
   318  func TestImage(t *testing.T) {
   319  	img := randomImage(t)
   320  	expectedRepo := "foo/bar"
   321  	layerDigest := mustManifest(t, img).Layers[0].Digest
   322  	layerSize := mustManifest(t, img).Layers[0].Size
   323  	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, img))
   324  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   325  	layerPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, layerDigest)
   326  	manifestReqCount := 0
   327  
   328  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   329  		switch r.URL.Path {
   330  		case "/v2/":
   331  			w.WriteHeader(http.StatusOK)
   332  		case configPath:
   333  			if r.Method != http.MethodGet {
   334  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   335  			}
   336  			w.Write(mustRawConfigFile(t, img))
   337  		case manifestPath:
   338  			manifestReqCount++
   339  			if r.Method != http.MethodGet {
   340  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   341  			}
   342  			w.Write(mustRawManifest(t, img))
   343  		case layerPath:
   344  			t.Fatalf("BlobSize should not make any request: %v", r.URL.Path)
   345  		default:
   346  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   347  		}
   348  	}))
   349  	defer server.Close()
   350  	u, err := url.Parse(server.URL)
   351  	if err != nil {
   352  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   353  	}
   354  
   355  	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   356  	rmt, err := Image(tag, WithTransport(http.DefaultTransport), WithAuthFromKeychain(authn.DefaultKeychain))
   357  	if err != nil {
   358  		t.Errorf("Image() = %v", err)
   359  	}
   360  
   361  	if got, want := mustRawManifest(t, rmt), mustRawManifest(t, img); !bytes.Equal(got, want) {
   362  		t.Errorf("RawManifest() = %v, want %v", got, want)
   363  	}
   364  	if got, want := mustRawConfigFile(t, rmt), mustRawConfigFile(t, img); !bytes.Equal(got, want) {
   365  		t.Errorf("RawConfigFile() = %v, want %v", got, want)
   366  	}
   367  	// Make sure caching the manifest works.
   368  	if manifestReqCount != 1 {
   369  		t.Errorf("RawManifest made %v requests, expected 1", manifestReqCount)
   370  	}
   371  
   372  	l, err := rmt.LayerByDigest(layerDigest)
   373  	if err != nil {
   374  		t.Errorf("LayerByDigest() = %v", err)
   375  	}
   376  	// BlobSize should not HEAD.
   377  	size, err := l.Size()
   378  	if err != nil {
   379  		t.Errorf("BlobSize() = %v", err)
   380  	}
   381  	if got, want := size, layerSize; want != got {
   382  		t.Errorf("BlobSize() = %v want %v", got, want)
   383  	}
   384  }
   385  
   386  func TestPullingManifestList(t *testing.T) {
   387  	idx := randomIndex(t)
   388  	expectedRepo := "foo/bar"
   389  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   390  	childDigest := mustIndexManifest(t, idx).Manifests[1].Digest
   391  	child := mustChild(t, idx, childDigest)
   392  	childPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, childDigest)
   393  	fakePlatformChildDigest := mustIndexManifest(t, idx).Manifests[0].Digest
   394  	fakePlatformChild := mustChild(t, idx, fakePlatformChildDigest)
   395  	fakePlatformChildPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, fakePlatformChildDigest)
   396  	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, child))
   397  
   398  	fakePlatform := v1.Platform{
   399  		Architecture: "not-real-arch",
   400  		OS:           "not-real-os",
   401  	}
   402  
   403  	// Rewrite the index to make sure the desired platform matches the second child.
   404  	manifest, err := idx.IndexManifest()
   405  	if err != nil {
   406  		t.Fatal(err)
   407  	}
   408  	// Make sure the first manifest doesn't match.
   409  	manifest.Manifests[0].Platform = &fakePlatform
   410  	// Make sure the second manifest does.
   411  	manifest.Manifests[1].Platform = &defaultPlatform
   412  	// Do short-circuiting via Data.
   413  	manifest.Manifests[1].Data = mustRawManifest(t, child)
   414  	rawManifest, err := json.Marshal(manifest)
   415  	if err != nil {
   416  		t.Fatal(err)
   417  	}
   418  
   419  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   420  		switch r.URL.Path {
   421  		case "/v2/":
   422  			w.WriteHeader(http.StatusOK)
   423  		case manifestPath:
   424  			if r.Method != http.MethodGet {
   425  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   426  			}
   427  			w.Header().Set("Content-Type", string(mustMediaType(t, idx)))
   428  			w.Write(rawManifest)
   429  		case childPath:
   430  			if r.Method != http.MethodGet {
   431  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   432  			}
   433  			w.Write(mustRawManifest(t, child))
   434  		case configPath:
   435  			if r.Method != http.MethodGet {
   436  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   437  			}
   438  			w.Write(mustRawConfigFile(t, child))
   439  		case fakePlatformChildPath:
   440  			if r.Method != http.MethodGet {
   441  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   442  			}
   443  			w.Write(mustRawManifest(t, fakePlatformChild))
   444  		default:
   445  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   446  		}
   447  	}))
   448  	defer server.Close()
   449  	u, err := url.Parse(server.URL)
   450  	if err != nil {
   451  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   452  	}
   453  
   454  	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   455  	rmtChild, err := Image(tag)
   456  	if err != nil {
   457  		t.Errorf("Image() = %v", err)
   458  	}
   459  
   460  	// Test that child works as expected.
   461  	if got, want := mustRawManifest(t, rmtChild), mustRawManifest(t, child); !bytes.Equal(got, want) {
   462  		t.Errorf("RawManifest() = %v, want %v", string(got), string(want))
   463  	}
   464  	if got, want := mustRawConfigFile(t, rmtChild), mustRawConfigFile(t, child); !bytes.Equal(got, want) {
   465  		t.Errorf("RawConfigFile() = %v, want %v", got, want)
   466  	}
   467  
   468  	// Make sure we can roundtrip platform info via Descriptor.
   469  	img, err := Image(tag, WithPlatform(fakePlatform))
   470  	if err != nil {
   471  		t.Fatal(err)
   472  	}
   473  	desc, err := partial.Descriptor(img)
   474  	if err != nil {
   475  		t.Fatal(err)
   476  	}
   477  
   478  	if diff := cmp.Diff(*desc.Platform, fakePlatform); diff != "" {
   479  		t.Errorf("Desciptor() (-want +got) = %v", diff)
   480  	}
   481  }
   482  
   483  func TestPullingManifestListNoMatch(t *testing.T) {
   484  	idx := randomIndex(t)
   485  	expectedRepo := "foo/bar"
   486  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   487  	childDigest := mustIndexManifest(t, idx).Manifests[1].Digest
   488  	child := mustChild(t, idx, childDigest)
   489  	childPath := fmt.Sprintf("/v2/%s/manifests/%s", expectedRepo, childDigest)
   490  	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, child))
   491  
   492  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   493  		switch r.URL.Path {
   494  		case "/v2/":
   495  			w.WriteHeader(http.StatusOK)
   496  		case manifestPath:
   497  			if r.Method != http.MethodGet {
   498  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   499  			}
   500  			w.Header().Set("Content-Type", string(mustMediaType(t, idx)))
   501  			w.Write(mustRawManifest(t, idx))
   502  		case childPath:
   503  			if r.Method != http.MethodGet {
   504  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   505  			}
   506  			w.Write(mustRawManifest(t, child))
   507  		case configPath:
   508  			if r.Method != http.MethodGet {
   509  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   510  			}
   511  			w.Write(mustRawConfigFile(t, child))
   512  		default:
   513  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   514  		}
   515  	}))
   516  	defer server.Close()
   517  	u, err := url.Parse(server.URL)
   518  	if err != nil {
   519  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   520  	}
   521  	platform := v1.Platform{
   522  		Architecture: "not-real-arch",
   523  		OS:           "not-real-os",
   524  	}
   525  	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   526  	if _, err := Image(tag, WithPlatform(platform)); err == nil {
   527  		t.Errorf("Image succeeded, wanted err")
   528  	}
   529  }
   530  
   531  func TestValidate(t *testing.T) {
   532  	img, err := random.Image(1024, 5)
   533  	if err != nil {
   534  		t.Fatal(err)
   535  	}
   536  
   537  	s := httptest.NewServer(registry.New())
   538  	defer s.Close()
   539  	u, err := url.Parse(s.URL)
   540  	if err != nil {
   541  		t.Fatal(err)
   542  	}
   543  
   544  	tag, err := name.NewTag(u.Host + "/foo/bar")
   545  	if err != nil {
   546  		t.Fatal(err)
   547  	}
   548  
   549  	if err := Write(tag, img); err != nil {
   550  		t.Fatal(err)
   551  	}
   552  
   553  	img, err = Image(tag)
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  
   558  	if err := validate.Image(img); err != nil {
   559  		t.Errorf("failed to validate remote.Image: %v", err)
   560  	}
   561  }
   562  
   563  func TestPullingForeignLayer(t *testing.T) {
   564  	// For that sweet, sweet coverage in options.
   565  	var b bytes.Buffer
   566  	logs.Debug.SetOutput(&b)
   567  
   568  	img := randomImage(t)
   569  	expectedRepo := "foo/bar"
   570  	foreignPath := "/foreign/path"
   571  
   572  	foreignLayer, err := random.Layer(1024, types.DockerForeignLayer)
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  
   577  	foreignServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   578  		switch r.URL.Path {
   579  		case foreignPath:
   580  			compressed, err := foreignLayer.Compressed()
   581  			if err != nil {
   582  				t.Fatal(err)
   583  			}
   584  			if _, err := io.Copy(w, compressed); err != nil {
   585  				t.Fatal(err)
   586  			}
   587  			w.WriteHeader(http.StatusOK)
   588  		default:
   589  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   590  		}
   591  	}))
   592  	defer foreignServer.Close()
   593  	fu, err := url.Parse(foreignServer.URL)
   594  	if err != nil {
   595  		t.Fatalf("url.Parse(%v) = %v", foreignServer.URL, err)
   596  	}
   597  
   598  	img, err = mutate.Append(img, mutate.Addendum{
   599  		Layer: foreignLayer,
   600  		URLs: []string{
   601  			"http://" + path.Join(fu.Host, foreignPath),
   602  		},
   603  	})
   604  	if err != nil {
   605  		t.Fatal(err)
   606  	}
   607  
   608  	// Set up a fake registry that will respond 404 to the foreign layer,
   609  	// but serve everything else correctly.
   610  	configPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, mustConfigName(t, img))
   611  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   612  	foreignLayerDigest := mustManifest(t, img).Layers[1].Digest
   613  	foreignLayerPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, foreignLayerDigest)
   614  	layerDigest := mustManifest(t, img).Layers[0].Digest
   615  	layerPath := fmt.Sprintf("/v2/%s/blobs/%s", expectedRepo, layerDigest)
   616  
   617  	layer, err := img.LayerByDigest(layerDigest)
   618  	if err != nil {
   619  		t.Fatal(err)
   620  	}
   621  
   622  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   623  		switch r.URL.Path {
   624  		case "/v2/":
   625  			w.WriteHeader(http.StatusOK)
   626  		case configPath:
   627  			if r.Method != http.MethodGet {
   628  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   629  			}
   630  			w.Write(mustRawConfigFile(t, img))
   631  		case manifestPath:
   632  			if r.Method != http.MethodGet {
   633  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   634  			}
   635  			w.Write(mustRawManifest(t, img))
   636  		case layerPath:
   637  			compressed, err := layer.Compressed()
   638  			if err != nil {
   639  				t.Fatal(err)
   640  			}
   641  			if _, err := io.Copy(w, compressed); err != nil {
   642  				t.Fatal(err)
   643  			}
   644  			w.WriteHeader(http.StatusOK)
   645  		case foreignLayerPath:
   646  			// Not here!
   647  			w.WriteHeader(http.StatusNotFound)
   648  		default:
   649  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   650  		}
   651  	}))
   652  	defer server.Close()
   653  	u, err := url.Parse(server.URL)
   654  	if err != nil {
   655  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   656  	}
   657  
   658  	// Pull from the registry and ensure that everything Validates; i.e. that
   659  	// we pull the layer from the foreignServer.
   660  	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   661  	rmt, err := Image(tag, WithTransport(http.DefaultTransport))
   662  	if err != nil {
   663  		t.Errorf("Image() = %v", err)
   664  	}
   665  
   666  	if err := validate.Image(rmt); err != nil {
   667  		t.Errorf("failed to validate foreign image: %v", err)
   668  	}
   669  
   670  	// Set up a fake registry and write what we pulled to it.
   671  	// This ensures we get coverage for the remoteLayer.MediaType path.
   672  	s := httptest.NewServer(registry.New())
   673  	defer s.Close()
   674  	u, err = url.Parse(s.URL)
   675  	if err != nil {
   676  		t.Fatal(err)
   677  	}
   678  	dst := fmt.Sprintf("%s/test/foreign/upload", u.Host)
   679  	ref, err := name.ParseReference(dst)
   680  	if err != nil {
   681  		t.Fatal(err)
   682  	}
   683  
   684  	if err := Write(ref, rmt); err != nil {
   685  		t.Errorf("failed to Write: %v", err)
   686  	}
   687  }
   688  
   689  func TestData(t *testing.T) {
   690  	img := randomImage(t)
   691  	manifest, err := img.Manifest()
   692  	if err != nil {
   693  		t.Fatal(err)
   694  	}
   695  	layers, err := img.Layers()
   696  	if err != nil {
   697  		t.Fatal(err)
   698  	}
   699  	cb, err := img.RawConfigFile()
   700  	if err != nil {
   701  		t.Fatal(err)
   702  	}
   703  
   704  	manifest.Config.Data = cb
   705  	rc, err := layers[0].Compressed()
   706  	if err != nil {
   707  		t.Fatal(err)
   708  	}
   709  	lb, err := io.ReadAll(rc)
   710  	if err != nil {
   711  		t.Fatal(err)
   712  	}
   713  	manifest.Layers[0].Data = lb
   714  	rawManifest, err := json.Marshal(manifest)
   715  	if err != nil {
   716  		t.Fatal(err)
   717  	}
   718  
   719  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   720  		switch r.URL.Path {
   721  		case "/v2/":
   722  			w.WriteHeader(http.StatusOK)
   723  		case "/v2/test/manifests/latest":
   724  			if r.Method != http.MethodGet {
   725  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   726  			}
   727  			w.Write(rawManifest)
   728  		default:
   729  			// explode if we try to read blob or config
   730  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   731  		}
   732  	}))
   733  	defer server.Close()
   734  	u, err := url.Parse(server.URL)
   735  	if err != nil {
   736  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   737  	}
   738  	ref, err := newReference(u.Host, "test", "latest")
   739  	if err != nil {
   740  		t.Fatal(err)
   741  	}
   742  	rmt, err := Image(ref)
   743  	if err != nil {
   744  		t.Fatal(err)
   745  	}
   746  	if err := validate.Image(rmt); err != nil {
   747  		t.Fatal(err)
   748  	}
   749  }
   750  

View as plain text