github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/docker/registry/internal/base_manifests.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package internal 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "net/http" 10 "strings" 11 12 "github.com/juju/errors" 13 ) 14 15 //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/manifests_mock.go github.com/juju/juju/docker/registry/internal ArchitectureGetter 16 17 type manifestsResponseV1 struct { 18 SchemaVersion int `json:"schemaVersion"` 19 Architecture string `json:"architecture"` 20 } 21 22 type manifestsResponseV2Config struct { 23 Digest string `json:"digest"` 24 } 25 26 type manifestsResponseV2 struct { 27 SchemaVersion int `json:"schemaVersion"` 28 Config manifestsResponseV2Config `json:"config"` 29 } 30 31 // ManifestsResult is the result of GetManifests. 32 type ManifestsResult struct { 33 Architecture string 34 Digest string 35 } 36 37 // BlobsResponse is the result of GetBlobs. 38 type BlobsResponse struct { 39 Architecture string `json:"architecture"` 40 } 41 42 // ArchitectureGetter defines manifests and blob APIs. 43 type ArchitectureGetter interface { 44 GetManifests(imageName, tag string) (*ManifestsResult, error) 45 GetBlobs(imageName, digest string) (*BlobsResponse, error) 46 } 47 48 // GetArchitecture returns the archtecture of the image for the specified tag. 49 func (c baseClient) GetArchitecture(imageName, tag string) (string, error) { 50 return getArchitecture(imageName, tag, c) 51 } 52 53 func getArchitecture(imageName, tag string, client ArchitectureGetter) (string, error) { 54 manifests, err := client.GetManifests(imageName, tag) 55 if err != nil { 56 return "", errors.Annotatef(err, "can not get manifests for %s:%s", imageName, tag) 57 } 58 if manifests.Architecture == "" && manifests.Digest == "" { 59 return "", errors.New(fmt.Sprintf("faild to get manifests for %q %q", imageName, tag)) 60 } 61 if manifests.Architecture != "" { 62 return manifests.Architecture, nil 63 } 64 blobs, err := client.GetBlobs(imageName, manifests.Digest) 65 if err != nil { 66 return "", errors.Trace(err) 67 } 68 return blobs.Architecture, nil 69 } 70 71 // GetManifests returns the manifests of the image for the specified tag. 72 func (c baseClient) GetManifests(imageName, tag string) (*ManifestsResult, error) { 73 repo := getRepositoryOnly(c.ImageRepoDetails().Repository) 74 url := c.url("/%s/%s/manifests/%s", repo, imageName, tag) 75 return c.GetManifestsCommon(url) 76 } 77 78 // GetManifestsCommon returns manifests result for the provided url. 79 func (c baseClient) GetManifestsCommon(url string) (*ManifestsResult, error) { 80 resp, err := c.client.Get(url) 81 if err != nil { 82 return nil, errors.Trace(unwrapNetError(err)) 83 } 84 defer resp.Body.Close() 85 return processManifestsResponse(resp) 86 } 87 88 const ( 89 manifestContentTypeV1 = "application/vnd.docker.distribution.manifest.v1" 90 manifestContentTypeV2 = "application/vnd.docker.distribution.manifest.v2" 91 ) 92 93 func processManifestsResponse(resp *http.Response) (*ManifestsResult, error) { 94 contentTypes := resp.Header[http.CanonicalHeaderKey("Content-Type")] 95 if len(contentTypes) == 0 { 96 return nil, errors.NotSupportedf(`no "Content-Type" found in header of registry API response`) 97 } 98 contentType := contentTypes[0] 99 notSupportedAPIVersionError := errors.NotSupportedf("manifest response version %q", contentType) 100 parts := strings.Split(contentType, "+") 101 if len(parts) != 2 { 102 return nil, notSupportedAPIVersionError 103 } 104 switch parts[0] { 105 case manifestContentTypeV1: 106 var data manifestsResponseV1 107 if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 108 return nil, errors.Trace(err) 109 } 110 return &ManifestsResult{Architecture: data.Architecture}, nil 111 case manifestContentTypeV2: 112 var data manifestsResponseV2 113 if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { 114 return nil, errors.Trace(err) 115 } 116 return &ManifestsResult{Digest: data.Config.Digest}, nil 117 default: 118 return nil, notSupportedAPIVersionError 119 } 120 } 121 122 // GetBlobs gets the archtecture of the image for the specified tag via blobs API. 123 func (c baseClient) GetBlobs(imageName, digest string) (*BlobsResponse, error) { 124 repo := getRepositoryOnly(c.ImageRepoDetails().Repository) 125 url := c.url("/%s/%s/blobs/%s", repo, imageName, digest) 126 return c.GetBlobsCommon(url) 127 } 128 129 // GetBlobsCommon returns blobs result for the provided url. 130 func (c baseClient) GetBlobsCommon(url string) (*BlobsResponse, error) { 131 resp, err := c.client.Get(url) 132 logger.Tracef("getting blobs for %q, err %v", url, err) 133 if err != nil { 134 return nil, errors.Trace(unwrapNetError(err)) 135 } 136 defer resp.Body.Close() 137 var result BlobsResponse 138 if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 139 return nil, errors.Trace(err) 140 } 141 return &result, nil 142 }