// getRepositoryMountCandidates returns an array of v2 metadata items belonging to the given registry. The // array is sorted from youngest to oldest. If requireReigstryMatch is true, the resulting array will contain // only metadata entries having registry part of SourceRepository matching the part of repoInfo. func getRepositoryMountCandidates( repoInfo reference.Named, hmacKey []byte, max int, v2Metadata []metadata.V2Metadata, ) []metadata.V2Metadata { candidates := []metadata.V2Metadata{} for _, meta := range v2Metadata { sourceRepo, err := reference.ParseNamed(meta.SourceRepository) if err != nil || repoInfo.Hostname() != sourceRepo.Hostname() { continue } // target repository is not a viable candidate if meta.SourceRepository == repoInfo.FullName() { continue } candidates = append(candidates, meta) } sortV2MetadataByLikenessAndAge(repoInfo, hmacKey, candidates) if max >= 0 && len(candidates) > max { // select the youngest metadata candidates = candidates[:max] } return candidates }
func sortV2MetadataByLikenessAndAge(repoInfo reference.Named, hmacKey []byte, marr []metadata.V2Metadata) { // reverse the metadata array to shift the newest entries to the beginning for i := 0; i < len(marr)/2; i++ { marr[i], marr[len(marr)-i-1] = marr[len(marr)-i-1], marr[i] } // keep equal entries ordered from the youngest to the oldest sort.Stable(byLikeness{ arr: marr, hmacKey: hmacKey, pathComponents: getPathComponents(repoInfo.FullName()), }) }
func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg nameString := repoName.FullName() if strings.HasPrefix(nameString, DefaultNamespace+"/") { endpoints = append(endpoints, APIEndpoint{ URL: DefaultV1Registry, Version: APIVersion1, Official: true, TrimHostname: true, TLSConfig: tlsConfig, }) return endpoints, nil } slashIndex := strings.IndexRune(nameString, '/') if slashIndex <= 0 { return nil, fmt.Errorf("invalid repo name: missing '/': %s", nameString) } hostname := nameString[:slashIndex] tlsConfig, err = s.TLSConfig(hostname) if err != nil { return nil, err } endpoints = []APIEndpoint{ { URL: &url.URL{ Scheme: "https", Host: hostname, }, Version: APIVersion1, TrimHostname: true, TLSConfig: tlsConfig, }, } if tlsConfig.InsecureSkipVerify { endpoints = append(endpoints, APIEndpoint{ // or this URL: &url.URL{ Scheme: "http", Host: hostname, }, Version: APIVersion1, TrimHostname: true, // used to check if supposed to be secure via InsecureSkipVerify TLSConfig: tlsConfig, }) } return endpoints, nil }
// layerAlreadyExists checks if the registry already know about any of the // metadata passed in the "metadata" slice. If it finds one that the registry // knows about, it returns the known digest and "true". func layerAlreadyExists(ctx context.Context, metadata []metadata.V2Metadata, repoInfo reference.Named, repo distribution.Repository, pushState *pushState) (distribution.Descriptor, bool, error) { for _, meta := range metadata { // Only check blobsums that are known to this repository or have an unknown source if meta.SourceRepository != "" && meta.SourceRepository != repoInfo.FullName() { continue } descriptor, err := repo.Blobs(ctx).Stat(ctx, meta.Digest) switch err { case nil: descriptor.MediaType = schema2.MediaTypeLayer return descriptor, true, nil case distribution.ErrBlobUnknown: // nop default: return distribution.Descriptor{}, false, err } } return distribution.Descriptor{}, false, nil }
// fullyExpandedDockerReference converts a reference.Named into a fully expanded format; // i.e. soft of an opposite to ref.String(), which is a fully canonicalized/minimized format. // This is guaranteed to be the same as reference.FullName(), with a tag or digest appended, if available. // FIXME? This feels like it should be provided by skopeo/reference. func fullyExpandedDockerReference(ref reference.Named) (string, error) { res := ref.FullName() tagged, isTagged := ref.(distreference.Tagged) digested, isDigested := ref.(distreference.Digested) // A github.com/distribution/reference value can have a tag and a digest at the same time! // skopeo/reference does not handle that, so fail. // FIXME? Should we support that? switch { case isTagged && isDigested: // Coverage: This should currently not happen, the way skopeo/reference sets up types, // isTagged and isDigested is mutually exclusive. return "", fmt.Errorf("Names with both a tag and digest are not currently supported") case isTagged: res = res + ":" + tagged.Tag() case isDigested: res = res + "@" + digested.Digest().String() default: // res is already OK. } return res, nil }
func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg nameString := repoName.FullName() if strings.HasPrefix(nameString, DefaultNamespace+"/") { // v2 mirrors for _, mirror := range s.Config.Mirrors { mirrorTLSConfig, err := s.tlsConfigForMirror(mirror) if err != nil { return nil, err } endpoints = append(endpoints, APIEndpoint{ URL: mirror, // guess mirrors are v2 Version: APIVersion2, Mirror: true, TrimHostname: true, TLSConfig: mirrorTLSConfig, }) } // v2 registry endpoints = append(endpoints, APIEndpoint{ URL: DefaultV2Registry, Version: APIVersion2, Official: true, TrimHostname: true, TLSConfig: tlsConfig, }) return endpoints, nil } slashIndex := strings.IndexRune(nameString, '/') if slashIndex <= 0 { return nil, fmt.Errorf("invalid repo name: missing '/': %s", nameString) } hostname := nameString[:slashIndex] tlsConfig, err = s.TLSConfig(hostname) if err != nil { return nil, err } endpoints = []APIEndpoint{ { URL: "https://" + hostname, Version: APIVersion2, TrimHostname: true, TLSConfig: tlsConfig, }, } if tlsConfig.InsecureSkipVerify { endpoints = append(endpoints, APIEndpoint{ URL: "http://" + hostname, Version: APIVersion2, TrimHostname: true, // used to check if supposed to be secure via InsecureSkipVerify TLSConfig: tlsConfig, }) } return endpoints, nil }
func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdated bool, err error) { tagOrDigest := "" if tagged, isTagged := ref.(reference.NamedTagged); isTagged { tagOrDigest = tagged.Tag() } else if digested, isCanonical := ref.(reference.Canonical); isCanonical { tagOrDigest = digested.Digest().String() } else { return false, fmt.Errorf("internal error: reference has neither a tag nor a digest: %s", ref.String()) } logrus.Debugf("Pulling ref from V2 registry: %s:%s", ref.FullName(), tagOrDigest) manSvc, err := p.repo.Manifests(ctx) if err != nil { return false, err } unverifiedManifest, err := manSvc.GetByTag(tagOrDigest) if err != nil { // If this manifest did not exist, we should allow a possible // fallback to the v1 protocol, because dual-version setups may // not host all manifests with the v2 protocol. We may also get // a "not authorized" error if the manifest doesn't exist. return false, allowV1Fallback(err) } if unverifiedManifest == nil { return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest) } // If GetByTag succeeded, we can be confident that the registry on // the other side speaks the v2 protocol. p.confirmedV2 = true var verifiedManifest *schema1.Manifest verifiedManifest, err = verifyManifest(unverifiedManifest, ref) if err != nil { return false, err } rootFS := image.NewRootFS() if err := detectBaseLayer(p.config.ImageStore, verifiedManifest, rootFS); err != nil { return false, err } // remove duplicate layers and check parent chain validity err = fixManifestLayers(verifiedManifest) if err != nil { return false, err } progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Name()) var descriptors []xfer.DownloadDescriptor // Image history converted to the new format var history []image.History // Note that the order of this loop is in the direction of bottom-most // to top-most, so that the downloads slice gets ordered correctly. for i := len(verifiedManifest.FSLayers) - 1; i >= 0; i-- { blobSum := verifiedManifest.FSLayers[i].BlobSum var throwAway struct { ThrowAway bool `json:"throwaway,omitempty"` } if err := json.Unmarshal([]byte(verifiedManifest.History[i].V1Compatibility), &throwAway); err != nil { return false, err } h, err := v1.HistoryFromConfig([]byte(verifiedManifest.History[i].V1Compatibility), throwAway.ThrowAway) if err != nil { return false, err } history = append(history, h) if throwAway.ThrowAway { continue } layerDescriptor := &v2LayerDescriptor{ digest: blobSum, repo: p.repo, blobSumService: p.blobSumService, } descriptors = append(descriptors, layerDescriptor) } resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput) if err != nil { return false, err } defer release() config, err := v1.MakeConfigFromV1Config([]byte(verifiedManifest.History[0].V1Compatibility), &resultRootFS, history) if err != nil { return false, err } imageID, err := p.config.ImageStore.Create(config) if err != nil { return false, err } manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo) if err != nil { return false, err } if manifestDigest != "" { progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String()) } oldTagImageID, err := p.config.ReferenceStore.Get(ref) if err == nil && oldTagImageID == imageID { return false, nil } if canonical, ok := ref.(reference.Canonical); ok { if err = p.config.ReferenceStore.AddDigest(canonical, imageID, true); err != nil { return false, err } } else if err = p.config.ReferenceStore.AddTag(ref, imageID, true); err != nil { return false, err } return true, nil }