Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/registry-facade/pkg/registry/http_client_test.go
2499 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
package registry_test
6
7
import (
8
"context"
9
"errors"
10
"io"
11
"net/http"
12
"testing"
13
14
"github.com/containerd/containerd/remotes"
15
"github.com/containerd/containerd/remotes/docker"
16
"github.com/google/go-cmp/cmp"
17
"github.com/hashicorp/go-retryablehttp"
18
"github.com/opencontainers/go-digest"
19
20
"github.com/gitpod-io/gitpod/registry-facade/pkg/registry"
21
)
22
23
func TestRetryableFetcher(t *testing.T) {
24
type Expectation struct {
25
Digest string
26
Error string
27
}
28
29
tests := []struct {
30
Name string
31
Ref string
32
Retries bool
33
Expectation Expectation
34
}{
35
{
36
Name: "valid reference",
37
Ref: "docker.io/library/alpine@sha256:7580ece7963bfa863801466c0a488f11c86f85d9988051a9f9c68cb27f6b7872",
38
Retries: true,
39
Expectation: Expectation{Digest: "sha256:7580ece7963bfa863801466c0a488f11c86f85d9988051a9f9c68cb27f6b7872"},
40
},
41
{
42
Name: "invalid reference",
43
Ref: "docker.io/library/invalid-alpine",
44
Retries: false,
45
Expectation: Expectation{Error: "object required"},
46
},
47
}
48
49
for _, test := range tests {
50
t.Run(test.Name, func(t *testing.T) {
51
var retries int
52
resolverFactory := func() remotes.Resolver {
53
client := registry.NewRetryableHTTPClient(
54
registry.WithHTTPClient(
55
&http.Client{
56
Transport: &failFirstErrorRoundTrip{rt: http.DefaultTransport},
57
},
58
),
59
registry.WithRequestLogHook(func(logger retryablehttp.Logger, req *http.Request, attempt int) {
60
retries = attempt
61
}),
62
)
63
64
resolverOpts := docker.ResolverOptions{
65
Client: client,
66
}
67
68
return docker.NewResolver(resolverOpts)
69
}
70
71
ctx, cancel := context.WithCancel(context.Background())
72
defer cancel()
73
74
resolver := resolverFactory()
75
76
_, desc, err := resolver.Resolve(ctx, test.Ref)
77
if err != nil {
78
if !test.Retries && retries == 0 {
79
return
80
}
81
82
t.Fatalf("cannot download ref: %+q", err)
83
}
84
85
fetcher, err := resolver.Fetcher(context.Background(), test.Ref)
86
if err != nil {
87
t.Error(err)
88
}
89
90
reader, err := fetcher.Fetch(ctx, desc)
91
if err != nil {
92
t.Error(err)
93
}
94
95
var (
96
outcome Expectation
97
)
98
99
data, err := io.ReadAll(reader)
100
if err != nil {
101
outcome.Error = err.Error()
102
}
103
104
outcome.Digest = digest.FromBytes(data).String()
105
106
if diff := cmp.Diff(test.Expectation, outcome); diff != "" {
107
t.Errorf("Info() mismatch (-want +got):\n%s", diff)
108
}
109
})
110
}
111
}
112
113
type failFirstErrorRoundTrip struct {
114
rt http.RoundTripper
115
requests int
116
}
117
118
func (rt *failFirstErrorRoundTrip) RoundTrip(req *http.Request) (resp *http.Response, err error) {
119
if rt.requests == 0 {
120
rt.requests += 1
121
return nil, errors.New("connection reset by peer")
122
}
123
124
return rt.rt.RoundTrip(req)
125
}
126
127