// GetRemoteTags retrieves all tags from the given repository. It queries each // of the registries supplied in the registries argument, and returns data from // the first one that answers the query successfully. It returns a map with // tag names as the keys and image IDs as the values. func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) { repository := repositoryRef.RemoteName() if strings.Count(repository, "/") == 0 { // This will be removed once the registry supports auto-resolution on // the "library" namespace repository = "library/" + repository } for _, host := range registries { endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) res, err := r.client.Get(endpoint) if err != nil { return nil, err } logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) defer res.Body.Close() if res.StatusCode == 404 { return nil, ErrRepoNotFound } if res.StatusCode != 200 { continue } result := make(map[string]string) if err := json.NewDecoder(res.Body).Decode(&result); err != nil { return nil, err } return result, nil } return nil, fmt.Errorf("Could not reach any registry endpoint") }
// GetTag returns the tag associated with the given reference name. func GetTag(ref reference.Named) string { tag := DefaultTag if ref, ok := ref.(reference.NamedTagged); ok { tag = ref.Tag() } return tag }
// ReferencesByName returns the references for a given repository name. // If there are no references known for this repository name, // ReferencesByName returns nil. func (store *repoCache) ReferencesByName(ref reference.Named) []Association { defer trace.End(trace.Begin("")) store.mu.RLock() defer store.mu.RUnlock() repository, exists := store.Repositories[ref.Name()] if !exists { return nil } var associations []Association for refStr, refID := range repository { ref, err := reference.ParseNamed(refStr) if err != nil { // Should never happen return nil } associations = append(associations, Association{ Ref: ref, ImageID: refID, }) } sort.Sort(lexicalAssociations(associations)) return associations }
// 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 (t *mockTagAdder) AddTag(ref reference.Named, id digest.Digest, force bool) error { if t.refs == nil { t.refs = make(map[string]string) } t.refs[ref.String()] = id.String() return nil }
// Looks up image by reference.Named func (c *ImageCache) GetImageByNamed(named reference.Named) (*metadata.ImageConfig, error) { c.m.RLock() defer c.m.RUnlock() if CacheNotUpdated { return nil, ErrCacheNotUpdated } var config *metadata.ImageConfig if tagged, ok := named.(reference.NamedTagged); ok { taggedName := tagged.Name() + ":" + tagged.Tag() config = c.cacheByName[taggedName] } else { // First try just the name. config = c.cacheByName[named.Name()] if config == nil { // try with the default docker tag taggedName := named.Name() + ":" + reference.DefaultTag config = c.cacheByName[taggedName] } } return config, nil }
func translatePullError(err error, ref reference.Named) error { switch v := err.(type) { case errcode.Errors: if len(v) != 0 { for _, extra := range v[1:] { logrus.Infof("Ignoring extra error returned from registry: %v", extra) } return translatePullError(v[0], ref) } case errcode.Error: var newErr error switch v.Code { case errcode.ErrorCodeDenied: // ErrorCodeDenied is used when access to the repository was denied newErr = errors.Errorf("repository %s not found: does not exist or no read access", ref.Name()) case v2.ErrorCodeManifestUnknown: newErr = errors.Errorf("manifest for %s not found", ref.String()) case v2.ErrorCodeNameUnknown: newErr = errors.Errorf("repository %s not found", ref.Name()) } if newErr != nil { logrus.Infof("Translating %q to %q", err, newErr) return newErr } case xfer.DoNotRetry: return translatePullError(v.Err, ref) } return err }
// TagImageWithReference adds the given reference to the image ID provided. func (daemon *Daemon) TagImageWithReference(imageID image.ID, newTag reference.Named) error { if err := daemon.referenceStore.AddTag(newTag, imageID.Digest(), true); err != nil { return err } daemon.LogImageEvent(imageID.String(), newTag.String(), "tag") return nil }
// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) { index, err := newIndexInfo(config, name.Hostname()) if err != nil { return nil, err } official := !strings.ContainsRune(name.Name(), '/') return &RepositoryInfo{name, index, official}, nil }
func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error { progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.FullName()) tagged, isTagged := ref.(reference.NamedTagged) repoData, err := p.session.GetRepositoryData(p.repoInfo) if err != nil { if strings.Contains(err.Error(), "HTTP code: 404") { if isTagged { return fmt.Errorf("Error: image %s:%s not found", p.repoInfo.RemoteName(), tagged.Tag()) } return fmt.Errorf("Error: image %s not found", p.repoInfo.RemoteName()) } // Unexpected HTTP error return err } logrus.Debug("Retrieving the tag list") var tagsList map[string]string if !isTagged { tagsList, err = p.session.GetRemoteTags(repoData.Endpoints, p.repoInfo) } else { var tagID string tagsList = make(map[string]string) tagID, err = p.session.GetRemoteTag(repoData.Endpoints, p.repoInfo, tagged.Tag()) if err == registry.ErrRepoNotFound { return fmt.Errorf("Tag %s not found in repository %s", tagged.Tag(), p.repoInfo.FullName()) } tagsList[tagged.Tag()] = tagID } if err != nil { logrus.Errorf("unable to get remote tags: %s", err) return err } for tag, id := range tagsList { repoData.ImgList[id] = ®istry.ImgData{ ID: id, Tag: tag, Checksum: "", } } layersDownloaded := false for _, imgData := range repoData.ImgList { if isTagged && imgData.Tag != tagged.Tag() { continue } err := p.downloadImage(ctx, repoData, imgData, &layersDownloaded) if err != nil { return err } } writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded) return nil }
// trustedPush handles content trust pushing of an image func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref.String(), requestPrivilege) if err != nil { return err } defer responseBody.Close() return PushTrustedReference(cli, repoInfo, ref, authConfig, responseBody) }
// TagImage creates a tag in the repository reponame, pointing to the image named // imageName. func (daemon *Daemon) TagImage(newTag reference.Named, imageName string) error { imageID, err := daemon.GetImageID(imageName) if err != nil { return err } if err := daemon.referenceStore.AddTag(newTag, imageID, true); err != nil { return err } daemon.EventsService.Log("tag", newTag.String(), "") return nil }
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 (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) { var layersDownloaded bool if !reference.IsNameOnly(ref) { var err error layersDownloaded, err = p.pullV2Tag(ctx, ref) if err != nil { return err } } else { manSvc, err := p.repo.Manifests(ctx) if err != nil { return err } tags, err := manSvc.Tags() if err != nil { // If this repository doesn't exist on V2, we should // permit a fallback to V1. return allowV1Fallback(err) } // The v2 registry knows about this repository, so we will not // allow fallback to the v1 protocol even if we encounter an // error later on. p.confirmedV2 = true // This probably becomes a lot nicer after the manifest // refactor... for _, tag := range tags { tagRef, err := reference.WithTag(ref, tag) if err != nil { return err } pulledNew, err := p.pullV2Tag(ctx, tagRef) if err != nil { // Since this is the pull-all-tags case, don't // allow an error pulling a particular tag to // make the whole pull fall back to v1. if fallbackErr, ok := err.(fallbackError); ok { return fallbackErr.err } return err } // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus? layersDownloaded = layersDownloaded || pulledNew } } writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded) return nil }
func (i *Image) PullImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { defer trace.End(trace.Begin("PullImage")) log.Printf("PullImage: ref = %+v, metaheaders = %+v\n", ref, metaHeaders) var cmdArgs []string cmdArgs = append(cmdArgs, "-reference", ref.String()) if authConfig != nil { if len(authConfig.Username) > 0 { cmdArgs = append(cmdArgs, "-username", authConfig.Username) } if len(authConfig.Password) > 0 { cmdArgs = append(cmdArgs, "-password", authConfig.Password) } } portLayerServer := PortLayerServer() if portLayerServer != "" { cmdArgs = append(cmdArgs, "-host", portLayerServer) } // intruct imagec to use os.TempDir cmdArgs = append(cmdArgs, "-destination", os.TempDir()) log.Printf("PullImage: cmd = %s %+v\n", Imagec, cmdArgs) cmd := exec.Command(Imagec, cmdArgs...) cmd.Stdout = outStream cmd.Stderr = outStream // Execute err := cmd.Start() if err != nil { log.Printf("Error starting %s - %s\n", Imagec, err) return fmt.Errorf("Error starting %s - %s\n", Imagec, err) } err = cmd.Wait() if err != nil { log.Println("imagec exit code:", err) return err } client := PortLayerClient() ImageCache().Update(client) return nil }
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 }
func digestFromManifest(m *schema1.SignedManifest, name reference.Named) (digest.Digest, int, error) { payload, err := m.Payload() if err != nil { // If this failed, the signatures section was corrupted // or missing. Treat the entire manifest as the payload. payload = m.Raw } manifestDigest, err := digest.FromBytes(payload) if err != nil { logrus.Infof("Could not compute manifest digest for %s:%s : %v", name.Name(), m.Tag, err) } return manifestDigest, len(payload), nil }
func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin { p := &plugin{ PluginObj: types.Plugin{ Name: ref.Name(), ID: id, }, runtimeSourcePath: filepath.Join(pm.runRoot, id), } if ref, ok := ref.(reference.NamedTagged); ok { p.PluginObj.Tag = ref.Tag() } return p }
func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) { var refs []reference.Named if !reference.IsNameOnly(ref) { refs = []reference.Named{ref} } else { manSvc, err := p.repo.Manifests(ctx) if err != nil { return err } tags, err := manSvc.Tags() if err != nil { return err } // If this call succeeded, we can be confident that the // registry on the other side speaks the v2 protocol. p.confirmedV2 = true // This probably becomes a lot nicer after the manifest // refactor... for _, tag := range tags { tagRef, err := reference.WithTag(ref, tag) if err != nil { return err } refs = append(refs, tagRef) } } var layersDownloaded bool for _, pullRef := range refs { // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus? pulledNew, err := p.pullV2Tag(ctx, pullRef) if err != nil { return err } layersDownloaded = layersDownloaded || pulledNew } writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded) return nil }
// Looks up image by reference.Named func (ic *ICache) getImageByNamed(named reference.Named) (*metadata.ImageConfig, error) { var config *metadata.ImageConfig if tagged, ok := named.(reference.NamedTagged); ok { taggedName := tagged.Name() + ":" + tagged.Tag() config = ic.cacheByName[taggedName] } else { // First try just the name. if config, ok = ic.cacheByName[named.Name()]; !ok { // try with the default docker tag taggedName := named.Name() + ":" + reference.DefaultTag config = ic.cacheByName[taggedName] } } return copyImageConfig(config), nil }
// Get returns the imageID for a parsed reference func (store *repoCache) Get(ref reference.Named) (string, error) { defer trace.End(trace.Begin("")) ref = reference.WithDefaultTag(ref) store.mu.RLock() defer store.mu.RUnlock() repository, exists := store.Repositories[ref.Name()] if !exists || repository == nil { return "", ErrDoesNotExist } imageID, exists := repository[ref.String()] if !exists { return "", ErrDoesNotExist } return imageID, 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 }
// Delete deletes a reference from the store. It returns true if a deletion // happened, or false otherwise. func (store *repoCache) Delete(ref reference.Named, save bool) (bool, error) { defer trace.End(trace.Begin("")) ref = reference.WithDefaultTag(ref) store.mu.Lock() defer store.mu.Unlock() var err error // return code -- assume success rtc := true repoName := ref.Name() repository, exists := store.Repositories[repoName] if !exists { return false, ErrDoesNotExist } refStr := ref.String() if imageID, exists := repository[refStr]; exists { delete(repository, refStr) if len(repository) == 0 { delete(store.Repositories, repoName) } if store.referencesByIDCache[imageID] != nil { delete(store.referencesByIDCache[imageID], refStr) if len(store.referencesByIDCache[imageID]) == 0 { delete(store.referencesByIDCache, imageID) } } if layer, exists := store.images[imageID]; exists { delete(store.Layers, imageID) delete(store.images, layer) } if save { err = store.Save() if err != nil { rtc = false } } return rtc, err } return false, ErrDoesNotExist }
// PushRegistryTag pushes a tag on the registry. // Remote has the format '<user>/<repo> func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error { // "jsonify" the string revision = "\"" + revision + "\"" path := fmt.Sprintf("repositories/%s/tags/%s", remote.RemoteName(), tag) req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision)) if err != nil { return err } req.Header.Add("Content-type", "application/json") req.ContentLength = int64(len(revision)) res, err := r.client.Do(req) if err != nil { return err } res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 201 { return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote.RemoteName()), res) } return 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 verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) { // If pull by digest, then verify the manifest digest. NOTE: It is // important to do this first, before any other content validation. If the // digest cannot be verified, don't even bother with those other things. if digested, isCanonical := ref.(reference.Canonical); isCanonical { verifier, err := digest.NewDigestVerifier(digested.Digest()) if err != nil { return nil, err } if _, err := verifier.Write(signedManifest.Canonical); err != nil { return nil, err } if !verifier.Verified() { err := fmt.Errorf("image verification failed for digest %s", digested.Digest()) logrus.Error(err) return nil, err } } m = &signedManifest.Manifest if m.SchemaVersion != 1 { return nil, fmt.Errorf("unsupported schema version %d for %q", m.SchemaVersion, ref.String()) } if len(m.FSLayers) != len(m.History) { return nil, fmt.Errorf("length of history not equal to number of layers for %q", ref.String()) } if len(m.FSLayers) == 0 { return nil, fmt.Errorf("no FSLayers in manifest for %q", ref.String()) } return m, nil }
// isSingleReference returns true when all references are from one repository // and there is at most one tag. Returns false for empty input. func isSingleReference(repoRefs []reference.Named) bool { if len(repoRefs) <= 1 { return len(repoRefs) == 1 } var singleRef reference.Named canonicalRefs := map[string]struct{}{} for _, repoRef := range repoRefs { if _, isCanonical := repoRef.(reference.Canonical); isCanonical { canonicalRefs[repoRef.Name()] = struct{}{} } else if singleRef == nil { singleRef = repoRef } else { return false } } if singleRef == nil { // Just use first canonical ref singleRef = repoRefs[0] } _, ok := canonicalRefs[singleRef.Name()] return len(canonicalRefs) == 1 && ok }
func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) { pd, err := distribution.Pull(ref, pm.registryService, metaHeader, authConfig) if err != nil { logrus.Debugf("error in distribution.Pull(): %v", err) return nil, err } if err := distribution.WritePullData(pd, filepath.Join(pm.libRoot, pluginID), true); err != nil { logrus.Debugf("error in distribution.WritePullData(): %v", err) return nil, err } tag := distribution.GetTag(ref) p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag) if err := p.InitPlugin(); err != nil { return nil, err } pm.pluginStore.Add(p) pm.pluginEventLogger(pluginID, ref.String(), "pull") return p.ComputePrivileges(), nil }
func (i *Image) PullImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { defer trace.End(trace.Begin(ref.String())) log.Debugf("PullImage: ref = %+v, metaheaders = %+v\n", ref, metaHeaders) options := imagec.Options{ Destination: os.TempDir(), Reference: ref.String(), Timeout: imagec.DefaultHTTPTimeout, Outstream: outStream, } if authConfig != nil { if len(authConfig.Username) > 0 { options.Username = authConfig.Username } if len(authConfig.Password) > 0 { options.Password = authConfig.Password } } portLayerServer := PortLayerServer() if portLayerServer != "" { options.Host = portLayerServer } insecureRegistries := InsecureRegistries() for _, registry := range insecureRegistries { if registry == ref.Hostname() { options.InsecureAllowHTTP = true break } } log.Infof("PullImage: reference: %s, %s, portlayer: %#v", options.Reference, options.Host, portLayerServer) ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter()) err := ic.PullImage() if err != nil { return err } return nil }
func verifyManifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) { // If pull by digest, then verify the manifest digest. NOTE: It is // important to do this first, before any other content validation. If the // digest cannot be verified, don't even bother with those other things. if digested, isCanonical := ref.(reference.Canonical); isCanonical { verifier, err := digest.NewDigestVerifier(digested.Digest()) if err != nil { return nil, err } payload, err := signedManifest.Payload() if err != nil { // If this failed, the signatures section was corrupted // or missing. Treat the entire manifest as the payload. payload = signedManifest.Raw } if _, err := verifier.Write(payload); err != nil { return nil, err } if !verifier.Verified() { err := fmt.Errorf("image verification failed for digest %s", digested.Digest()) logrus.Error(err) return nil, err } var verifiedManifest schema1.Manifest if err = json.Unmarshal(payload, &verifiedManifest); err != nil { return nil, err } m = &verifiedManifest } else { m = &signedManifest.Manifest } if m.SchemaVersion != 1 { return nil, fmt.Errorf("unsupported schema version %d for %q", m.SchemaVersion, ref.String()) } if len(m.FSLayers) != len(m.History) { return nil, fmt.Errorf("length of history not equal to number of layers for %q", ref.String()) } if len(m.FSLayers) == 0 { return nil, fmt.Errorf("no FSLayers in manifest for %q", ref.String()) } return m, nil }