Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/image-builder-mk3/pkg/resolve/resolve_test.go
2500 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
//go:generate sh -c "mockgen -package resolve_test github.com/containerd/containerd/remotes Resolver,Fetcher > resolve_mock_test.go"
6
//go:generate leeway run components:update-license-header
7
8
package resolve_test
9
10
import (
11
"bytes"
12
"context"
13
"encoding/json"
14
"errors"
15
"io"
16
"testing"
17
18
"github.com/containerd/containerd/errdefs"
19
"github.com/containerd/containerd/remotes"
20
"github.com/gitpod-io/gitpod/image-builder/pkg/resolve"
21
"github.com/golang/mock/gomock"
22
"github.com/google/go-cmp/cmp"
23
"github.com/google/go-cmp/cmp/cmpopts"
24
"github.com/opencontainers/go-digest"
25
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
26
)
27
28
func TestStandaloneRefResolverResolve(t *testing.T) {
29
type Expectation struct {
30
Ref string
31
Error string
32
ErrorMatcher func(error) bool
33
}
34
type ResolveResponse struct {
35
Error error
36
ResolvedRef string
37
NormalisedRef string
38
MF *ociv1.Manifest
39
Index *ociv1.Index
40
}
41
tests := []struct {
42
Name string
43
ResolveResponse ResolveResponse
44
Expectation Expectation
45
Ref string
46
}{
47
{
48
Name: "basic resolve",
49
Ref: "docker.io/library/alpine:latest",
50
ResolveResponse: ResolveResponse{
51
MF: &ociv1.Manifest{Config: ociv1.Descriptor{MediaType: ociv1.MediaTypeImageManifest, Size: 1}},
52
},
53
Expectation: Expectation{
54
Ref: "docker.io/library/alpine@sha256:839b30716e23e3ac46ed28d6f9fc0f77e3e7121a9b4b2833e8fb6ec4ec89b3bd",
55
},
56
},
57
{
58
Name: "with-port",
59
Ref: "some-internal-registry.customer.com:5000/gitpod/base-image:latest",
60
ResolveResponse: ResolveResponse{
61
MF: &ociv1.Manifest{Config: ociv1.Descriptor{MediaType: ociv1.MediaTypeImageManifest, Size: 1}},
62
},
63
Expectation: Expectation{
64
Ref: "some-internal-registry.customer.com:5000/gitpod/base-image@sha256:839b30716e23e3ac46ed28d6f9fc0f77e3e7121a9b4b2833e8fb6ec4ec89b3bd",
65
},
66
},
67
{
68
Name: "with-broken-index",
69
Ref: "docker.io/library/alpine:latest",
70
ResolveResponse: ResolveResponse{
71
Index: &ociv1.Index{
72
Manifests: []ociv1.Descriptor{
73
{
74
MediaType: ociv1.MediaTypeImageManifest,
75
Digest: digest.FromString(""),
76
},
77
},
78
},
79
},
80
Expectation: Expectation{Error: "no manifest for platform linux-amd64 found"},
81
},
82
{
83
Name: "with-working-index",
84
Ref: "docker.io/library/alpine:latest",
85
ResolveResponse: ResolveResponse{
86
Index: &ociv1.Index{
87
Manifests: []ociv1.Descriptor{
88
{
89
MediaType: ociv1.MediaTypeImageManifest,
90
Digest: digest.FromString(""),
91
Platform: &ociv1.Platform{
92
Architecture: "amd64",
93
OS: "linux",
94
},
95
},
96
},
97
},
98
},
99
Expectation: Expectation{Ref: "docker.io/library/alpine@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
100
},
101
{
102
Name: "not authorized",
103
Ref: "registry-1.testing.gitpod-self-hosted.com:5000/gitpod/gitpod/workspace-full:latest",
104
ResolveResponse: ResolveResponse{
105
Error: errors.New("401 Unauthorized"),
106
},
107
Expectation: Expectation{
108
Error: resolve.ErrUnauthorized.Error(),
109
},
110
},
111
{
112
Name: "dockerhub rate limit",
113
Ref: "registry-1.docker.io:5000/gitpod/gitpod/workspace-full:latest-pulled-too-often",
114
ResolveResponse: ResolveResponse{
115
Error: errors.New("httpReadSeeker: failed open: unexpected status code https://registry-1.docker.io/v2/gitpod/workspace-full/manifests/sha256:279f925ad6395f11f6b60e63d7efa5c0b26a853c6052327efbe29bbcc0bafd6a: 429 Too Many Requests - Server message: toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit"),
116
},
117
Expectation: Expectation{
118
ErrorMatcher: resolve.TooManyRequestsMatcher,
119
},
120
},
121
{
122
Name: "not found",
123
Ref: "something.com/we/dont:find",
124
ResolveResponse: ResolveResponse{
125
Error: errdefs.ErrNotFound,
126
},
127
Expectation: Expectation{
128
Error: resolve.ErrNotFound.Error(),
129
},
130
},
131
{
132
Name: "not found with digest",
133
Ref: "something.com/we/dont/find@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
134
ResolveResponse: ResolveResponse{
135
Error: errdefs.ErrNotFound,
136
},
137
Expectation: Expectation{
138
Error: resolve.ErrNotFound.Error(),
139
},
140
},
141
{
142
Name: "familiar name",
143
Ref: "alpine:latest",
144
ResolveResponse: ResolveResponse{
145
NormalisedRef: "docker.io/library/alpine:latest",
146
Error: errdefs.ErrNotFound,
147
},
148
Expectation: Expectation{
149
Error: resolve.ErrNotFound.Error(),
150
},
151
},
152
}
153
154
for _, test := range tests {
155
t.Run(test.Name, func(t *testing.T) {
156
ctrl := gomock.NewController(t)
157
defer ctrl.Finish()
158
159
factory := func() remotes.Resolver {
160
resolver := NewMockResolver(ctrl)
161
162
resolvedRef := test.ResolveResponse.ResolvedRef
163
if resolvedRef == "" {
164
resolvedRef = test.Ref
165
}
166
normalizedRef := test.ResolveResponse.NormalisedRef
167
if normalizedRef == "" {
168
normalizedRef = test.Ref
169
}
170
var respBytes []byte
171
switch {
172
case test.ResolveResponse.MF != nil:
173
var err error
174
respBytes, err = json.Marshal(test.ResolveResponse.MF)
175
if err != nil {
176
t.Fatal(err)
177
}
178
179
case test.ResolveResponse.Index != nil:
180
var err error
181
respBytes, err = json.Marshal(test.ResolveResponse.Index)
182
if err != nil {
183
t.Fatal(err)
184
}
185
default:
186
}
187
if respBytes == nil {
188
resolver.EXPECT().Resolve(gomock.Any(), gomock.Eq(normalizedRef)).AnyTimes().Return("", ociv1.Descriptor{}, test.ResolveResponse.Error)
189
} else {
190
resolver.EXPECT().Resolve(gomock.Any(), gomock.Eq(normalizedRef)).AnyTimes().Return(resolvedRef, ociv1.Descriptor{
191
MediaType: ociv1.MediaTypeImageManifest,
192
Digest: digest.FromBytes(respBytes),
193
Size: int64(len(respBytes)),
194
}, nil)
195
fetcher := NewMockFetcher(ctrl)
196
fetcher.EXPECT().Fetch(gomock.Any(), gomock.Any()).AnyTimes().Return(io.NopCloser(bytes.NewReader(respBytes)), nil).AnyTimes()
197
resolver.EXPECT().Fetcher(gomock.Any(), gomock.Any()).AnyTimes().Return(fetcher, nil)
198
}
199
200
return resolver
201
}
202
203
sr := &resolve.StandaloneRefResolver{ResolverFactory: factory}
204
ref, err := sr.Resolve(context.Background(), test.Ref)
205
act := Expectation{Ref: ref}
206
if err != nil {
207
act.Error = err.Error()
208
}
209
210
// ErrorMatcher?
211
if err != nil && test.Expectation.ErrorMatcher != nil {
212
if test.Expectation.ErrorMatcher(err) {
213
test.Expectation.Error = act.Error
214
} else {
215
test.Expectation.Error = "ErrorMatcher failed"
216
}
217
}
218
219
if diff := cmp.Diff(test.Expectation, act, cmpopts.IgnoreFields(Expectation{}, "ErrorMatcher")); diff != "" {
220
t.Errorf("Resolve() mismatch (-want +got):\n%s", diff)
221
}
222
})
223
}
224
}
225
226