diff --git a/api/server/router/image/backend.go b/api/server/router/image/backend.go index 39843fa7ab..7e5a6b6436 100644 --- a/api/server/router/image/backend.go +++ b/api/server/router/image/backend.go @@ -4,7 +4,6 @@ import ( "io" "github.com/docker/docker/api/types/backend" - "github.com/docker/docker/reference" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/registry" "golang.org/x/net/context" @@ -28,17 +27,17 @@ type imageBackend interface { ImageHistory(imageName string) ([]*types.ImageHistory, error) Images(filterArgs string, filter string, all bool) ([]*types.Image, error) LookupImage(name string) (*types.ImageInspect, error) - TagImage(newTag reference.Named, imageName string) error + TagImage(imageName, repository, tag string) error } type importExportBackend interface { LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error - ImportImage(src string, newRef reference.Named, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error + ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error ExportImage(names []string, outStream io.Writer) error } type registryBackend interface { - PullImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error - PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error + PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error + PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error SearchRegistryForImages(ctx context.Context, term string, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error) } diff --git a/api/server/router/image/image_routes.go b/api/server/router/image/image_routes.go index b1db315fd2..0c1f1d083e 100644 --- a/api/server/router/image/image_routes.go +++ b/api/server/router/image/image_routes.go @@ -3,20 +3,17 @@ package image import ( "encoding/base64" "encoding/json" - "errors" "fmt" "io" "net/http" "net/url" "strings" - "github.com/docker/distribution/digest" "github.com/docker/distribution/registry/api/errcode" "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types/backend" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/streamformatter" - "github.com/docker/docker/reference" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/container" "golang.org/x/net/context" @@ -89,46 +86,26 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite 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 - } - } - - 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{} - } - } - - err = s.backend.PullImage(ctx, ref, metaHeaders, authConfig, output) + metaHeaders := map[string][]string{} + for k, v := range r.Header { + if strings.HasPrefix(k, "X-Meta-") { + metaHeaders[k] = v } } + + 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{} + } + } + + err = s.backend.PullImage(ctx, image, tag, metaHeaders, authConfig, output) + // Check the error from pulling an image to make sure the request // was authorized. Modify the status if the request was // unauthorized to respond with 401 rather than 500. @@ -136,31 +113,11 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite err = errcode.ErrorCodeUnauthorized.WithMessage(fmt.Sprintf("Authentication is required: %s", err)) } } 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 - err = s.backend.ImportImage(src, newRef, message, r.Body, output, r.Form["changes"]) + err = s.backend.ImportImage(src, repo, tag, message, r.Body, output, r.Form["changes"]) } if err != nil { if !output.Flushed() { @@ -200,25 +157,15 @@ func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, } } - ref, err := reference.ParseNamed(vars["name"]) - if err != nil { - return err - } + image := vars["name"] 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.backend.PushImage(ctx, ref, metaHeaders, authConfig, output); err != nil { + if err := s.backend.PushImage(ctx, image, tag, metaHeaders, authConfig, output); err != nil { if !output.Flushed() { return err } @@ -322,18 +269,7 @@ func (s *imageRouter) postImagesTag(ctx context.Context, w http.ResponseWriter, 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.backend.TagImage(newTag, vars["name"]); err != nil { + if err := s.backend.TagImage(vars["name"], r.Form.Get("repo"), r.Form.Get("tag")); err != nil { return err } w.WriteHeader(http.StatusCreated) diff --git a/builder/builder.go b/builder/builder.go index 3fb0070a91..65e7f02346 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -10,6 +10,7 @@ import ( "time" "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/image" "github.com/docker/docker/reference" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/container" @@ -109,7 +110,7 @@ type Backend interface { // GetImageOnBuild looks up a Docker image referenced by `name`. GetImageOnBuild(name string) (Image, error) // TagImage tags an image with newTag - TagImage(newTag reference.Named, imageName string) error + TagImageWithReference(image.ID, reference.Named) error // PullOnBuild tells Docker to pull image referenced by `name`. PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (Image, error) // ContainerAttachRaw attaches to container. diff --git a/builder/dockerfile/builder.go b/builder/dockerfile/builder.go index 4144876708..658b095a82 100644 --- a/builder/dockerfile/builder.go +++ b/builder/dockerfile/builder.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/builder" "github.com/docker/docker/builder/dockerfile/parser" + "github.com/docker/docker/image" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/reference" "github.com/docker/engine-api/types" @@ -256,8 +257,9 @@ func (b *Builder) build(config *types.ImageBuildOptions, context builder.Context return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") } + imageID := image.ID(b.image) for _, rt := range repoAndTags { - if err := b.docker.TagImage(rt, b.image); err != nil { + if err := b.docker.TagImageWithReference(imageID, rt); err != nil { return "", err } } diff --git a/daemon/commit.go b/daemon/commit.go index c8bf230c2e..bb82c85e54 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -215,7 +215,7 @@ func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (str return "", err } } - if err := daemon.TagImage(newTag, id.String()); err != nil { + if err := daemon.TagImageWithReference(id, newTag); err != nil { return "", err } } diff --git a/daemon/daemon.go b/daemon/daemon.go index 4503b96875..a8c5fb9ace 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -38,7 +38,6 @@ import ( _ "github.com/docker/docker/daemon/graphdriver/register" "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/network" - "github.com/docker/docker/distribution" dmetadata "github.com/docker/docker/distribution/metadata" "github.com/docker/docker/distribution/xfer" "github.com/docker/docker/dockerversion" @@ -951,21 +950,6 @@ func (daemon *Daemon) changes(container *container.Container) ([]archive.Change, return container.RWLayer.Changes() } -// 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(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.LogImageEvent(imageID.String(), newTag.String(), "tag") - return nil -} - func writeDistributionProgress(cancelFunc func(), outStream io.Writer, progressChan <-chan progress.Progress) { progressOutput := streamformatter.NewJSONStreamFormatter().NewProgressOutput(outStream, false) operationCancelled := false @@ -996,69 +980,6 @@ func isBrokenPipe(e error) bool { return e == syscall.EPIPE } -// 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, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { - // 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) - }() - - imagePullConfig := &distribution.ImagePullConfig{ - MetaHeaders: metaHeaders, - AuthConfig: authConfig, - ProgressOutput: progress.ChanOutput(progressChan), - RegistryService: daemon.RegistryService, - ImageEventLogger: daemon.LogImageEvent, - MetadataStore: daemon.distributionMetadataStore, - ImageStore: daemon.imageStore, - ReferenceStore: daemon.referenceStore, - DownloadManager: daemon.downloadManager, - } - - err := distribution.Pull(ctx, ref, imagePullConfig) - close(progressChan) - <-writesDone - return err -} - -// PullOnBuild tells Docker to pull image referenced by `name`. -func (daemon *Daemon) PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) { - ref, err := reference.ParseNamed(name) - if err != nil { - return nil, err - } - ref = reference.WithDefaultTag(ref) - - pullRegistryAuth := &types.AuthConfig{} - if len(authConfigs) > 0 { - // The request came with a full auth config file, we prefer to use that - repoInfo, err := daemon.RegistryService.ResolveRepository(ref) - if err != nil { - return nil, err - } - - resolvedConfig := registry.ResolveAuthConfig( - authConfigs, - repoInfo.Index, - ) - pullRegistryAuth = &resolvedConfig - } - - if err := daemon.PullImage(ctx, ref, nil, pullRegistryAuth, output); err != nil { - return nil, err - } - return daemon.GetImage(name) -} - // ExportImage exports a list of images to the given output stream. The // exported images are archived into a tar when written to the output // stream. All images with the given tag and all versions containing @@ -1069,41 +990,6 @@ func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error { return imageExporter.Save(names, outStream) } -// PushImage initiates a push operation on the repository named localName. -func (daemon *Daemon) PushImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { - // 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{ - MetaHeaders: metaHeaders, - AuthConfig: authConfig, - ProgressOutput: progress.ChanOutput(progressChan), - RegistryService: daemon.RegistryService, - ImageEventLogger: daemon.LogImageEvent, - MetadataStore: daemon.distributionMetadataStore, - LayerStore: daemon.layerStore, - ImageStore: daemon.imageStore, - ReferenceStore: daemon.referenceStore, - TrustKey: daemon.trustKey, - UploadManager: daemon.uploadManager, - } - - err := distribution.Push(ctx, ref, imagePushConfig) - close(progressChan) - <-writesDone - return err -} - // LookupImage looks up an image by name and returns it as an ImageInspect // structure. func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) { diff --git a/daemon/image_pull.go b/daemon/image_pull.go new file mode 100644 index 0000000000..06ffa06fbe --- /dev/null +++ b/daemon/image_pull.go @@ -0,0 +1,106 @@ +package daemon + +import ( + "io" + "strings" + + "github.com/docker/distribution/digest" + "github.com/docker/docker/builder" + "github.com/docker/docker/distribution" + "github.com/docker/docker/pkg/progress" + "github.com/docker/docker/reference" + "github.com/docker/docker/registry" + "github.com/docker/engine-api/types" + "golang.org/x/net/context" +) + +// 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) +} + +// PullOnBuild tells Docker to pull image referenced by `name`. +func (daemon *Daemon) PullOnBuild(ctx context.Context, name string, authConfigs map[string]types.AuthConfig, output io.Writer) (builder.Image, error) { + ref, err := reference.ParseNamed(name) + if err != nil { + return nil, err + } + ref = reference.WithDefaultTag(ref) + + pullRegistryAuth := &types.AuthConfig{} + if len(authConfigs) > 0 { + // The request came with a full auth config file, we prefer to use that + repoInfo, err := daemon.RegistryService.ResolveRepository(ref) + if err != nil { + return nil, err + } + + resolvedConfig := registry.ResolveAuthConfig( + authConfigs, + repoInfo.Index, + ) + pullRegistryAuth = &resolvedConfig + } + + if err := daemon.pullImageWithReference(ctx, ref, nil, pullRegistryAuth, output); err != nil { + return nil, err + } + return daemon.GetImage(name) +} + +func (daemon *Daemon) pullImageWithReference(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { + // 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) + }() + + imagePullConfig := &distribution.ImagePullConfig{ + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + ProgressOutput: progress.ChanOutput(progressChan), + RegistryService: daemon.RegistryService, + ImageEventLogger: daemon.LogImageEvent, + MetadataStore: daemon.distributionMetadataStore, + ImageStore: daemon.imageStore, + ReferenceStore: daemon.referenceStore, + DownloadManager: daemon.downloadManager, + } + + err := distribution.Pull(ctx, ref, imagePullConfig) + close(progressChan) + <-writesDone + return err +} diff --git a/daemon/image_push.go b/daemon/image_push.go new file mode 100644 index 0000000000..11c89709f1 --- /dev/null +++ b/daemon/image_push.go @@ -0,0 +1,58 @@ +package daemon + +import ( + "io" + + "github.com/docker/docker/distribution" + "github.com/docker/docker/pkg/progress" + "github.com/docker/docker/reference" + "github.com/docker/engine-api/types" + "golang.org/x/net/context" +) + +// 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{ + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + ProgressOutput: progress.ChanOutput(progressChan), + RegistryService: daemon.RegistryService, + ImageEventLogger: daemon.LogImageEvent, + MetadataStore: daemon.distributionMetadataStore, + LayerStore: daemon.layerStore, + ImageStore: daemon.imageStore, + ReferenceStore: daemon.referenceStore, + TrustKey: daemon.trustKey, + UploadManager: daemon.uploadManager, + } + + err = distribution.Push(ctx, ref, imagePushConfig) + close(progressChan) + <-writesDone + return err +} diff --git a/daemon/image_tag.go b/daemon/image_tag.go new file mode 100644 index 0000000000..01127d4707 --- /dev/null +++ b/daemon/image_tag.go @@ -0,0 +1,37 @@ +package daemon + +import ( + "github.com/docker/docker/image" + "github.com/docker/docker/reference" +) + +// 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) +} + +// 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, true); err != nil { + return err + } + + daemon.LogImageEvent(imageID.String(), newTag.String(), "tag") + return nil +} diff --git a/daemon/import.go b/daemon/import.go index 3c62eb8386..b980f210f5 100644 --- a/daemon/import.go +++ b/daemon/import.go @@ -2,6 +2,7 @@ package daemon import ( "encoding/json" + "errors" "io" "net/http" "net/url" @@ -24,13 +25,33 @@ import ( // 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, newRef reference.Named, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error { +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 + 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 @@ -103,7 +124,7 @@ func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string // FIXME: connect with commit code and call refstore directly if newRef != nil { - if err := daemon.TagImage(newRef, id.String()); err != nil { + if err := daemon.TagImageWithReference(id, newRef); err != nil { return err } }