1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
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
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
122 desc, err := Get(tag)
123 if err != nil {
124 t.Fatalf("Get(%s) = %v", tag, err)
125 }
126
127
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
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
183
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
222
223
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
247
248
249
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