Skip to content

Commit 7ae65ce

Browse files
authored
[image-builder] Detect "429 - Too Many Request" and bubble up as "Unavailable" (#20112)
1 parent cd00bab commit 7ae65ce

File tree

3 files changed

+35
-3
lines changed

3 files changed

+35
-3
lines changed

components/image-builder-mk3/pkg/orchestrator/orchestrator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,9 @@ func (o *Orchestrator) getAbsoluteImageRef(ctx context.Context, ref string, allo
624624
}
625625
return "", status.Error(codes.Unauthenticated, "cannot resolve image")
626626
}
627+
if resolve.TooManyRequestsMatcher(err) {
628+
return "", status.Errorf(codes.Unavailable, "upstream registry responds with 'too many request': %v", err)
629+
}
627630
if err != nil {
628631
return "", status.Errorf(codes.Internal, "cannot resolve image: %v", err)
629632
}

components/image-builder-mk3/pkg/resolve/resolve.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ var (
3232

3333
// ErrNotFound is returned when we're not authorized to return the reference
3434
ErrUnauthorized = xerrors.Errorf("not authorized")
35+
36+
// TooManyRequestsMatcher returns true if an error is a code 429 "Too Many Requests" error
37+
TooManyRequestsMatcher = func(err error) bool {
38+
if err == nil {
39+
return false
40+
}
41+
return strings.Contains(err.Error(), "429 Too Many Requests")
42+
}
3543
)
3644

3745
// StandaloneRefResolver can resolve image references without a Docker daemon

components/image-builder-mk3/pkg/resolve/resolve_test.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ import (
2020
"github.com/gitpod-io/gitpod/image-builder/pkg/resolve"
2121
"github.com/golang/mock/gomock"
2222
"github.com/google/go-cmp/cmp"
23+
"github.com/google/go-cmp/cmp/cmpopts"
2324
"github.com/opencontainers/go-digest"
2425
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
2526
)
2627

2728
func TestStandaloneRefResolverResolve(t *testing.T) {
2829
type Expectation struct {
29-
Ref string
30-
Error string
30+
Ref string
31+
Error string
32+
ErrorMatcher func(error) bool
3133
}
3234
type ResolveResponse struct {
3335
Error error
@@ -106,6 +108,16 @@ func TestStandaloneRefResolverResolve(t *testing.T) {
106108
Error: resolve.ErrUnauthorized.Error(),
107109
},
108110
},
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+
},
109121
{
110122
Name: "not found",
111123
Ref: "something.com/we/dont:find",
@@ -195,7 +207,16 @@ func TestStandaloneRefResolverResolve(t *testing.T) {
195207
act.Error = err.Error()
196208
}
197209

198-
if diff := cmp.Diff(test.Expectation, act); diff != "" {
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 != "" {
199220
t.Errorf("Resolve() mismatch (-want +got):\n%s", diff)
200221
}
201222
})

0 commit comments

Comments
 (0)