// Add adds an image to the image cache func (ic *ICache) Add(imageConfig *metadata.ImageConfig) { defer trace.End(trace.Begin("")) ic.m.Lock() defer ic.m.Unlock() // Normalize the name stored in imageConfig using Docker's reference code ref, err := reference.WithName(imageConfig.Name) if err != nil { log.Errorf("Tried to create reference from %s: %s", imageConfig.Name, err.Error()) return } imageID := prefixImageID(imageConfig.ImageID) ic.iDIndex.Add(imageConfig.ImageID) ic.cacheByID[imageID] = imageConfig for _, tag := range imageConfig.Tags { ref, err = reference.WithTag(ref, tag) if err != nil { log.Errorf("Tried to create tagged reference from %s and tag %s: %s", imageConfig.Name, tag, err.Error()) return } ic.cacheByName[imageConfig.Reference] = imageConfig } }
// PullImage initiates a pull operation. image is the repository name to pull, and // tag may be either empty, or indicate a specific tag to pull. func (daemon *Daemon) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { // Special case: "pull -a" may send an image name with a // trailing :. This is ugly, but let's not break API // compatibility. image = strings.TrimSuffix(image, ":") ref, err := reference.ParseNamed(image) if err != nil { return err } if tag != "" { // The "tag" could actually be a digest. var dgst digest.Digest dgst, err = digest.ParseDigest(tag) if err == nil { ref, err = reference.WithDigest(ref, dgst) } else { ref, err = reference.WithTag(ref, tag) } if err != nil { return err } } return daemon.pullImageWithReference(ctx, ref, metaHeaders, authConfig, outStream) }
// sanitizeRepoAndTags parses the raw "t" parameter received from the client // to a slice of repoAndTag. // It also validates each repoName and tag. func sanitizeRepoAndTags(names []string) ([]reference.Named, error) { var ( repoAndTags []reference.Named // This map is used for deduplicating the "-t" parameter. uniqNames = make(map[string]struct{}) ) for _, repo := range names { if repo == "" { continue } ref, err := reference.ParseNamed(repo) if err != nil { return nil, err } ref = reference.WithDefaultTag(ref) if _, isCanonical := ref.(reference.Canonical); isCanonical { return nil, errors.New("build tag cannot contain a digest") } if _, isTagged := ref.(reference.NamedTagged); !isTagged { ref, err = reference.WithTag(ref, reference.DefaultTag) } nameWithTag := ref.String() if _, exists := uniqNames[nameWithTag]; !exists { uniqNames[nameWithTag] = struct{}{} repoAndTags = append(repoAndTags, ref) } } return repoAndTags, nil }
func pullImage(client client.APIClient, service *Service, image string) error { distributionRef, err := reference.ParseNamed(image) if err != nil { return err } switch distributionRef.(type) { case reference.Canonical: case reference.NamedTagged: default: distributionRef, err = reference.WithTag(distributionRef, "latest") if err != nil { return err } } repoInfo, err := registry.ParseRepositoryInfo(distributionRef) if err != nil { return err } authConfig := types.AuthConfig{} if service.context.ConfigFile != nil && repoInfo != nil && repoInfo.Index != nil { authConfig = registry.ResolveAuthConfig(service.context.ConfigFile.AuthConfigs, repoInfo.Index) } encodedAuth, err := encodeAuthToBase64(authConfig) if err != nil { return err } options := types.ImagePullOptions{ RegistryAuth: encodedAuth, } responseBody, err := client.ImagePull(context.Background(), distributionRef.String(), options) if err != nil { logrus.Errorf("Failed to pull image %s: %v", image, err) return err } defer responseBody.Close() var writeBuff io.Writer = os.Stdout outFd, isTerminalOut := term.GetFdInfo(os.Stdout) err = jsonmessage.DisplayJSONMessagesStream(responseBody, writeBuff, outFd, isTerminalOut, nil) if err != nil { if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 } fmt.Fprintf(os.Stderr, "%s", writeBuff) return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code) } } return err }
func testTokenPassThru(t *testing.T, ts *httptest.Server) { tmp, err := testDirectory("") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmp) uri, err := url.Parse(ts.URL) if err != nil { t.Fatalf("could not parse url from test server: %v", err) } endpoint := registry.APIEndpoint{ Mirror: false, URL: uri, Version: 2, Official: false, TrimHostname: false, TLSConfig: nil, //VersionHeader: "verheader", } n, _ := reference.ParseNamed("testremotename") repoInfo := ®istry.RepositoryInfo{ Named: n, Index: ®istrytypes.IndexInfo{ Name: "testrepo", Mirrors: nil, Secure: false, Official: false, }, Official: false, } imagePullConfig := &ImagePullConfig{ Config: Config{ MetaHeaders: http.Header{}, AuthConfig: &types.AuthConfig{ RegistryToken: secretRegistryToken, }, }, Schema2Types: ImageTypes, } puller, err := newPuller(endpoint, repoInfo, imagePullConfig) if err != nil { t.Fatal(err) } p := puller.(*v2Puller) ctx := context.Background() p.repo, _, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") if err != nil { t.Fatal(err) } logrus.Debug("About to pull") // We expect it to fail, since we haven't mock'd the full registry exchange in our handler above tag, _ := reference.WithTag(n, "tag_goes_here") _ = p.pullV2Repository(ctx, tag) }
func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOutput progress.Output) error { legacyLoadedMap := make(map[string]image.ID) dirs, err := ioutil.ReadDir(tmpDir) if err != nil { return err } // every dir represents an image for _, d := range dirs { if d.IsDir() { if err := l.legacyLoadImage(d.Name(), tmpDir, legacyLoadedMap, progressOutput); err != nil { return err } } } // load tags from repositories file repositoriesPath, err := safePath(tmpDir, legacyRepositoriesFileName) if err != nil { return err } repositoriesFile, err := os.Open(repositoriesPath) if err != nil { if !os.IsNotExist(err) { return err } return repositoriesFile.Close() } defer repositoriesFile.Close() repositories := make(map[string]map[string]string) if err := json.NewDecoder(repositoriesFile).Decode(&repositories); err != nil { return err } for name, tagMap := range repositories { for tag, oldID := range tagMap { imgID, ok := legacyLoadedMap[oldID] if !ok { return fmt.Errorf("invalid target ID: %v", oldID) } named, err := reference.WithName(name) if err != nil { return err } ref, err := reference.WithTag(named, tag) if err != nil { return err } l.setLoadedTag(ref, imgID, outStream) } } return nil }
func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } if err := httputils.ParseForm(r); err != nil { return err } authConfig := &types.AuthConfig{} authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { // the new format is to handle the authConfig as a header authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { // to increase compatibility to existing api it is defaulting to be empty authConfig = &types.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) } } ref, err := reference.ParseNamed(vars["name"]) if err != nil { return err } tag := r.Form.Get("tag") if tag != "" { // Push by digest is not supported, so only tags are supported. ref, err = reference.WithTag(ref, tag) if err != nil { return err } } output := ioutils.NewWriteFlusher(w) defer output.Close() w.Header().Set("Content-Type", "application/json") if err := s.daemon.PushImage(ref, metaHeaders, authConfig, output); err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
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 (p *v1Puller) downloadImage(ctx context.Context, repoData *registry.RepositoryData, img *registry.ImgData, layersDownloaded *bool) error { if img.Tag == "" { logrus.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) return nil } localNameRef, err := reference.WithTag(p.repoInfo, img.Tag) if err != nil { retErr := fmt.Errorf("Image (id: %s) has invalid tag: %s", img.ID, img.Tag) logrus.Debug(retErr.Error()) return retErr } if err := v1.ValidateID(img.ID); err != nil { return err } progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s", img.Tag, p.repoInfo.FullName()) success := false var lastErr error for _, ep := range p.repoInfo.Index.Mirrors { ep += "v1/" progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, p.repoInfo.FullName(), ep)) if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil { // Don't report errors when pulling from mirrors. logrus.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, p.repoInfo.FullName(), ep, err) continue } success = true break } if !success { for _, ep := range repoData.Endpoints { progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s, endpoint: %s", img.Tag, p.repoInfo.FullName(), ep) if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil { // It's not ideal that only the last error is returned, it would be better to concatenate the errors. // As the error is also given to the output stream the user will see the error. lastErr = err progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, p.repoInfo.FullName(), ep, err) continue } success = true break } } if !success { err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, p.repoInfo.FullName(), lastErr) progress.Update(p.config.ProgressOutput, stringid.TruncateID(img.ID), err.Error()) return err } return nil }
// PushImage initiates a push operation on the repository named localName. func (daemon *Daemon) PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { ref, err := reference.ParseNamed(image) if err != nil { return err } if tag != "" { // Push by digest is not supported, so only tags are supported. ref, err = reference.WithTag(ref, tag) if err != nil { return err } } // Include a buffer so that slow client connections don't affect // transfer performance. progressChan := make(chan progress.Progress, 100) writesDone := make(chan struct{}) ctx, cancelFunc := context.WithCancel(ctx) go func() { writeDistributionProgress(cancelFunc, outStream, progressChan) close(writesDone) }() imagePushConfig := &distribution.ImagePushConfig{ Config: distribution.Config{ MetaHeaders: metaHeaders, AuthConfig: authConfig, ProgressOutput: progress.ChanOutput(progressChan), RegistryService: daemon.RegistryService, ImageEventLogger: daemon.LogImageEvent, MetadataStore: daemon.distributionMetadataStore, ImageStore: distribution.NewImageConfigStoreFromStore(daemon.imageStore), ReferenceStore: daemon.referenceStore, }, ConfigMediaType: schema2.MediaTypeImageConfig, LayerStore: distribution.NewLayerProviderFromStore(daemon.layerStore), TrustKey: daemon.trustKey, UploadManager: daemon.uploadManager, } err = distribution.Push(ctx, ref, imagePushConfig) close(progressChan) <-writesDone return err }
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 }
// TagImage creates the tag specified by newTag, pointing to the image named // imageName (alternatively, imageName can also be an image ID). func (daemon *Daemon) TagImage(imageName, repository, tag string) error { imageID, err := daemon.GetImageID(imageName) if err != nil { return err } newTag, err := reference.WithName(repository) if err != nil { return err } if tag != "" { if newTag, err = reference.WithTag(newTag, tag); err != nil { return err } } return daemon.TagImageWithReference(imageID, newTag) }
// AddImage adds an image to the image cache func (ic *ICache) AddImage(imageConfig *metadata.ImageConfig) { ic.m.Lock() defer ic.m.Unlock() var imageID string // Don't assume the image id in image has "sha256:<id> as format. We store it in // this fomat to make it easier to lookup by digest if strings.HasPrefix(imageConfig.ImageID, "sha") { imageID = imageConfig.ImageID } else { imageID = "sha256:" + imageConfig.ImageID } // add image id to truncindex ic.idIndex.Add(imageConfig.ImageID) ic.cacheByID[imageID] = imageConfig // Normalize the name stored in imageConfig using Docker's reference code ref, err := reference.WithName(imageConfig.Name) if err != nil { log.Errorf("Tried to create reference from %s: %s", imageConfig.Name, err.Error()) return } for _, tag := range imageConfig.Tags { ref, err = reference.WithTag(ref, tag) if err != nil { log.Errorf("Tried to create tagged reference from %s and tag %s: %s", imageConfig.Name, tag, err.Error()) return } if tagged, ok := ref.(reference.NamedTagged); ok { taggedName := fmt.Sprintf("%s:%s", tagged.Name(), tagged.Tag()) ic.cacheByName[taggedName] = imageConfig } else { ic.cacheByName[ref.Name()] = imageConfig } } }
func (daemon *Daemon) CmdImagePush(repo, tag string, authConfig *types.AuthConfig, metaHeaders map[string][]string, output io.Writer) error { // Special case: "pull -a" may send an image name with a // trailing :. This is ugly, but let's not break API // compatibility. repo = strings.TrimSuffix(repo, ":") var ref reference.Named ref, err := reference.ParseNamed(repo) if err != nil { return err } if tag != "" { ref, err = reference.WithTag(ref, tag) if err != nil { return err } } return daemon.Daemon.PushImage(ref, metaHeaders, authConfig, output) }
func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } repo := r.Form.Get("repo") tag := r.Form.Get("tag") newTag, err := reference.WithName(repo) if err != nil { return err } if tag != "" { if newTag, err = reference.WithTag(newTag, tag); err != nil { return err } } if err := s.daemon.TagImage(newTag, vars["name"]); err != nil { return err } w.WriteHeader(http.StatusCreated) return nil }
// parseRemoteRef parses the remote reference into a reference.Named // returning the tag associated with the reference. In the case the // given reference string includes both digest and tag, the returned // reference will have the digest without the tag, but the tag will // be returned. func parseRemoteRef(remote string) (reference.Named, string, error) { // Parse remote reference, supporting remotes with name and tag // NOTE: Using distribution reference to handle references // containing both a name and digest remoteRef, err := distreference.ParseNamed(remote) if err != nil { return nil, "", err } var tag string if t, ok := remoteRef.(distreference.Tagged); ok { tag = t.Tag() } // Convert distribution reference to docker reference // TODO: remove when docker reference changes reconciled upstream ref, err := reference.WithName(remoteRef.Name()) if err != nil { return nil, "", err } if d, ok := remoteRef.(distreference.Digested); ok { ref, err = reference.WithDigest(ref, d.Digest()) if err != nil { return nil, "", err } } else if tag != "" { ref, err = reference.WithTag(ref, tag) if err != nil { return nil, "", err } } else { ref = reference.WithDefaultTag(ref) } return ref, tag, nil }
// ImportImage imports an image, getting the archived layer data either from // inConfig (if src is "-"), or from a URI specified in src. Progress output is // written to outStream. Repository and tag names can optionally be given in // the repo and tag arguments, respectively. func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error { var ( sf = streamformatter.NewJSONStreamFormatter() rc io.ReadCloser resp *http.Response newRef reference.Named ) if repository != "" { var err error newRef, err = reference.ParseNamed(repository) if err != nil { return err } if _, isCanonical := newRef.(reference.Canonical); isCanonical { return errors.New("cannot import digest reference") } if tag != "" { newRef, err = reference.WithTag(newRef, tag) if err != nil { return err } } } config, err := dockerfile.BuildFromConfig(&container.Config{}, changes) if err != nil { return err } if src == "-" { rc = inConfig } else { inConfig.Close() u, err := url.Parse(src) if err != nil { return err } if u.Scheme == "" { u.Scheme = "http" u.Host = src u.Path = "" } outStream.Write(sf.FormatStatus("", "Downloading from %s", u)) resp, err = httputils.Download(u.String()) if err != nil { return err } progressOutput := sf.NewProgressOutput(outStream, true) rc = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing") } defer rc.Close() if len(msg) == 0 { msg = "Imported from " + src } inflatedLayerData, err := archive.DecompressStream(rc) if err != nil { return err } // TODO: support windows baselayer? l, err := daemon.layerStore.Register(inflatedLayerData, "") if err != nil { return err } defer layer.ReleaseAndLog(daemon.layerStore, l) created := time.Now().UTC() imgConfig, err := json.Marshal(&image.Image{ V1Image: image.V1Image{ DockerVersion: dockerversion.Version, Config: config, Architecture: runtime.GOARCH, OS: runtime.GOOS, Created: created, Comment: msg, }, RootFS: &image.RootFS{ Type: "layers", DiffIDs: []layer.DiffID{l.DiffID()}, }, History: []image.History{{ Created: created, Comment: msg, }}, }) if err != nil { return err } id, err := daemon.imageStore.Create(imgConfig) if err != nil { return err } // FIXME: connect with commit code and call refstore directly if newRef != nil { if err := daemon.TagImageWithReference(id, newRef); err != nil { return err } } daemon.LogImageEvent(id.String(), id.String(), "import") outStream.Write(sf.FormatStatus("", id.String())) return nil }
func migrateRefs(root, driverName string, rs refAdder, mappings map[string]image.ID) error { migrationFile := filepath.Join(root, migrationTagsFileName) if _, err := os.Lstat(migrationFile); !os.IsNotExist(err) { return err } type repositories struct { Repositories map[string]map[string]string } var repos repositories f, err := os.Open(filepath.Join(root, repositoriesFilePrefixLegacy+driverName)) if err != nil { if os.IsNotExist(err) { return nil } return err } defer f.Close() if err := json.NewDecoder(f).Decode(&repos); err != nil { return err } for name, repo := range repos.Repositories { for tag, id := range repo { if strongID, exists := mappings[id]; exists { ref, err := reference.WithName(name) if err != nil { logrus.Errorf("migrate tags: invalid name %q, %q", name, err) continue } if dgst, err := digest.ParseDigest(tag); err == nil { canonical, err := reference.WithDigest(ref, dgst) if err != nil { logrus.Errorf("migrate tags: invalid digest %q, %q", dgst, err) continue } if err := rs.AddDigest(canonical, strongID, false); err != nil { logrus.Errorf("can't migrate digest %q for %q, err: %q", ref.String(), strongID, err) } } else { tagRef, err := reference.WithTag(ref, tag) if err != nil { logrus.Errorf("migrate tags: invalid tag %q, %q", tag, err) continue } if err := rs.AddTag(tagRef, strongID, false); err != nil { logrus.Errorf("can't migrate tag %q for %q, err: %q", ref.String(), strongID, err) } } logrus.Infof("migrated tag %s:%s to point to %s", name, tag, strongID) } } } mf, err := os.Create(migrationFile) if err != nil { return err } mf.Close() return nil }
func (l *tarexporter) parseNames(names []string) (map[image.ID]*imageDescriptor, error) { imgDescr := make(map[image.ID]*imageDescriptor) addAssoc := func(id image.ID, ref reference.Named) { if _, ok := imgDescr[id]; !ok { imgDescr[id] = &imageDescriptor{} } if ref != nil { var tagged reference.NamedTagged if _, ok := ref.(reference.Canonical); ok { return } var ok bool if tagged, ok = ref.(reference.NamedTagged); !ok { var err error if tagged, err = reference.WithTag(ref, reference.DefaultTag); err != nil { return } } for _, t := range imgDescr[id].refs { if tagged.String() == t.String() { return } } imgDescr[id].refs = append(imgDescr[id].refs, tagged) } } for _, name := range names { id, ref, err := reference.ParseIDOrReference(name) if err != nil { return nil, err } if id != "" { _, err := l.is.Get(image.ID(id)) if err != nil { return nil, err } addAssoc(image.ID(id), nil) continue } if ref.Name() == string(digest.Canonical) { imgID, err := l.is.Search(name) if err != nil { return nil, err } addAssoc(imgID, nil) continue } if reference.IsNameOnly(ref) { assocs := l.rs.ReferencesByName(ref) for _, assoc := range assocs { addAssoc(assoc.ImageID, assoc.Ref) } if len(assocs) == 0 { imgID, err := l.is.Search(name) if err != nil { return nil, err } addAssoc(imgID, nil) } continue } var imgID image.ID if imgID, err = l.rs.Get(ref); err != nil { return nil, err } addAssoc(imgID, ref) } return imgDescr, nil }
func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege apiclient.RequestPrivilegeFunc) error { var refs []target notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig) if err != nil { fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) return err } if ref.String() == "" { // List all targets targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole) if err != nil { return notaryError(repoInfo.FullName(), err) } for _, tgt := range targets { t, err := convertTarget(tgt.Target) if err != nil { fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name()) continue } refs = append(refs, t) } } else { t, err := notaryRepo.GetTargetByName(ref.String(), releasesRole, data.CanonicalTargetsRole) if err != nil { return notaryError(repoInfo.FullName(), err) } r, err := convertTarget(t.Target) if err != nil { return err } refs = append(refs, r) } for i, r := range refs { displayTag := r.reference.String() if displayTag != "" { displayTag = ":" + displayTag } fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest) if err := cli.imagePullPrivileged(authConfig, repoInfo.Name(), r.digest.String(), requestPrivilege); err != nil { return err } // If reference is not trusted, tag by trusted reference if !r.reference.HasDigest() { tagged, err := reference.WithTag(repoInfo, r.reference.String()) if err != nil { return err } trustedRef, err := reference.WithDigest(repoInfo, r.digest) if err != nil { return err } if err := cli.tagTrusted(trustedRef, tagged); err != nil { return err } } } return nil }
func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error { // Parse name using distribution reference package to support name // containing both tag and digest. Names with both tag and digest // will be treated by the daemon as a pull by digest with // an alias for the tag (if no alias is provided). ref, err := distreference.ParseNamed(opts.name) if err != nil { return err } alias := "" if opts.alias != "" { aref, err := reference.ParseNamed(opts.alias) if err != nil { return err } aref = reference.WithDefaultTag(aref) if _, ok := aref.(reference.NamedTagged); !ok { return fmt.Errorf("invalid name: %s", opts.alias) } alias = aref.String() } ctx := context.Background() index, err := getRepoIndexFromUnnormalizedRef(ref) if err != nil { return err } remote := ref.String() _, isCanonical := ref.(distreference.Canonical) if command.IsTrusted() && !isCanonical { if alias == "" { alias = ref.String() } var nt reference.NamedTagged named, err := reference.ParseNamed(ref.Name()) if err != nil { return err } if tagged, ok := ref.(distreference.Tagged); ok { nt, err = reference.WithTag(named, tagged.Tag()) if err != nil { return err } } else { named = reference.WithDefaultTag(named) nt = named.(reference.NamedTagged) } trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService()) if err != nil { return err } remote = trusted.String() } authConfig := command.ResolveAuthConfig(ctx, dockerCli, index) encodedAuth, err := command.EncodeAuthToBase64(authConfig) if err != nil { return err } registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, index, "plugin install") options := types.PluginInstallOptions{ RegistryAuth: encodedAuth, RemoteRef: remote, Disabled: opts.disable, AcceptAllPermissions: opts.grantPerms, AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name), // TODO: Rename PrivilegeFunc, it has nothing to do with privileges PrivilegeFunc: registryAuthFunc, Args: opts.args, } responseBody, err := dockerCli.Client().PluginInstall(ctx, alias, options) if err != nil { if strings.Contains(err.Error(), "target is image") { return errors.New(err.Error() + " - Use `docker image pull`") } return err } defer responseBody.Close() if err := jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil); err != nil { return err } fmt.Fprintf(dockerCli.Out(), "Installed plugin %s\n", opts.name) // todo: return proper values from the API for this result return nil }
// Update adds new images to the cache func (c *ImageCache) Update(client *client.PortLayer) error { c.m.Lock() defer c.m.Unlock() CacheNotUpdated = true log.Debugf("Updating image cache...") host, err := guest.UUID() if host == "" { host, err = os.Hostname() } if err != nil { return fmt.Errorf("Unexpected error getting hostname: %s", err) } // attempt to create the image store if it doesn't exist store := &models.ImageStore{Name: host} _, err = client.Storage.CreateImageStore( storage.NewCreateImageStoreParams().WithBody(store), ) if err != nil { if _, ok := err.(*storage.CreateImageStoreConflict); ok { log.Debugf("Store already exists") } else { log.Debugf("Creating a store failed: %#v", err) return err } } params := storage.NewListImagesParams().WithStoreName(host) layers, err := client.Storage.ListImages(params) if err != nil { return fmt.Errorf("Failed to retrieve image list from portlayer: %s", err) } for _, layer := range layers.Payload { imageConfig := &metadata.ImageConfig{} if err := json.Unmarshal([]byte(layer.Metadata["metaData"]), imageConfig); err != nil { derr.NewErrorWithStatusCode(fmt.Errorf("Failed to unmarshal image config: %s", err), http.StatusInternalServerError) } if imageConfig.ImageID != "" { var imageID string // Don't assume the image id in image has "sha256:<id> as format. We store it in // this fomat to make it easier to lookup by digest if strings.HasPrefix(imageConfig.ImageID, "sha") { imageID = imageConfig.ImageID } else { imageID = "sha256:" + imageConfig.ImageID } c.cacheByID[imageID] = imageConfig // Normalize the name stored in imageConfig using Docker's reference code ref, err := reference.WithName(imageConfig.Name) if err != nil { log.Errorf("Tried to create reference from %s: %s", imageConfig.Name, err.Error()) continue } for id := range imageConfig.Tags { tag := imageConfig.Tags[id] ref, err = reference.WithTag(ref, tag) if err != nil { log.Errorf("Tried to create tagged reference from %s and tag %s: %s", imageConfig.Name, tag, err.Error()) continue } if tagged, ok := ref.(reference.NamedTagged); ok { taggedName := fmt.Sprintf("%s:%s", tagged.Name(), tagged.Tag()) c.cacheByName[taggedName] = imageConfig } else { c.cacheByName[ref.Name()] = imageConfig } } } } CacheNotUpdated = false return nil }
func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error { type graphDriverStore interface { GraphDriver() graphdriver.Driver } gds, ok := ls.(graphDriverStore) if !ok { return nil } driver := gds.GraphDriver() wd, ok := driver.(*windows.Driver) if !ok { return nil } imageInfos, err := wd.GetCustomImageInfos() if err != nil { return err } // Convert imageData to valid image configuration for _, info := range imageInfos { name := strings.ToLower(info.Name) type registrar interface { RegisterDiffID(graphID string, size int64) (layer.Layer, error) } r, ok := ls.(registrar) if !ok { return errors.New("Layerstore doesn't support RegisterDiffID") } if _, err := r.RegisterDiffID(info.ID, info.Size); err != nil { return err } // layer is intentionally not released rootFS := image.NewRootFSWithBaseLayer(filepath.Base(info.Path)) // Create history for base layer config, err := json.Marshal(&image.Image{ V1Image: image.V1Image{ DockerVersion: dockerversion.Version, Architecture: runtime.GOARCH, OS: runtime.GOOS, Created: info.CreatedTime, }, RootFS: rootFS, History: []image.History{}, OSVersion: info.OSVersion, OSFeatures: info.OSFeatures, }) named, err := reference.ParseNamed(name) if err != nil { return err } ref, err := reference.WithTag(named, info.Version) if err != nil { return err } id, err := is.Create(config) if err != nil { logrus.Warnf("Failed to restore custom image %s with error: %s.", name, err) logrus.Warnf("Skipping image %s...", name) continue } if err := rs.AddTag(ref, id, true); err != nil { return err } logrus.Debugf("Registered base layer %s as %s", ref, id) } return nil }
func (mf *v1ManifestFetcher) fetchWithSession(ctx context.Context, ref reference.Named) (*types.ImageInspect, error) { repoData, err := mf.session.GetRepositoryData(mf.repoInfo) if err != nil { if strings.Contains(err.Error(), "HTTP code: 404") { return nil, fmt.Errorf("Error: image %s not found", mf.repoInfo.RemoteName()) } // Unexpected HTTP error return nil, err } var tagsList map[string]string tagsList, err = mf.session.GetRemoteTags(repoData.Endpoints, mf.repoInfo) if err != nil { logrus.Errorf("unable to get remote tags: %s", err) return nil, err } logrus.Debugf("Retrieving the tag list") tagged, isTagged := ref.(reference.NamedTagged) var tagID, tag string if isTagged { tag = tagged.Tag() tagsList[tagged.Tag()] = tagID } else { ref, err = reference.WithTag(ref, reference.DefaultTag) if err != nil { return nil, err } tagged, _ := ref.(reference.NamedTagged) tag = tagged.Tag() tagsList[tagged.Tag()] = tagID } tagID, err = mf.session.GetRemoteTag(repoData.Endpoints, mf.repoInfo, tag) if err == registry.ErrRepoNotFound { return nil, fmt.Errorf("Tag %s not found in repository %s", tag, mf.repoInfo.FullName()) } if err != nil { logrus.Errorf("unable to get remote tags: %s", err) return nil, err } tagList := []string{} for tag := range tagsList { tagList = append(tagList, tag) } img := repoData.ImgList[tagID] var pulledImg *image.Image for _, ep := range mf.repoInfo.Index.Mirrors { if pulledImg, err = mf.pullImageJSON(img.ID, ep, repoData.Tokens); err != nil { // Don't report errors when pulling from mirrors. logrus.Debugf("Error pulling image json of %s:%s, mirror: %s, %s", mf.repoInfo.FullName(), img.Tag, ep, err) continue } break } if pulledImg == nil { for _, ep := range repoData.Endpoints { if pulledImg, err = mf.pullImageJSON(img.ID, ep, repoData.Tokens); err != nil { // It's not ideal that only the last error is returned, it would be better to concatenate the errors. logrus.Infof("Error pulling image json of %s:%s, endpoint: %s, %v", mf.repoInfo.FullName(), img.Tag, ep, err) continue } break } } if err != nil { return nil, fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, mf.repoInfo.FullName(), err) } if pulledImg == nil { return nil, fmt.Errorf("No such image %s:%s", mf.repoInfo.FullName(), tag) } return makeImageInspect(pulledImg, tag, "", tagList), nil }
func (mf *v2ManifestFetcher) fetchWithRepository(ctx context.Context, ref reference.Named) (*imageInspect, error) { var ( manifest distribution.Manifest tagOrDigest string // Used for logging/progress only tagList = []string{} ) manSvc, err := mf.repo.Manifests(ctx) if err != nil { return nil, err } if _, isTagged := ref.(reference.NamedTagged); !isTagged { ref, err = reference.WithTag(ref, reference.DefaultTag) if err != nil { return nil, err } } if tagged, isTagged := ref.(reference.NamedTagged); isTagged { // NOTE: not using TagService.Get, since it uses HEAD requests // against the manifests endpoint, which are not supported by // all registry versions. manifest, err = manSvc.Get(ctx, "", client.WithTag(tagged.Tag())) if err != nil { return nil, allowV1Fallback(err) } tagOrDigest = tagged.Tag() } else if digested, isDigested := ref.(reference.Canonical); isDigested { manifest, err = manSvc.Get(ctx, digested.Digest()) if err != nil { return nil, err } tagOrDigest = digested.Digest().String() } else { return nil, fmt.Errorf("internal error: reference has neither a tag nor a digest: %s", ref.String()) } if manifest == nil { return nil, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest) } // If manSvc.Get succeeded, we can be confident that the registry on // the other side speaks the v2 protocol. mf.confirmedV2 = true tagList, err = mf.repo.Tags(ctx).All(ctx) if err != nil { // If this repository doesn't exist on V2, we should // permit a fallback to V1. return nil, allowV1Fallback(err) } var ( image *image.Image manifestDigest digest.Digest ) switch v := manifest.(type) { case *schema1.SignedManifest: image, manifestDigest, err = mf.pullSchema1(ctx, ref, v) if err != nil { return nil, err } case *schema2.DeserializedManifest: image, manifestDigest, err = mf.pullSchema2(ctx, ref, v) if err != nil { return nil, err } case *manifestlist.DeserializedManifestList: image, manifestDigest, err = mf.pullManifestList(ctx, ref, v) if err != nil { return nil, err } default: return nil, errors.New("unsupported manifest format") } // TODO(runcom) //var showTags bool //if reference.IsNameOnly(ref) { //showTags = true //logrus.Debug("Using default tag: latest") //ref = reference.WithDefaultTag(ref) //} //_ = showTags return makeImageInspect(image, tagOrDigest, manifestDigest, tagList), nil }
func TestTokenPassThru(t *testing.T) { authConfig := &types.AuthConfig{ RegistryToken: "mysecrettoken", } gotToken := false handler := func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.Header.Get("Authorization"), authConfig.RegistryToken) { logrus.Debug("Detected registry token in auth header") gotToken = true } if r.RequestURI == "/v2/" { w.Header().Set("WWW-Authenticate", `Bearer realm="foorealm"`) w.WriteHeader(401) } } ts := httptest.NewServer(http.HandlerFunc(handler)) defer ts.Close() tmp, err := utils.TestDirectory("") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmp) endpoint := registry.APIEndpoint{ Mirror: false, URL: ts.URL, Version: 2, Official: false, TrimHostname: false, TLSConfig: nil, //VersionHeader: "verheader", } n, _ := reference.ParseNamed("testremotename") repoInfo := ®istry.RepositoryInfo{ Named: n, Index: ®istrytypes.IndexInfo{ Name: "testrepo", Mirrors: nil, Secure: false, Official: false, }, Official: false, } imagePullConfig := &ImagePullConfig{ MetaHeaders: http.Header{}, AuthConfig: authConfig, } puller, err := newPuller(endpoint, repoInfo, imagePullConfig) if err != nil { t.Fatal(err) } p := puller.(*v2Puller) ctx := context.Background() p.repo, _, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") if err != nil { t.Fatal(err) } logrus.Debug("About to pull") // We expect it to fail, since we haven't mock'd the full registry exchange in our handler above tag, _ := reference.WithTag(n, "tag_goes_here") _ = p.pullV2Repository(ctx, tag) if !gotToken { t.Fatal("Failed to receive registry token") } }
// Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository. func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (string, error) { container, err := daemon.GetContainer(name) if err != nil { return "", err } // It is not possible to commit a running container on Windows if runtime.GOOS == "windows" && container.IsRunning() { return "", fmt.Errorf("Windows does not support commit of a running container") } if c.Pause && !container.IsPaused() { daemon.containerPause(container) defer daemon.containerUnpause(container) } newConfig, err := dockerfile.BuildFromConfig(c.Config, c.Changes) if err != nil { return "", err } if c.MergeConfigs { if err := merge(newConfig, container.Config); err != nil { return "", err } } rwTar, err := daemon.exportContainerRw(container) if err != nil { return "", err } defer func() { if rwTar != nil { rwTar.Close() } }() var history []image.History rootFS := image.NewRootFS() osVersion := "" var osFeatures []string if container.ImageID != "" { img, err := daemon.imageStore.Get(container.ImageID) if err != nil { return "", err } history = img.History rootFS = img.RootFS osVersion = img.OSVersion osFeatures = img.OSFeatures } l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID()) if err != nil { return "", err } defer layer.ReleaseAndLog(daemon.layerStore, l) h := image.History{ Author: c.Author, Created: time.Now().UTC(), CreatedBy: strings.Join(container.Config.Cmd, " "), Comment: c.Comment, EmptyLayer: true, } if diffID := l.DiffID(); layer.DigestSHA256EmptyTar != diffID { h.EmptyLayer = false rootFS.Append(diffID) } history = append(history, h) config, err := json.Marshal(&image.Image{ V1Image: image.V1Image{ DockerVersion: dockerversion.Version, Config: newConfig, Architecture: runtime.GOARCH, OS: runtime.GOOS, Container: container.ID, ContainerConfig: *container.Config, Author: c.Author, Created: h.Created, }, RootFS: rootFS, History: history, OSFeatures: osFeatures, OSVersion: osVersion, }) if err != nil { return "", err } id, err := daemon.imageStore.Create(config) if err != nil { return "", err } if container.ImageID != "" { if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil { return "", err } } if c.Repo != "" { newTag, err := reference.WithName(c.Repo) // todo: should move this to API layer if err != nil { return "", err } if c.Tag != "" { if newTag, err = reference.WithTag(newTag, c.Tag); err != nil { return "", err } } if err := daemon.TagImageWithReference(id, newTag); err != nil { return "", err } } attributes := map[string]string{ "comment": c.Comment, } daemon.LogContainerEventWithAttributes(container, "commit", attributes) return id.String(), nil }
// TrustedPull handles content trust pulling of an image func (cli *DockerCli) TrustedPull(ctx context.Context, repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { var refs []target notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig, "pull") if err != nil { fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) return err } if ref.String() == "" { // List all targets targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole) if err != nil { return notaryError(repoInfo.FullName(), err) } for _, tgt := range targets { t, err := convertTarget(tgt.Target) if err != nil { fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name()) continue } // Only list tags in the top level targets role or the releases delegation role - ignore // all other delegation roles if tgt.Role != releasesRole && tgt.Role != data.CanonicalTargetsRole { continue } refs = append(refs, t) } if len(refs) == 0 { return notaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName())) } } else { t, err := notaryRepo.GetTargetByName(ref.String(), releasesRole, data.CanonicalTargetsRole) if err != nil { return notaryError(repoInfo.FullName(), err) } // Only get the tag if it's in the top level targets role or the releases delegation role // ignore it if it's in any other delegation roles if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole { return notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.String())) } logrus.Debugf("retrieving target for %s role\n", t.Role) r, err := convertTarget(t.Target) if err != nil { return err } refs = append(refs, r) } for i, r := range refs { displayTag := r.reference.String() if displayTag != "" { displayTag = ":" + displayTag } fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest) ref, err := reference.WithDigest(repoInfo, r.digest) if err != nil { return err } if err := cli.ImagePullPrivileged(ctx, authConfig, ref.String(), requestPrivilege, false); err != nil { return err } // If reference is not trusted, tag by trusted reference if !r.reference.HasDigest() { tagged, err := reference.WithTag(repoInfo, r.reference.String()) if err != nil { return err } trustedRef, err := reference.WithDigest(repoInfo, r.digest) if err != nil { return err } if err := cli.TagTrusted(ctx, trustedRef, tagged); err != nil { return err } } } return nil }
func restoreCustomImage(driver graphdriver.Driver, is image.Store, ls layer.Store, rs reference.Store) error { if wd, ok := driver.(*windows.Driver); ok { imageInfos, err := wd.GetCustomImageInfos() if err != nil { return err } // Convert imageData to valid image configuration for i := range imageInfos { name := strings.ToLower(imageInfos[i].Name) type registrar interface { RegisterDiffID(graphID string, size int64) (layer.Layer, error) } r, ok := ls.(registrar) if !ok { return errors.New("Layerstore doesn't support RegisterDiffID") } if _, err := r.RegisterDiffID(imageInfos[i].ID, imageInfos[i].Size); err != nil { return err } // layer is intentionally not released rootFS := image.NewRootFS() rootFS.BaseLayer = filepath.Base(imageInfos[i].Path) // Create history for base layer config, err := json.Marshal(&image.Image{ V1Image: image.V1Image{ DockerVersion: dockerversion.Version, Architecture: runtime.GOARCH, OS: runtime.GOOS, Created: imageInfos[i].CreatedTime, }, RootFS: rootFS, History: []image.History{}, }) named, err := reference.ParseNamed(name) if err != nil { return err } ref, err := reference.WithTag(named, imageInfos[i].Version) if err != nil { return err } id, err := is.Create(config) if err != nil { return err } if err := rs.AddTag(ref, id, true); err != nil { return err } logrus.Debugf("Registered base layer %s as %s", ref, id) } } return nil }
// Creates an image from Pull or from Import func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } var ( image = r.Form.Get("fromImage") repo = r.Form.Get("repo") tag = r.Form.Get("tag") message = r.Form.Get("message") ) authEncoded := r.Header.Get("X-Registry-Auth") authConfig := &types.AuthConfig{} if authEncoded != "" { authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty authConfig = &types.AuthConfig{} } } var ( err error output = ioutils.NewWriteFlusher(w) ) defer output.Close() w.Header().Set("Content-Type", "application/json") if image != "" { //pull // Special case: "pull -a" may send an image name with a // trailing :. This is ugly, but let's not break API // compatibility. image = strings.TrimSuffix(image, ":") var ref reference.Named ref, err = reference.ParseNamed(image) if err == nil { if tag != "" { // The "tag" could actually be a digest. var dgst digest.Digest dgst, err = digest.ParseDigest(tag) if err == nil { ref, err = reference.WithDigest(ref, dgst) } else { ref, err = reference.WithTag(ref, tag) } } if err == nil { metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } err = s.daemon.PullImage(ref, metaHeaders, authConfig, output) } } } else { //import var newRef reference.Named if repo != "" { var err error newRef, err = reference.ParseNamed(repo) if err != nil { return err } if _, isCanonical := newRef.(reference.Canonical); isCanonical { return errors.New("cannot import digest reference") } if tag != "" { newRef, err = reference.WithTag(newRef, tag) if err != nil { return err } } } src := r.Form.Get("fromSrc") // 'err' MUST NOT be defined within this block, we need any error // generated from the download to be available to the output // stream processing below var newConfig *container.Config newConfig, err = dockerfile.BuildFromConfig(&container.Config{}, r.Form["changes"]) if err != nil { return err } err = s.daemon.ImportImage(src, newRef, message, r.Body, output, newConfig) } if err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }