...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package transport
16
17 import (
18 "encoding/json"
19 "fmt"
20 "io"
21 "net/http"
22 "strings"
23
24 "github.com/google/go-containerregistry/internal/redact"
25 )
26
27
28
29 type Error struct {
30 Errors []Diagnostic `json:"errors,omitempty"`
31
32 StatusCode int
33
34 Request *http.Request
35
36 rawBody string
37
38
39 temporary bool
40 }
41
42
43 var _ error = (*Error)(nil)
44
45
46 func (e *Error) Error() string {
47 prefix := ""
48 if e.Request != nil {
49 prefix = fmt.Sprintf("%s %s: ", e.Request.Method, redact.URL(e.Request.URL))
50 }
51 return prefix + e.responseErr()
52 }
53
54 func (e *Error) responseErr() string {
55 switch len(e.Errors) {
56 case 0:
57 if len(e.rawBody) == 0 {
58 if e.Request != nil && e.Request.Method == http.MethodHead {
59 return fmt.Sprintf("unexpected status code %d %s (HEAD responses have no body, use GET for details)", e.StatusCode, http.StatusText(e.StatusCode))
60 }
61 return fmt.Sprintf("unexpected status code %d %s", e.StatusCode, http.StatusText(e.StatusCode))
62 }
63 return fmt.Sprintf("unexpected status code %d %s: %s", e.StatusCode, http.StatusText(e.StatusCode), e.rawBody)
64 case 1:
65 return e.Errors[0].String()
66 default:
67 var errors []string
68 for _, d := range e.Errors {
69 errors = append(errors, d.String())
70 }
71 return fmt.Sprintf("multiple errors returned: %s",
72 strings.Join(errors, "; "))
73 }
74 }
75
76
77 func (e *Error) Temporary() bool {
78 if e.temporary {
79 return true
80 }
81
82 if len(e.Errors) == 0 {
83 _, ok := temporaryStatusCodes[e.StatusCode]
84 return ok
85 }
86 for _, d := range e.Errors {
87 if _, ok := temporaryErrorCodes[d.Code]; !ok {
88 return false
89 }
90 }
91 return true
92 }
93
94
95 type Diagnostic struct {
96 Code ErrorCode `json:"code"`
97 Message string `json:"message,omitempty"`
98 Detail any `json:"detail,omitempty"`
99 }
100
101
102 func (d Diagnostic) String() string {
103 msg := fmt.Sprintf("%s: %s", d.Code, d.Message)
104 if d.Detail != nil {
105 msg = fmt.Sprintf("%s; %v", msg, d.Detail)
106 }
107 return msg
108 }
109
110
111 type ErrorCode string
112
113
114
115 const (
116 BlobUnknownErrorCode ErrorCode = "BLOB_UNKNOWN"
117 BlobUploadInvalidErrorCode ErrorCode = "BLOB_UPLOAD_INVALID"
118 BlobUploadUnknownErrorCode ErrorCode = "BLOB_UPLOAD_UNKNOWN"
119 DigestInvalidErrorCode ErrorCode = "DIGEST_INVALID"
120 ManifestBlobUnknownErrorCode ErrorCode = "MANIFEST_BLOB_UNKNOWN"
121 ManifestInvalidErrorCode ErrorCode = "MANIFEST_INVALID"
122 ManifestUnknownErrorCode ErrorCode = "MANIFEST_UNKNOWN"
123 ManifestUnverifiedErrorCode ErrorCode = "MANIFEST_UNVERIFIED"
124 NameInvalidErrorCode ErrorCode = "NAME_INVALID"
125 NameUnknownErrorCode ErrorCode = "NAME_UNKNOWN"
126 SizeInvalidErrorCode ErrorCode = "SIZE_INVALID"
127 TagInvalidErrorCode ErrorCode = "TAG_INVALID"
128 UnauthorizedErrorCode ErrorCode = "UNAUTHORIZED"
129 DeniedErrorCode ErrorCode = "DENIED"
130 UnsupportedErrorCode ErrorCode = "UNSUPPORTED"
131 TooManyRequestsErrorCode ErrorCode = "TOOMANYREQUESTS"
132 UnknownErrorCode ErrorCode = "UNKNOWN"
133
134
135
136 UnavailableErrorCode ErrorCode = "UNAVAILABLE"
137 )
138
139
140 var temporaryErrorCodes = map[ErrorCode]struct{}{
141 BlobUploadInvalidErrorCode: {},
142 TooManyRequestsErrorCode: {},
143 UnknownErrorCode: {},
144 UnavailableErrorCode: {},
145 }
146
147 var temporaryStatusCodes = map[int]struct{}{
148 http.StatusRequestTimeout: {},
149 http.StatusInternalServerError: {},
150 http.StatusBadGateway: {},
151 http.StatusServiceUnavailable: {},
152 http.StatusGatewayTimeout: {},
153 }
154
155
156 func CheckError(resp *http.Response, codes ...int) error {
157 for _, code := range codes {
158 if resp.StatusCode == code {
159
160 return nil
161 }
162 }
163
164 b, err := io.ReadAll(resp.Body)
165 if err != nil {
166 return err
167 }
168
169 return makeError(resp, b)
170 }
171
172 func makeError(resp *http.Response, body []byte) *Error {
173
174 structuredError := &Error{}
175
176
177
178 _ = json.Unmarshal(body, structuredError)
179
180 structuredError.rawBody = string(body)
181 structuredError.StatusCode = resp.StatusCode
182 structuredError.Request = resp.Request
183
184 return structuredError
185 }
186
187 func retryError(resp *http.Response) error {
188 b, err := io.ReadAll(resp.Body)
189 if err != nil {
190 return err
191 }
192
193 rerr := makeError(resp, b)
194 rerr.temporary = true
195 return rerr
196 }
197
View as plain text