Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -7,14 +7,6 @@ import ( |
| 7 | 7 |
"github.com/docker/docker/errdefs" |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 |
-type notFoundError string |
|
| 11 |
- |
|
| 12 |
-func (e notFoundError) Error() string {
|
|
| 13 |
- return string(e) |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-func (notFoundError) NotFound() {}
|
|
| 17 |
- |
|
| 18 | 10 |
func translateV2AuthError(err error) error {
|
| 19 | 11 |
switch e := err.(type) {
|
| 20 | 12 |
case *url.Error: |
| ... | ... |
@@ -1,10 +1,8 @@ |
| 1 | 1 |
package registry // import "github.com/docker/docker/registry" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 | 4 |
"net/http" |
| 6 | 5 |
"net/http/httputil" |
| 7 |
- "net/url" |
|
| 8 | 6 |
"os" |
| 9 | 7 |
"strings" |
| 10 | 8 |
"testing" |
| ... | ... |
@@ -17,15 +15,6 @@ import ( |
| 17 | 17 |
"gotest.tools/v3/skip" |
| 18 | 18 |
) |
| 19 | 19 |
|
| 20 |
-var ( |
|
| 21 |
- token = []string{"fake-token"}
|
|
| 22 |
-) |
|
| 23 |
- |
|
| 24 |
-const ( |
|
| 25 |
- imageID = "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d" |
|
| 26 |
- REPO = "foo42/bar" |
|
| 27 |
-) |
|
| 28 |
- |
|
| 29 | 20 |
func spawnTestRegistrySession(t *testing.T) *Session {
|
| 30 | 21 |
authConfig := &types.AuthConfig{}
|
| 31 | 22 |
endpoint, err := NewV1Endpoint(makeIndex("/v1/"), "", nil)
|
| ... | ... |
@@ -50,7 +39,7 @@ func spawnTestRegistrySession(t *testing.T) *Session {
|
| 50 | 50 |
// Because we know that the client's transport is an `*authTransport` we simply cast it, |
| 51 | 51 |
// in order to set the internal cached token to the fake token, and thus send that fake token |
| 52 | 52 |
// upon every subsequent requests. |
| 53 |
- r.client.Transport.(*authTransport).token = token |
|
| 53 |
+ r.client.Transport.(*authTransport).token = []string{"fake-token"}
|
|
| 54 | 54 |
return r |
| 55 | 55 |
} |
| 56 | 56 |
|
| ... | ... |
@@ -148,153 +137,6 @@ func TestEndpoint(t *testing.T) {
|
| 148 | 148 |
} |
| 149 | 149 |
} |
| 150 | 150 |
|
| 151 |
-func TestGetRemoteHistory(t *testing.T) {
|
|
| 152 |
- r := spawnTestRegistrySession(t) |
|
| 153 |
- hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"))
|
|
| 154 |
- if err != nil {
|
|
| 155 |
- t.Fatal(err) |
|
| 156 |
- } |
|
| 157 |
- assertEqual(t, len(hist), 2, "Expected 2 images in history") |
|
| 158 |
- assertEqual(t, hist[0], imageID, "Expected "+imageID+"as first ancestry") |
|
| 159 |
- assertEqual(t, hist[1], "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", |
|
| 160 |
- "Unexpected second ancestry") |
|
| 161 |
-} |
|
| 162 |
- |
|
| 163 |
-func TestLookupRemoteImage(t *testing.T) {
|
|
| 164 |
- r := spawnTestRegistrySession(t) |
|
| 165 |
- err := r.LookupRemoteImage(imageID, makeURL("/v1/"))
|
|
| 166 |
- assertEqual(t, err, nil, "Expected error of remote lookup to nil") |
|
| 167 |
- if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil {
|
|
| 168 |
- t.Fatal("Expected error of remote lookup to not nil")
|
|
| 169 |
- } |
|
| 170 |
-} |
|
| 171 |
- |
|
| 172 |
-func TestGetRemoteImageJSON(t *testing.T) {
|
|
| 173 |
- r := spawnTestRegistrySession(t) |
|
| 174 |
- json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"))
|
|
| 175 |
- if err != nil {
|
|
| 176 |
- t.Fatal(err) |
|
| 177 |
- } |
|
| 178 |
- assertEqual(t, size, int64(154), "Expected size 154") |
|
| 179 |
- if len(json) == 0 {
|
|
| 180 |
- t.Fatal("Expected non-empty json")
|
|
| 181 |
- } |
|
| 182 |
- |
|
| 183 |
- _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"))
|
|
| 184 |
- if err == nil {
|
|
| 185 |
- t.Fatal("Expected image not found error")
|
|
| 186 |
- } |
|
| 187 |
-} |
|
| 188 |
- |
|
| 189 |
-func TestGetRemoteImageLayer(t *testing.T) {
|
|
| 190 |
- r := spawnTestRegistrySession(t) |
|
| 191 |
- data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0)
|
|
| 192 |
- if err != nil {
|
|
| 193 |
- t.Fatal(err) |
|
| 194 |
- } |
|
| 195 |
- if data == nil {
|
|
| 196 |
- t.Fatal("Expected non-nil data result")
|
|
| 197 |
- } |
|
| 198 |
- |
|
| 199 |
- _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0)
|
|
| 200 |
- if err == nil {
|
|
| 201 |
- t.Fatal("Expected image not found error")
|
|
| 202 |
- } |
|
| 203 |
-} |
|
| 204 |
- |
|
| 205 |
-func TestGetRemoteTag(t *testing.T) {
|
|
| 206 |
- r := spawnTestRegistrySession(t) |
|
| 207 |
- repoRef, err := reference.ParseNormalizedNamed(REPO) |
|
| 208 |
- if err != nil {
|
|
| 209 |
- t.Fatal(err) |
|
| 210 |
- } |
|
| 211 |
- tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, repoRef, "test")
|
|
| 212 |
- if err != nil {
|
|
| 213 |
- t.Fatal(err) |
|
| 214 |
- } |
|
| 215 |
- assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID) |
|
| 216 |
- |
|
| 217 |
- bazRef, err := reference.ParseNormalizedNamed("foo42/baz")
|
|
| 218 |
- if err != nil {
|
|
| 219 |
- t.Fatal(err) |
|
| 220 |
- } |
|
| 221 |
- _, err = r.GetRemoteTag([]string{makeURL("/v1/")}, bazRef, "foo")
|
|
| 222 |
- if err != ErrRepoNotFound {
|
|
| 223 |
- t.Fatal("Expected ErrRepoNotFound error when fetching tag for bogus repo")
|
|
| 224 |
- } |
|
| 225 |
-} |
|
| 226 |
- |
|
| 227 |
-func TestGetRemoteTags(t *testing.T) {
|
|
| 228 |
- r := spawnTestRegistrySession(t) |
|
| 229 |
- repoRef, err := reference.ParseNormalizedNamed(REPO) |
|
| 230 |
- if err != nil {
|
|
| 231 |
- t.Fatal(err) |
|
| 232 |
- } |
|
| 233 |
- tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, repoRef)
|
|
| 234 |
- if err != nil {
|
|
| 235 |
- t.Fatal(err) |
|
| 236 |
- } |
|
| 237 |
- assertEqual(t, len(tags), 2, "Expected two tags") |
|
| 238 |
- assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID) |
|
| 239 |
- assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID) |
|
| 240 |
- |
|
| 241 |
- bazRef, err := reference.ParseNormalizedNamed("foo42/baz")
|
|
| 242 |
- if err != nil {
|
|
| 243 |
- t.Fatal(err) |
|
| 244 |
- } |
|
| 245 |
- _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, bazRef)
|
|
| 246 |
- if err != ErrRepoNotFound {
|
|
| 247 |
- t.Fatal("Expected ErrRepoNotFound error when fetching tags for bogus repo")
|
|
| 248 |
- } |
|
| 249 |
-} |
|
| 250 |
- |
|
| 251 |
-func TestGetRepositoryData(t *testing.T) {
|
|
| 252 |
- r := spawnTestRegistrySession(t) |
|
| 253 |
- parsedURL, err := url.Parse(makeURL("/v1/"))
|
|
| 254 |
- if err != nil {
|
|
| 255 |
- t.Fatal(err) |
|
| 256 |
- } |
|
| 257 |
- host := "http://" + parsedURL.Host + "/v1/" |
|
| 258 |
- repoRef, err := reference.ParseNormalizedNamed(REPO) |
|
| 259 |
- if err != nil {
|
|
| 260 |
- t.Fatal(err) |
|
| 261 |
- } |
|
| 262 |
- data, err := r.GetRepositoryData(repoRef) |
|
| 263 |
- if err != nil {
|
|
| 264 |
- t.Fatal(err) |
|
| 265 |
- } |
|
| 266 |
- assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList") |
|
| 267 |
- assertEqual(t, len(data.Endpoints), 2, |
|
| 268 |
- fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints)))
|
|
| 269 |
- assertEqual(t, data.Endpoints[0], host, |
|
| 270 |
- fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0]))
|
|
| 271 |
- assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/", |
|
| 272 |
- fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1]))
|
|
| 273 |
- |
|
| 274 |
-} |
|
| 275 |
- |
|
| 276 |
-func TestPushImageJSONRegistry(t *testing.T) {
|
|
| 277 |
- r := spawnTestRegistrySession(t) |
|
| 278 |
- imgData := &ImgData{
|
|
| 279 |
- ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", |
|
| 280 |
- Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", |
|
| 281 |
- } |
|
| 282 |
- |
|
| 283 |
- err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"))
|
|
| 284 |
- if err != nil {
|
|
| 285 |
- t.Fatal(err) |
|
| 286 |
- } |
|
| 287 |
-} |
|
| 288 |
- |
|
| 289 |
-func TestPushImageLayerRegistry(t *testing.T) {
|
|
| 290 |
- r := spawnTestRegistrySession(t) |
|
| 291 |
- layer := strings.NewReader("")
|
|
| 292 |
- _, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{})
|
|
| 293 |
- if err != nil {
|
|
| 294 |
- t.Fatal(err) |
|
| 295 |
- } |
|
| 296 |
-} |
|
| 297 |
- |
|
| 298 | 151 |
func TestParseRepositoryInfo(t *testing.T) {
|
| 299 | 152 |
type staticRepositoryInfo struct {
|
| 300 | 153 |
Index *registrytypes.IndexInfo |
| ... | ... |
@@ -701,50 +543,6 @@ func TestMirrorEndpointLookup(t *testing.T) {
|
| 701 | 701 |
} |
| 702 | 702 |
} |
| 703 | 703 |
|
| 704 |
-func TestPushRegistryTag(t *testing.T) {
|
|
| 705 |
- r := spawnTestRegistrySession(t) |
|
| 706 |
- repoRef, err := reference.ParseNormalizedNamed(REPO) |
|
| 707 |
- if err != nil {
|
|
| 708 |
- t.Fatal(err) |
|
| 709 |
- } |
|
| 710 |
- err = r.PushRegistryTag(repoRef, imageID, "stable", makeURL("/v1/"))
|
|
| 711 |
- if err != nil {
|
|
| 712 |
- t.Fatal(err) |
|
| 713 |
- } |
|
| 714 |
-} |
|
| 715 |
- |
|
| 716 |
-func TestPushImageJSONIndex(t *testing.T) {
|
|
| 717 |
- r := spawnTestRegistrySession(t) |
|
| 718 |
- imgData := []*ImgData{
|
|
| 719 |
- {
|
|
| 720 |
- ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", |
|
| 721 |
- Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", |
|
| 722 |
- }, |
|
| 723 |
- {
|
|
| 724 |
- ID: "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", |
|
| 725 |
- Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", |
|
| 726 |
- }, |
|
| 727 |
- } |
|
| 728 |
- repoRef, err := reference.ParseNormalizedNamed(REPO) |
|
| 729 |
- if err != nil {
|
|
| 730 |
- t.Fatal(err) |
|
| 731 |
- } |
|
| 732 |
- repoData, err := r.PushImageJSONIndex(repoRef, imgData, false, nil) |
|
| 733 |
- if err != nil {
|
|
| 734 |
- t.Fatal(err) |
|
| 735 |
- } |
|
| 736 |
- if repoData == nil {
|
|
| 737 |
- t.Fatal("Expected RepositoryData object")
|
|
| 738 |
- } |
|
| 739 |
- repoData, err = r.PushImageJSONIndex(repoRef, imgData, true, []string{r.indexEndpoint.String()})
|
|
| 740 |
- if err != nil {
|
|
| 741 |
- t.Fatal(err) |
|
| 742 |
- } |
|
| 743 |
- if repoData == nil {
|
|
| 744 |
- t.Fatal("Expected RepositoryData object")
|
|
| 745 |
- } |
|
| 746 |
-} |
|
| 747 |
- |
|
| 748 | 704 |
func TestSearchRepositories(t *testing.T) {
|
| 749 | 705 |
r := spawnTestRegistrySession(t) |
| 750 | 706 |
results, err := r.SearchRepositories("fakequery", 25)
|
| ... | ... |
@@ -1,43 +1,26 @@ |
| 1 | 1 |
package registry // import "github.com/docker/docker/registry" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bytes" |
|
| 5 |
- "crypto/sha256" |
|
| 6 |
- |
|
| 7 | 4 |
// this is required for some certificates |
| 8 | 5 |
_ "crypto/sha512" |
| 9 |
- "encoding/hex" |
|
| 10 | 6 |
"encoding/json" |
| 11 | 7 |
"fmt" |
| 12 |
- "io" |
|
| 13 |
- "io/ioutil" |
|
| 14 | 8 |
"net/http" |
| 15 | 9 |
"net/http/cookiejar" |
| 16 | 10 |
"net/url" |
| 17 |
- "strconv" |
|
| 18 | 11 |
"strings" |
| 19 | 12 |
"sync" |
| 20 | 13 |
|
| 21 |
- "github.com/docker/distribution/reference" |
|
| 22 |
- "github.com/docker/distribution/registry/api/errcode" |
|
| 23 | 14 |
"github.com/docker/docker/api/types" |
| 24 | 15 |
registrytypes "github.com/docker/docker/api/types/registry" |
| 25 | 16 |
"github.com/docker/docker/errdefs" |
| 26 | 17 |
"github.com/docker/docker/pkg/ioutils" |
| 27 | 18 |
"github.com/docker/docker/pkg/jsonmessage" |
| 28 | 19 |
"github.com/docker/docker/pkg/stringid" |
| 29 |
- "github.com/docker/docker/pkg/tarsum" |
|
| 30 |
- "github.com/docker/docker/registry/resumable" |
|
| 31 | 20 |
"github.com/pkg/errors" |
| 32 | 21 |
"github.com/sirupsen/logrus" |
| 33 | 22 |
) |
| 34 | 23 |
|
| 35 |
-var ( |
|
| 36 |
- // ErrRepoNotFound is returned if the repository didn't exist on the |
|
| 37 |
- // remote side |
|
| 38 |
- ErrRepoNotFound notFoundError = "Repository not found" |
|
| 39 |
-) |
|
| 40 |
- |
|
| 41 | 24 |
// A Session is used to communicate with a V1 registry |
| 42 | 25 |
type Session struct {
|
| 43 | 26 |
indexEndpoint *V1Endpoint |
| ... | ... |
@@ -214,527 +197,6 @@ func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1E |
| 214 | 214 |
return newSession(client, authConfig, endpoint), nil |
| 215 | 215 |
} |
| 216 | 216 |
|
| 217 |
-// ID returns this registry session's ID. |
|
| 218 |
-func (r *Session) ID() string {
|
|
| 219 |
- return r.id |
|
| 220 |
-} |
|
| 221 |
- |
|
| 222 |
-// GetRemoteHistory retrieves the history of a given image from the registry. |
|
| 223 |
-// It returns a list of the parent's JSON files (including the requested image). |
|
| 224 |
-func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
|
|
| 225 |
- res, err := r.client.Get(registry + "images/" + imgID + "/ancestry") |
|
| 226 |
- if err != nil {
|
|
| 227 |
- return nil, err |
|
| 228 |
- } |
|
| 229 |
- defer res.Body.Close() |
|
| 230 |
- if res.StatusCode != http.StatusOK {
|
|
| 231 |
- if res.StatusCode == http.StatusUnauthorized {
|
|
| 232 |
- return nil, errcode.ErrorCodeUnauthorized.WithArgs() |
|
| 233 |
- } |
|
| 234 |
- return nil, newJSONError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
|
|
| 235 |
- } |
|
| 236 |
- |
|
| 237 |
- var history []string |
|
| 238 |
- if err := json.NewDecoder(res.Body).Decode(&history); err != nil {
|
|
| 239 |
- return nil, fmt.Errorf("Error while reading the http response: %v", err)
|
|
| 240 |
- } |
|
| 241 |
- |
|
| 242 |
- logrus.Debugf("Ancestry: %v", history)
|
|
| 243 |
- return history, nil |
|
| 244 |
-} |
|
| 245 |
- |
|
| 246 |
-// LookupRemoteImage checks if an image exists in the registry |
|
| 247 |
-func (r *Session) LookupRemoteImage(imgID, registry string) error {
|
|
| 248 |
- res, err := r.client.Get(registry + "images/" + imgID + "/json") |
|
| 249 |
- if err != nil {
|
|
| 250 |
- return err |
|
| 251 |
- } |
|
| 252 |
- res.Body.Close() |
|
| 253 |
- if res.StatusCode != http.StatusOK {
|
|
| 254 |
- return newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
|
|
| 255 |
- } |
|
| 256 |
- return nil |
|
| 257 |
-} |
|
| 258 |
- |
|
| 259 |
-// GetRemoteImageJSON retrieves an image's JSON metadata from the registry. |
|
| 260 |
-func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int64, error) {
|
|
| 261 |
- res, err := r.client.Get(registry + "images/" + imgID + "/json") |
|
| 262 |
- if err != nil {
|
|
| 263 |
- return nil, -1, fmt.Errorf("Failed to download json: %s", err)
|
|
| 264 |
- } |
|
| 265 |
- defer res.Body.Close() |
|
| 266 |
- if res.StatusCode != http.StatusOK {
|
|
| 267 |
- return nil, -1, newJSONError(fmt.Sprintf("HTTP code %d", res.StatusCode), res)
|
|
| 268 |
- } |
|
| 269 |
- // if the size header is not present, then set it to '-1' |
|
| 270 |
- imageSize := int64(-1) |
|
| 271 |
- if hdr := res.Header.Get("X-Docker-Size"); hdr != "" {
|
|
| 272 |
- imageSize, err = strconv.ParseInt(hdr, 10, 64) |
|
| 273 |
- if err != nil {
|
|
| 274 |
- return nil, -1, err |
|
| 275 |
- } |
|
| 276 |
- } |
|
| 277 |
- |
|
| 278 |
- jsonString, err := ioutil.ReadAll(res.Body) |
|
| 279 |
- if err != nil {
|
|
| 280 |
- return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString)
|
|
| 281 |
- } |
|
| 282 |
- return jsonString, imageSize, nil |
|
| 283 |
-} |
|
| 284 |
- |
|
| 285 |
-// GetRemoteImageLayer retrieves an image layer from the registry |
|
| 286 |
-func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) {
|
|
| 287 |
- var ( |
|
| 288 |
- statusCode = 0 |
|
| 289 |
- res *http.Response |
|
| 290 |
- err error |
|
| 291 |
- imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
|
|
| 292 |
- ) |
|
| 293 |
- |
|
| 294 |
- req, err := http.NewRequest(http.MethodGet, imageURL, nil) |
|
| 295 |
- if err != nil {
|
|
| 296 |
- return nil, fmt.Errorf("Error while getting from the server: %v", err)
|
|
| 297 |
- } |
|
| 298 |
- |
|
| 299 |
- res, err = r.client.Do(req) |
|
| 300 |
- if err != nil {
|
|
| 301 |
- logrus.Debugf("Error contacting registry %s: %v", registry, err)
|
|
| 302 |
- // the only case err != nil && res != nil is https://golang.org/src/net/http/client.go#L515 |
|
| 303 |
- if res != nil {
|
|
| 304 |
- if res.Body != nil {
|
|
| 305 |
- res.Body.Close() |
|
| 306 |
- } |
|
| 307 |
- statusCode = res.StatusCode |
|
| 308 |
- } |
|
| 309 |
- return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
|
|
| 310 |
- statusCode, imgID) |
|
| 311 |
- } |
|
| 312 |
- |
|
| 313 |
- if res.StatusCode != http.StatusOK {
|
|
| 314 |
- res.Body.Close() |
|
| 315 |
- return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
|
|
| 316 |
- res.StatusCode, imgID) |
|
| 317 |
- } |
|
| 318 |
- |
|
| 319 |
- if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
|
|
| 320 |
- logrus.Debug("server supports resume")
|
|
| 321 |
- return resumable.NewRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil |
|
| 322 |
- } |
|
| 323 |
- logrus.Debug("server doesn't support resume")
|
|
| 324 |
- return res.Body, nil |
|
| 325 |
-} |
|
| 326 |
- |
|
| 327 |
-// GetRemoteTag retrieves the tag named in the askedTag argument from the given |
|
| 328 |
-// repository. It queries each of the registries supplied in the registries |
|
| 329 |
-// argument, and returns data from the first one that answers the query |
|
| 330 |
-// successfully. |
|
| 331 |
-func (r *Session) GetRemoteTag(registries []string, repositoryRef reference.Named, askedTag string) (string, error) {
|
|
| 332 |
- repository := reference.Path(repositoryRef) |
|
| 333 |
- |
|
| 334 |
- if strings.Count(repository, "/") == 0 {
|
|
| 335 |
- // This will be removed once the registry supports auto-resolution on |
|
| 336 |
- // the "library" namespace |
|
| 337 |
- repository = "library/" + repository |
|
| 338 |
- } |
|
| 339 |
- for _, host := range registries {
|
|
| 340 |
- endpoint := fmt.Sprintf("%srepositories/%s/tags/%s", host, repository, askedTag)
|
|
| 341 |
- res, err := r.client.Get(endpoint) |
|
| 342 |
- if err != nil {
|
|
| 343 |
- return "", err |
|
| 344 |
- } |
|
| 345 |
- |
|
| 346 |
- logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
|
|
| 347 |
- defer res.Body.Close() |
|
| 348 |
- |
|
| 349 |
- if res.StatusCode == 404 {
|
|
| 350 |
- return "", ErrRepoNotFound |
|
| 351 |
- } |
|
| 352 |
- if res.StatusCode != http.StatusOK {
|
|
| 353 |
- continue |
|
| 354 |
- } |
|
| 355 |
- |
|
| 356 |
- var tagID string |
|
| 357 |
- if err := json.NewDecoder(res.Body).Decode(&tagID); err != nil {
|
|
| 358 |
- return "", err |
|
| 359 |
- } |
|
| 360 |
- return tagID, nil |
|
| 361 |
- } |
|
| 362 |
- return "", fmt.Errorf("Could not reach any registry endpoint")
|
|
| 363 |
-} |
|
| 364 |
- |
|
| 365 |
-// GetRemoteTags retrieves all tags from the given repository. It queries each |
|
| 366 |
-// of the registries supplied in the registries argument, and returns data from |
|
| 367 |
-// the first one that answers the query successfully. It returns a map with |
|
| 368 |
-// tag names as the keys and image IDs as the values. |
|
| 369 |
-func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) {
|
|
| 370 |
- repository := reference.Path(repositoryRef) |
|
| 371 |
- |
|
| 372 |
- if strings.Count(repository, "/") == 0 {
|
|
| 373 |
- // This will be removed once the registry supports auto-resolution on |
|
| 374 |
- // the "library" namespace |
|
| 375 |
- repository = "library/" + repository |
|
| 376 |
- } |
|
| 377 |
- for _, host := range registries {
|
|
| 378 |
- endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
|
|
| 379 |
- res, err := r.client.Get(endpoint) |
|
| 380 |
- if err != nil {
|
|
| 381 |
- return nil, err |
|
| 382 |
- } |
|
| 383 |
- |
|
| 384 |
- logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
|
|
| 385 |
- defer res.Body.Close() |
|
| 386 |
- |
|
| 387 |
- if res.StatusCode == 404 {
|
|
| 388 |
- return nil, ErrRepoNotFound |
|
| 389 |
- } |
|
| 390 |
- if res.StatusCode != http.StatusOK {
|
|
| 391 |
- continue |
|
| 392 |
- } |
|
| 393 |
- |
|
| 394 |
- result := make(map[string]string) |
|
| 395 |
- if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
|
|
| 396 |
- return nil, err |
|
| 397 |
- } |
|
| 398 |
- return result, nil |
|
| 399 |
- } |
|
| 400 |
- return nil, fmt.Errorf("Could not reach any registry endpoint")
|
|
| 401 |
-} |
|
| 402 |
- |
|
| 403 |
-func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
|
|
| 404 |
- var endpoints []string |
|
| 405 |
- parsedURL, err := url.Parse(indexEp) |
|
| 406 |
- if err != nil {
|
|
| 407 |
- return nil, err |
|
| 408 |
- } |
|
| 409 |
- var urlScheme = parsedURL.Scheme |
|
| 410 |
- // The registry's URL scheme has to match the Index' |
|
| 411 |
- for _, ep := range headers {
|
|
| 412 |
- epList := strings.Split(ep, ",") |
|
| 413 |
- for _, epListElement := range epList {
|
|
| 414 |
- endpoints = append( |
|
| 415 |
- endpoints, |
|
| 416 |
- fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement)))
|
|
| 417 |
- } |
|
| 418 |
- } |
|
| 419 |
- return endpoints, nil |
|
| 420 |
-} |
|
| 421 |
- |
|
| 422 |
-// GetRepositoryData returns lists of images and endpoints for the repository |
|
| 423 |
-func (r *Session) GetRepositoryData(name reference.Named) (*RepositoryData, error) {
|
|
| 424 |
- repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.String(), reference.Path(name))
|
|
| 425 |
- |
|
| 426 |
- logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
|
|
| 427 |
- |
|
| 428 |
- req, err := http.NewRequest(http.MethodGet, repositoryTarget, nil) |
|
| 429 |
- if err != nil {
|
|
| 430 |
- return nil, err |
|
| 431 |
- } |
|
| 432 |
- // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests |
|
| 433 |
- req.Header.Set("X-Docker-Token", "true")
|
|
| 434 |
- res, err := r.client.Do(req) |
|
| 435 |
- if err != nil {
|
|
| 436 |
- // check if the error is because of i/o timeout |
|
| 437 |
- // and return a non-obtuse error message for users |
|
| 438 |
- // "Get https://index.docker.io/v1/repositories/library/busybox/images: i/o timeout" |
|
| 439 |
- // was a top search on the docker user forum |
|
| 440 |
- if isTimeout(err) {
|
|
| 441 |
- return nil, fmt.Errorf("network timed out while trying to connect to %s. You may want to check your internet connection or if you are behind a proxy", repositoryTarget)
|
|
| 442 |
- } |
|
| 443 |
- return nil, fmt.Errorf("Error while pulling image: %v", err)
|
|
| 444 |
- } |
|
| 445 |
- defer res.Body.Close() |
|
| 446 |
- if res.StatusCode == http.StatusUnauthorized {
|
|
| 447 |
- return nil, errcode.ErrorCodeUnauthorized.WithArgs() |
|
| 448 |
- } |
|
| 449 |
- // TODO: Right now we're ignoring checksums in the response body. |
|
| 450 |
- // In the future, we need to use them to check image validity. |
|
| 451 |
- if res.StatusCode == 404 {
|
|
| 452 |
- return nil, newJSONError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res)
|
|
| 453 |
- } else if res.StatusCode != http.StatusOK {
|
|
| 454 |
- errBody, err := ioutil.ReadAll(res.Body) |
|
| 455 |
- if err != nil {
|
|
| 456 |
- logrus.Debugf("Error reading response body: %s", err)
|
|
| 457 |
- } |
|
| 458 |
- return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, reference.Path(name), errBody), res)
|
|
| 459 |
- } |
|
| 460 |
- |
|
| 461 |
- var endpoints []string |
|
| 462 |
- if res.Header.Get("X-Docker-Endpoints") != "" {
|
|
| 463 |
- endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String()) |
|
| 464 |
- if err != nil {
|
|
| 465 |
- return nil, err |
|
| 466 |
- } |
|
| 467 |
- } else {
|
|
| 468 |
- // Assume the endpoint is on the same host |
|
| 469 |
- endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", r.indexEndpoint.URL.Scheme, req.URL.Host))
|
|
| 470 |
- } |
|
| 471 |
- |
|
| 472 |
- remoteChecksums := []*ImgData{}
|
|
| 473 |
- if err := json.NewDecoder(res.Body).Decode(&remoteChecksums); err != nil {
|
|
| 474 |
- return nil, err |
|
| 475 |
- } |
|
| 476 |
- |
|
| 477 |
- // Forge a better object from the retrieved data |
|
| 478 |
- imgsData := make(map[string]*ImgData, len(remoteChecksums)) |
|
| 479 |
- for _, elem := range remoteChecksums {
|
|
| 480 |
- imgsData[elem.ID] = elem |
|
| 481 |
- } |
|
| 482 |
- |
|
| 483 |
- return &RepositoryData{
|
|
| 484 |
- ImgList: imgsData, |
|
| 485 |
- Endpoints: endpoints, |
|
| 486 |
- }, nil |
|
| 487 |
-} |
|
| 488 |
- |
|
| 489 |
-// PushImageChecksumRegistry uploads checksums for an image |
|
| 490 |
-func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error {
|
|
| 491 |
- u := registry + "images/" + imgData.ID + "/checksum" |
|
| 492 |
- |
|
| 493 |
- logrus.Debugf("[registry] Calling PUT %s", u)
|
|
| 494 |
- |
|
| 495 |
- req, err := http.NewRequest(http.MethodPut, u, nil) |
|
| 496 |
- if err != nil {
|
|
| 497 |
- return err |
|
| 498 |
- } |
|
| 499 |
- req.Header.Set("X-Docker-Checksum", imgData.Checksum)
|
|
| 500 |
- req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
|
|
| 501 |
- |
|
| 502 |
- res, err := r.client.Do(req) |
|
| 503 |
- if err != nil {
|
|
| 504 |
- return fmt.Errorf("Failed to upload metadata: %v", err)
|
|
| 505 |
- } |
|
| 506 |
- defer res.Body.Close() |
|
| 507 |
- if len(res.Cookies()) > 0 {
|
|
| 508 |
- r.client.Jar.SetCookies(req.URL, res.Cookies()) |
|
| 509 |
- } |
|
| 510 |
- if res.StatusCode != http.StatusOK {
|
|
| 511 |
- errBody, err := ioutil.ReadAll(res.Body) |
|
| 512 |
- if err != nil {
|
|
| 513 |
- return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
|
|
| 514 |
- } |
|
| 515 |
- var jsonBody map[string]string |
|
| 516 |
- if err := json.Unmarshal(errBody, &jsonBody); err != nil {
|
|
| 517 |
- errBody = []byte(err.Error()) |
|
| 518 |
- } else if jsonBody["error"] == "Image already exists" {
|
|
| 519 |
- return ErrAlreadyExists |
|
| 520 |
- } |
|
| 521 |
- return fmt.Errorf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody)
|
|
| 522 |
- } |
|
| 523 |
- return nil |
|
| 524 |
-} |
|
| 525 |
- |
|
| 526 |
-// PushImageJSONRegistry pushes JSON metadata for a local image to the registry |
|
| 527 |
-func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error {
|
|
| 528 |
- |
|
| 529 |
- u := registry + "images/" + imgData.ID + "/json" |
|
| 530 |
- |
|
| 531 |
- logrus.Debugf("[registry] Calling PUT %s", u)
|
|
| 532 |
- |
|
| 533 |
- req, err := http.NewRequest(http.MethodPut, u, bytes.NewReader(jsonRaw)) |
|
| 534 |
- if err != nil {
|
|
| 535 |
- return err |
|
| 536 |
- } |
|
| 537 |
- req.Header.Add("Content-type", "application/json")
|
|
| 538 |
- |
|
| 539 |
- res, err := r.client.Do(req) |
|
| 540 |
- if err != nil {
|
|
| 541 |
- return fmt.Errorf("Failed to upload metadata: %s", err)
|
|
| 542 |
- } |
|
| 543 |
- defer res.Body.Close() |
|
| 544 |
- if res.StatusCode == http.StatusUnauthorized && strings.HasPrefix(registry, "http://") {
|
|
| 545 |
- return newJSONError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
|
|
| 546 |
- } |
|
| 547 |
- if res.StatusCode != http.StatusOK {
|
|
| 548 |
- errBody, err := ioutil.ReadAll(res.Body) |
|
| 549 |
- if err != nil {
|
|
| 550 |
- return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
|
|
| 551 |
- } |
|
| 552 |
- var jsonBody map[string]string |
|
| 553 |
- if err := json.Unmarshal(errBody, &jsonBody); err != nil {
|
|
| 554 |
- errBody = []byte(err.Error()) |
|
| 555 |
- } else if jsonBody["error"] == "Image already exists" {
|
|
| 556 |
- return ErrAlreadyExists |
|
| 557 |
- } |
|
| 558 |
- return newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res)
|
|
| 559 |
- } |
|
| 560 |
- return nil |
|
| 561 |
-} |
|
| 562 |
- |
|
| 563 |
-// PushImageLayerRegistry sends the checksum of an image layer to the registry |
|
| 564 |
-func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
|
|
| 565 |
- u := registry + "images/" + imgID + "/layer" |
|
| 566 |
- |
|
| 567 |
- logrus.Debugf("[registry] Calling PUT %s", u)
|
|
| 568 |
- |
|
| 569 |
- tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0) |
|
| 570 |
- if err != nil {
|
|
| 571 |
- return "", "", err |
|
| 572 |
- } |
|
| 573 |
- h := sha256.New() |
|
| 574 |
- h.Write(jsonRaw) |
|
| 575 |
- h.Write([]byte{'\n'})
|
|
| 576 |
- checksumLayer := io.TeeReader(tarsumLayer, h) |
|
| 577 |
- |
|
| 578 |
- req, err := http.NewRequest(http.MethodPut, u, checksumLayer) |
|
| 579 |
- if err != nil {
|
|
| 580 |
- return "", "", err |
|
| 581 |
- } |
|
| 582 |
- req.Header.Add("Content-Type", "application/octet-stream")
|
|
| 583 |
- req.ContentLength = -1 |
|
| 584 |
- req.TransferEncoding = []string{"chunked"}
|
|
| 585 |
- res, err := r.client.Do(req) |
|
| 586 |
- if err != nil {
|
|
| 587 |
- return "", "", fmt.Errorf("Failed to upload layer: %v", err)
|
|
| 588 |
- } |
|
| 589 |
- if rc, ok := layer.(io.Closer); ok {
|
|
| 590 |
- if err := rc.Close(); err != nil {
|
|
| 591 |
- return "", "", err |
|
| 592 |
- } |
|
| 593 |
- } |
|
| 594 |
- defer res.Body.Close() |
|
| 595 |
- |
|
| 596 |
- if res.StatusCode != http.StatusOK {
|
|
| 597 |
- errBody, err := ioutil.ReadAll(res.Body) |
|
| 598 |
- if err != nil {
|
|
| 599 |
- return "", "", newJSONError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res)
|
|
| 600 |
- } |
|
| 601 |
- return "", "", newJSONError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res)
|
|
| 602 |
- } |
|
| 603 |
- |
|
| 604 |
- checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil)) |
|
| 605 |
- return tarsumLayer.Sum(jsonRaw), checksumPayload, nil |
|
| 606 |
-} |
|
| 607 |
- |
|
| 608 |
-// PushRegistryTag pushes a tag on the registry. |
|
| 609 |
-// Remote has the format '<user>/<repo> |
|
| 610 |
-func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error {
|
|
| 611 |
- // "jsonify" the string |
|
| 612 |
- revision = "\"" + revision + "\"" |
|
| 613 |
- path := fmt.Sprintf("repositories/%s/tags/%s", reference.Path(remote), tag)
|
|
| 614 |
- |
|
| 615 |
- req, err := http.NewRequest(http.MethodPut, registry+path, strings.NewReader(revision)) |
|
| 616 |
- if err != nil {
|
|
| 617 |
- return err |
|
| 618 |
- } |
|
| 619 |
- req.Header.Add("Content-type", "application/json")
|
|
| 620 |
- req.ContentLength = int64(len(revision)) |
|
| 621 |
- res, err := r.client.Do(req) |
|
| 622 |
- if err != nil {
|
|
| 623 |
- return err |
|
| 624 |
- } |
|
| 625 |
- res.Body.Close() |
|
| 626 |
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
|
|
| 627 |
- return newJSONError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, reference.Path(remote)), res)
|
|
| 628 |
- } |
|
| 629 |
- return nil |
|
| 630 |
-} |
|
| 631 |
- |
|
| 632 |
-// PushImageJSONIndex uploads an image list to the repository |
|
| 633 |
-func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
|
|
| 634 |
- cleanImgList := []*ImgData{}
|
|
| 635 |
- if validate {
|
|
| 636 |
- for _, elem := range imgList {
|
|
| 637 |
- if elem.Checksum != "" {
|
|
| 638 |
- cleanImgList = append(cleanImgList, elem) |
|
| 639 |
- } |
|
| 640 |
- } |
|
| 641 |
- } else {
|
|
| 642 |
- cleanImgList = imgList |
|
| 643 |
- } |
|
| 644 |
- |
|
| 645 |
- imgListJSON, err := json.Marshal(cleanImgList) |
|
| 646 |
- if err != nil {
|
|
| 647 |
- return nil, err |
|
| 648 |
- } |
|
| 649 |
- var suffix string |
|
| 650 |
- if validate {
|
|
| 651 |
- suffix = "images" |
|
| 652 |
- } |
|
| 653 |
- u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.String(), reference.Path(remote), suffix)
|
|
| 654 |
- logrus.Debugf("[registry] PUT %s", u)
|
|
| 655 |
- logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
|
|
| 656 |
- headers := map[string][]string{
|
|
| 657 |
- "Content-type": {"application/json"},
|
|
| 658 |
- // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests |
|
| 659 |
- "X-Docker-Token": {"true"},
|
|
| 660 |
- } |
|
| 661 |
- if validate {
|
|
| 662 |
- headers["X-Docker-Endpoints"] = regs |
|
| 663 |
- } |
|
| 664 |
- |
|
| 665 |
- // Redirect if necessary |
|
| 666 |
- var res *http.Response |
|
| 667 |
- for {
|
|
| 668 |
- if res, err = r.putImageRequest(u, headers, imgListJSON); err != nil {
|
|
| 669 |
- return nil, err |
|
| 670 |
- } |
|
| 671 |
- if !shouldRedirect(res) {
|
|
| 672 |
- break |
|
| 673 |
- } |
|
| 674 |
- res.Body.Close() |
|
| 675 |
- u = res.Header.Get("Location")
|
|
| 676 |
- logrus.Debugf("Redirected to %s", u)
|
|
| 677 |
- } |
|
| 678 |
- defer res.Body.Close() |
|
| 679 |
- |
|
| 680 |
- if res.StatusCode == http.StatusUnauthorized {
|
|
| 681 |
- return nil, errcode.ErrorCodeUnauthorized.WithArgs() |
|
| 682 |
- } |
|
| 683 |
- |
|
| 684 |
- var tokens, endpoints []string |
|
| 685 |
- if !validate {
|
|
| 686 |
- if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
|
|
| 687 |
- errBody, err := ioutil.ReadAll(res.Body) |
|
| 688 |
- if err != nil {
|
|
| 689 |
- logrus.Debugf("Error reading response body: %s", err)
|
|
| 690 |
- } |
|
| 691 |
- return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, reference.Path(remote), errBody), res)
|
|
| 692 |
- } |
|
| 693 |
- tokens = res.Header["X-Docker-Token"] |
|
| 694 |
- logrus.Debugf("Auth token: %v", tokens)
|
|
| 695 |
- |
|
| 696 |
- if res.Header.Get("X-Docker-Endpoints") == "" {
|
|
| 697 |
- return nil, fmt.Errorf("Index response didn't contain any endpoints")
|
|
| 698 |
- } |
|
| 699 |
- endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String()) |
|
| 700 |
- if err != nil {
|
|
| 701 |
- return nil, err |
|
| 702 |
- } |
|
| 703 |
- } else {
|
|
| 704 |
- if res.StatusCode != http.StatusNoContent {
|
|
| 705 |
- errBody, err := ioutil.ReadAll(res.Body) |
|
| 706 |
- if err != nil {
|
|
| 707 |
- logrus.Debugf("Error reading response body: %s", err)
|
|
| 708 |
- } |
|
| 709 |
- return nil, newJSONError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, reference.Path(remote), errBody), res)
|
|
| 710 |
- } |
|
| 711 |
- } |
|
| 712 |
- |
|
| 713 |
- return &RepositoryData{
|
|
| 714 |
- Endpoints: endpoints, |
|
| 715 |
- }, nil |
|
| 716 |
-} |
|
| 717 |
- |
|
| 718 |
-func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
|
|
| 719 |
- req, err := http.NewRequest(http.MethodPut, u, bytes.NewReader(body)) |
|
| 720 |
- if err != nil {
|
|
| 721 |
- return nil, err |
|
| 722 |
- } |
|
| 723 |
- req.ContentLength = int64(len(body)) |
|
| 724 |
- for k, v := range headers {
|
|
| 725 |
- req.Header[k] = v |
|
| 726 |
- } |
|
| 727 |
- response, err := r.client.Do(req) |
|
| 728 |
- if err != nil {
|
|
| 729 |
- return nil, err |
|
| 730 |
- } |
|
| 731 |
- return response, nil |
|
| 732 |
-} |
|
| 733 |
- |
|
| 734 |
-func shouldRedirect(response *http.Response) bool {
|
|
| 735 |
- return response.StatusCode >= 300 && response.StatusCode < 400 |
|
| 736 |
-} |
|
| 737 |
- |
|
| 738 | 217 |
// SearchRepositories performs a search against the remote repository |
| 739 | 218 |
func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) {
|
| 740 | 219 |
if limit < 1 || limit > 100 {
|
| ... | ... |
@@ -755,28 +217,11 @@ func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.Sea |
| 755 | 755 |
} |
| 756 | 756 |
defer res.Body.Close() |
| 757 | 757 |
if res.StatusCode != http.StatusOK {
|
| 758 |
- return nil, newJSONError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res)
|
|
| 758 |
+ return nil, &jsonmessage.JSONError{
|
|
| 759 |
+ Message: fmt.Sprintf("Unexpected status code %d", res.StatusCode),
|
|
| 760 |
+ Code: res.StatusCode, |
|
| 761 |
+ } |
|
| 759 | 762 |
} |
| 760 | 763 |
result := new(registrytypes.SearchResults) |
| 761 | 764 |
return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results") |
| 762 | 765 |
} |
| 763 |
- |
|
| 764 |
-func isTimeout(err error) bool {
|
|
| 765 |
- type timeout interface {
|
|
| 766 |
- Timeout() bool |
|
| 767 |
- } |
|
| 768 |
- e := err |
|
| 769 |
- switch urlErr := err.(type) {
|
|
| 770 |
- case *url.Error: |
|
| 771 |
- e = urlErr.Err |
|
| 772 |
- } |
|
| 773 |
- t, ok := e.(timeout) |
|
| 774 |
- return ok && t.Timeout() |
|
| 775 |
-} |
|
| 776 |
- |
|
| 777 |
-func newJSONError(msg string, res *http.Response) error {
|
|
| 778 |
- return &jsonmessage.JSONError{
|
|
| 779 |
- Message: msg, |
|
| 780 |
- Code: res.StatusCode, |
|
| 781 |
- } |
|
| 782 |
-} |