...

Source file src/github.com/google/go-containerregistry/pkg/v1/remote/descriptor_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  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"net/url"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	v1 "github.com/google/go-containerregistry/pkg/v1"
    30  	"github.com/google/go-containerregistry/pkg/v1/types"
    31  )
    32  
    33  var fakeDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
    34  
    35  func TestGetSchema1(t *testing.T) {
    36  	expectedRepo := "foo/bar"
    37  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
    38  
    39  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    40  		switch r.URL.Path {
    41  		case "/v2/":
    42  			w.WriteHeader(http.StatusOK)
    43  		case manifestPath:
    44  			if r.Method != http.MethodGet {
    45  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
    46  			}
    47  			w.Header().Set("Content-Type", string(types.DockerManifestSchema1Signed))
    48  			w.Header().Set("Docker-Content-Digest", fakeDigest)
    49  			w.Write([]byte("doesn't matter"))
    50  		default:
    51  			t.Fatalf("Unexpected path: %v", r.URL.Path)
    52  		}
    53  	}))
    54  	defer server.Close()
    55  	u, err := url.Parse(server.URL)
    56  	if err != nil {
    57  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
    58  	}
    59  
    60  	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
    61  
    62  	// Get should succeed even for invalid json. We don't parse the response.
    63  	desc, err := Get(tag)
    64  	if err != nil {
    65  		t.Fatalf("Get(%s) = %v", tag, err)
    66  	}
    67  
    68  	if desc.Digest.String() != fakeDigest {
    69  		t.Errorf("Descriptor.Digest = %q, expected %q", desc.Digest, fakeDigest)
    70  	}
    71  
    72  	want := `unsupported MediaType: "application/vnd.docker.distribution.manifest.v1+prettyjws", see https://github.com/google/go-containerregistry/issues/377`
    73  	// Should fail based on media type.
    74  	if _, err := desc.Image(); err != nil {
    75  		if !errors.Is(err, ErrSchema1) {
    76  			t.Errorf("Image() = %v, expected remote.ErrSchema1", err)
    77  		}
    78  		if diff := cmp.Diff(want, err.Error()); diff != "" {
    79  			t.Errorf("Image() error message (-want +got) = %v", diff)
    80  		}
    81  	} else {
    82  		t.Errorf("Image() = %v, expected err", err)
    83  	}
    84  
    85  	// Should fail based on media type.
    86  	if _, err := desc.ImageIndex(); err != nil {
    87  		if !errors.Is(err, ErrSchema1) {
    88  			t.Errorf("ImageImage() = %v, expected remote.ErrSchema1", err)
    89  		}
    90  	} else {
    91  		t.Errorf("ImageIndex() = %v, expected err", err)
    92  	}
    93  }
    94  
    95  func TestGetImageAsIndex(t *testing.T) {
    96  	expectedRepo := "foo/bar"
    97  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
    98  
    99  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   100  		switch r.URL.Path {
   101  		case "/v2/":
   102  			w.WriteHeader(http.StatusOK)
   103  		case manifestPath:
   104  			if r.Method != http.MethodGet {
   105  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodGet)
   106  			}
   107  			w.Header().Set("Content-Type", string(types.DockerManifestSchema2))
   108  			w.Write([]byte("doesn't matter"))
   109  		default:
   110  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   111  		}
   112  	}))
   113  	defer server.Close()
   114  	u, err := url.Parse(server.URL)
   115  	if err != nil {
   116  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   117  	}
   118  
   119  	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   120  
   121  	// Get should succeed even for invalid json. We don't parse the response.
   122  	desc, err := Get(tag)
   123  	if err != nil {
   124  		t.Fatalf("Get(%s) = %v", tag, err)
   125  	}
   126  
   127  	// Should fail based on media type.
   128  	if _, err := desc.ImageIndex(); err == nil {
   129  		t.Errorf("ImageIndex() = %v, expected err", err)
   130  	}
   131  }
   132  
   133  func TestHeadSchema1(t *testing.T) {
   134  	expectedRepo := "foo/bar"
   135  	mediaType := types.DockerManifestSchema1Signed
   136  	response := []byte("doesn't matter")
   137  	manifestPath := fmt.Sprintf("/v2/%s/manifests/latest", expectedRepo)
   138  
   139  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   140  		switch r.URL.Path {
   141  		case "/v2/":
   142  			w.WriteHeader(http.StatusOK)
   143  		case manifestPath:
   144  			if r.Method != http.MethodHead {
   145  				t.Errorf("Method; got %v, want %v", r.Method, http.MethodHead)
   146  			}
   147  			w.Header().Set("Content-Type", string(mediaType))
   148  			w.Header().Set("Content-Length", strconv.Itoa(len(response)))
   149  			w.Header().Set("Docker-Content-Digest", fakeDigest)
   150  			w.Write(response)
   151  		default:
   152  			t.Fatalf("Unexpected path: %v", r.URL.Path)
   153  		}
   154  	}))
   155  	defer server.Close()
   156  	u, err := url.Parse(server.URL)
   157  	if err != nil {
   158  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   159  	}
   160  
   161  	tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, expectedRepo))
   162  
   163  	// Head should succeed even for invalid json. We don't parse the response.
   164  	desc, err := Head(tag)
   165  	if err != nil {
   166  		t.Fatalf("Head(%s) = %v", tag, err)
   167  	}
   168  
   169  	if desc.MediaType != mediaType {
   170  		t.Errorf("Descriptor.MediaType = %q, expected %q", desc.MediaType, mediaType)
   171  	}
   172  
   173  	if desc.Digest.String() != fakeDigest {
   174  		t.Errorf("Descriptor.Digest = %q, expected %q", desc.Digest, fakeDigest)
   175  	}
   176  
   177  	if desc.Size != int64(len(response)) {
   178  		t.Errorf("Descriptor.Size = %q, expected %q", desc.Size, len(response))
   179  	}
   180  }
   181  
   182  // TestHead_MissingHeaders tests that HEAD responses missing necessary headers
   183  // result in errors.
   184  func TestHead_MissingHeaders(t *testing.T) {
   185  	missingType := "missing-type"
   186  	missingLength := "missing-length"
   187  	missingDigest := "missing-digest"
   188  
   189  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   190  		if r.URL.Path == "/v2/" {
   191  			w.WriteHeader(http.StatusOK)
   192  			return
   193  		}
   194  		if r.Method != http.MethodHead {
   195  			t.Errorf("Method; got %v, want %v", r.Method, http.MethodHead)
   196  		}
   197  		if !strings.Contains(r.URL.Path, missingType) {
   198  			w.Header().Set("Content-Type", "My-Media-Type")
   199  		}
   200  		if !strings.Contains(r.URL.Path, missingLength) {
   201  			w.Header().Set("Content-Length", "10")
   202  		}
   203  		if !strings.Contains(r.URL.Path, missingDigest) {
   204  			w.Header().Set("Docker-Content-Digest", fakeDigest)
   205  		}
   206  	}))
   207  	defer server.Close()
   208  	u, err := url.Parse(server.URL)
   209  	if err != nil {
   210  		t.Fatalf("url.Parse(%v) = %v", server.URL, err)
   211  	}
   212  
   213  	for _, repo := range []string{missingType, missingLength, missingDigest} {
   214  		tag := mustNewTag(t, fmt.Sprintf("%s/%s:latest", u.Host, repo))
   215  		if _, err := Head(tag); err == nil {
   216  			t.Errorf("Head(%q): expected error, got nil", tag)
   217  		}
   218  	}
   219  }
   220  
   221  // TestRedactFetchBlob tests that a request to fetchBlob that gets redirected
   222  // to a URL that contains sensitive information has that information redacted
   223  // if the subsequent request fails.
   224  func TestRedactFetchBlob(t *testing.T) {
   225  	ctx := context.Background()
   226  	f := fetcher{
   227  		target: mustNewTag(t, "original.com/repo:latest").Context(),
   228  		client: &http.Client{
   229  			Transport: errTransport{},
   230  		},
   231  	}
   232  	h, err := v1.NewHash(fakeDigest)
   233  	if err != nil {
   234  		t.Fatal("NewHash:", err)
   235  	}
   236  	if _, err := f.fetchBlob(ctx, 0, h); err == nil {
   237  		t.Fatalf("fetchBlob: expected error, got nil")
   238  	} else if !strings.Contains(err.Error(), "access_token=REDACTED") {
   239  		t.Fatalf("fetchBlob: expected error to contain redacted access token, got %v", err)
   240  	}
   241  }
   242  
   243  type errTransport struct{}
   244  
   245  func (errTransport) RoundTrip(req *http.Request) (*http.Response, error) {
   246  	// This simulates a registry that returns a redirect upon the first
   247  	// request, and then returns an error upon subsequent requests. This helps
   248  	// test whether error redaction takes into account URLs in error messasges
   249  	// that are not the original request URL.
   250  	if req.URL.Host == "original.com" {
   251  		return &http.Response{
   252  			StatusCode: http.StatusSeeOther,
   253  			Header:     http.Header{"Location": []string{"https://redirected.com?access_token=SECRET"}},
   254  		}, nil
   255  	}
   256  	return nil, fmt.Errorf("error reaching %s", req.URL.String())
   257  }
   258  

View as plain text