github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/registry/client/fetcher.go (about) 1 package client 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 8 "github.com/docker/cli/cli/manifest/types" 9 "github.com/docker/distribution" 10 "github.com/docker/distribution/manifest/manifestlist" 11 "github.com/docker/distribution/manifest/schema2" 12 "github.com/docker/distribution/reference" 13 "github.com/docker/distribution/registry/api/errcode" 14 "github.com/docker/distribution/registry/api/v2" 15 distclient "github.com/docker/distribution/registry/client" 16 "github.com/docker/docker/registry" 17 digest "github.com/opencontainers/go-digest" 18 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 ) 22 23 // fetchManifest pulls a manifest from a registry and returns it. An error 24 // is returned if no manifest is found matching namedRef. 25 func fetchManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (types.ImageManifest, error) { 26 manifest, err := getManifest(ctx, repo, ref) 27 if err != nil { 28 return types.ImageManifest{}, err 29 } 30 31 switch v := manifest.(type) { 32 // Removed Schema 1 support 33 case *schema2.DeserializedManifest: 34 imageManifest, err := pullManifestSchemaV2(ctx, ref, repo, *v) 35 if err != nil { 36 return types.ImageManifest{}, err 37 } 38 return imageManifest, nil 39 case *manifestlist.DeserializedManifestList: 40 return types.ImageManifest{}, errors.Errorf("%s is a manifest list", ref) 41 } 42 return types.ImageManifest{}, errors.Errorf("%s is not a manifest", ref) 43 } 44 45 func fetchList(ctx context.Context, repo distribution.Repository, ref reference.Named) ([]types.ImageManifest, error) { 46 manifest, err := getManifest(ctx, repo, ref) 47 if err != nil { 48 return nil, err 49 } 50 51 switch v := manifest.(type) { 52 case *manifestlist.DeserializedManifestList: 53 imageManifests, err := pullManifestList(ctx, ref, repo, *v) 54 if err != nil { 55 return nil, err 56 } 57 return imageManifests, nil 58 default: 59 return nil, errors.Errorf("unsupported manifest format: %v", v) 60 } 61 } 62 63 func getManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (distribution.Manifest, error) { 64 manSvc, err := repo.Manifests(ctx) 65 if err != nil { 66 return nil, err 67 } 68 69 dgst, opts, err := getManifestOptionsFromReference(ref) 70 if err != nil { 71 return nil, errors.Errorf("image manifest for %q does not exist", ref) 72 } 73 return manSvc.Get(ctx, dgst, opts...) 74 } 75 76 func pullManifestSchemaV2(ctx context.Context, ref reference.Named, repo distribution.Repository, mfst schema2.DeserializedManifest) (types.ImageManifest, error) { 77 manifestDesc, err := validateManifestDigest(ref, mfst) 78 if err != nil { 79 return types.ImageManifest{}, err 80 } 81 configJSON, err := pullManifestSchemaV2ImageConfig(ctx, mfst.Target().Digest, repo) 82 if err != nil { 83 return types.ImageManifest{}, err 84 } 85 86 if manifestDesc.Platform == nil { 87 manifestDesc.Platform = &ocispec.Platform{} 88 } 89 90 // Fill in os and architecture fields from config JSON 91 if err := json.Unmarshal(configJSON, manifestDesc.Platform); err != nil { 92 return types.ImageManifest{}, err 93 } 94 95 return types.NewImageManifest(ref, manifestDesc, &mfst), nil 96 } 97 98 func pullManifestSchemaV2ImageConfig(ctx context.Context, dgst digest.Digest, repo distribution.Repository) ([]byte, error) { 99 blobs := repo.Blobs(ctx) 100 configJSON, err := blobs.Get(ctx, dgst) 101 if err != nil { 102 return nil, err 103 } 104 105 verifier := dgst.Verifier() 106 if err != nil { 107 return nil, err 108 } 109 if _, err := verifier.Write(configJSON); err != nil { 110 return nil, err 111 } 112 if !verifier.Verified() { 113 return nil, errors.Errorf("image config verification failed for digest %s", dgst) 114 } 115 return configJSON, nil 116 } 117 118 // validateManifestDigest computes the manifest digest, and, if pulling by 119 // digest, ensures that it matches the requested digest. 120 func validateManifestDigest(ref reference.Named, mfst distribution.Manifest) (ocispec.Descriptor, error) { 121 mediaType, canonical, err := mfst.Payload() 122 if err != nil { 123 return ocispec.Descriptor{}, err 124 } 125 desc := ocispec.Descriptor{ 126 Digest: digest.FromBytes(canonical), 127 Size: int64(len(canonical)), 128 MediaType: mediaType, 129 } 130 131 // If pull by digest, then verify the manifest digest. 132 if digested, isDigested := ref.(reference.Canonical); isDigested { 133 if digested.Digest() != desc.Digest { 134 err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest()) 135 return ocispec.Descriptor{}, err 136 } 137 } 138 139 return desc, nil 140 } 141 142 // pullManifestList handles "manifest lists" which point to various 143 // platform-specific manifests. 144 func pullManifestList(ctx context.Context, ref reference.Named, repo distribution.Repository, mfstList manifestlist.DeserializedManifestList) ([]types.ImageManifest, error) { 145 infos := []types.ImageManifest{} 146 147 if _, err := validateManifestDigest(ref, mfstList); err != nil { 148 return nil, err 149 } 150 151 for _, manifestDescriptor := range mfstList.Manifests { 152 manSvc, err := repo.Manifests(ctx) 153 if err != nil { 154 return nil, err 155 } 156 manifest, err := manSvc.Get(ctx, manifestDescriptor.Digest) 157 if err != nil { 158 return nil, err 159 } 160 v, ok := manifest.(*schema2.DeserializedManifest) 161 if !ok { 162 return nil, fmt.Errorf("unsupported manifest format: %v", v) 163 } 164 165 manifestRef, err := reference.WithDigest(ref, manifestDescriptor.Digest) 166 if err != nil { 167 return nil, err 168 } 169 imageManifest, err := pullManifestSchemaV2(ctx, manifestRef, repo, *v) 170 if err != nil { 171 return nil, err 172 } 173 174 // Replace platform from config 175 imageManifest.Descriptor.Platform = types.OCIPlatform(&manifestDescriptor.Platform) 176 177 infos = append(infos, imageManifest) 178 } 179 return infos, nil 180 } 181 182 func continueOnError(err error) bool { 183 switch v := err.(type) { 184 case errcode.Errors: 185 if len(v) == 0 { 186 return true 187 } 188 return continueOnError(v[0]) 189 case errcode.Error: 190 e := err.(errcode.Error) 191 switch e.Code { 192 case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown: 193 return true 194 } 195 return false 196 case *distclient.UnexpectedHTTPResponseError: 197 return true 198 } 199 return false 200 } 201 202 func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error { 203 endpoints, err := allEndpoints(namedRef, c.insecureRegistry) 204 if err != nil { 205 return err 206 } 207 208 repoInfo, err := registry.ParseRepositoryInfo(namedRef) 209 if err != nil { 210 return err 211 } 212 213 confirmedTLSRegistries := make(map[string]bool) 214 for _, endpoint := range endpoints { 215 216 if endpoint.Version == registry.APIVersion1 { 217 logrus.Debugf("skipping v1 endpoint %s", endpoint.URL) 218 continue 219 } 220 221 if endpoint.URL.Scheme != "https" { 222 if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS { 223 logrus.Debugf("skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL) 224 continue 225 } 226 } 227 228 if c.insecureRegistry { 229 endpoint.TLSConfig.InsecureSkipVerify = true 230 } 231 repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo} 232 repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint) 233 if err != nil { 234 logrus.Debugf("error with repo endpoint %s: %s", repoEndpoint, err) 235 if _, ok := err.(ErrHTTPProto); ok { 236 continue 237 } 238 return err 239 } 240 241 if endpoint.URL.Scheme == "http" && !c.insecureRegistry { 242 logrus.Debugf("skipping non-tls registry endpoint: %s", endpoint.URL) 243 continue 244 } 245 done, err := each(ctx, repo, namedRef) 246 if err != nil { 247 if continueOnError(err) { 248 if endpoint.URL.Scheme == "https" { 249 confirmedTLSRegistries[endpoint.URL.Host] = true 250 } 251 logrus.Debugf("continuing on error (%T) %s", err, err) 252 continue 253 } 254 logrus.Debugf("not continuing on error (%T) %s", err, err) 255 return err 256 } 257 if done { 258 return nil 259 } 260 } 261 return newNotFoundError(namedRef.String()) 262 } 263 264 // allEndpoints returns a list of endpoints ordered by priority (v2, https, v1). 265 func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) { 266 repoInfo, err := registry.ParseRepositoryInfo(namedRef) 267 if err != nil { 268 return nil, err 269 } 270 271 var serviceOpts registry.ServiceOptions 272 if insecure { 273 logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef)) 274 serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)} 275 } 276 registryService, err := registry.NewService(serviceOpts) 277 if err != nil { 278 return []registry.APIEndpoint{}, err 279 } 280 endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) 281 logrus.Debugf("endpoints for %s: %v", namedRef, endpoints) 282 return endpoints, err 283 } 284 285 type notFoundError struct { 286 object string 287 } 288 289 func newNotFoundError(ref string) *notFoundError { 290 return ¬FoundError{object: ref} 291 } 292 293 func (n *notFoundError) Error() string { 294 return fmt.Sprintf("no such manifest: %s", n.object) 295 } 296 297 // NotFound interface 298 func (n *notFoundError) NotFound() {} 299 300 // IsNotFound returns true if the error is a not found error 301 func IsNotFound(err error) bool { 302 _, ok := err.(notFound) 303 return ok 304 } 305 306 type notFound interface { 307 NotFound() 308 }