mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Add ability to refer to image by name + digest
Add ability to refer to an image by repository name and digest using the format repository@digest. Works for pull, push, run, build, and rmi. Signed-off-by: Andy Goldstein <agoldste@redhat.com>
This commit is contained in:
		
							parent
							
								
									ad56b5c603
								
							
						
					
					
						commit
						a2b0c9778f
					
				
					 28 changed files with 987 additions and 118 deletions
				
			
		| 
						 | 
				
			
			@ -108,7 +108,7 @@ RUN go get golang.org/x/tools/cmd/cover
 | 
			
		|||
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
 | 
			
		||||
 | 
			
		||||
# Install registry
 | 
			
		||||
ENV REGISTRY_COMMIT c448e0416925a9876d5576e412703c9b8b865e19
 | 
			
		||||
ENV REGISTRY_COMMIT b4cc5e3ecc2e9f4fa0e95d94c389e1d79e902486
 | 
			
		||||
RUN set -x \
 | 
			
		||||
	&& git clone https://github.com/docker/distribution.git /go/src/github.com/docker/distribution \
 | 
			
		||||
	&& (cd /go/src/github.com/docker/distribution && git checkout -q $REGISTRY_COMMIT) \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1312,7 +1312,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func (cli *DockerCli) CmdPull(args ...string) error {
 | 
			
		||||
	cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry", true)
 | 
			
		||||
	cmd := cli.Subcmd("pull", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true)
 | 
			
		||||
	allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
 | 
			
		||||
	cmd.Require(flag.Exact, 1)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1325,7 +1325,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 | 
			
		|||
	)
 | 
			
		||||
	taglessRemote, tag := parsers.ParseRepositoryTag(remote)
 | 
			
		||||
	if tag == "" && !*allTags {
 | 
			
		||||
		newRemote = taglessRemote + ":" + graph.DEFAULTTAG
 | 
			
		||||
		newRemote = utils.ImageReference(taglessRemote, graph.DEFAULTTAG)
 | 
			
		||||
	}
 | 
			
		||||
	if tag != "" && *allTags {
 | 
			
		||||
		return fmt.Errorf("tag can't be used with --all-tags/-a")
 | 
			
		||||
| 
						 | 
				
			
			@ -1378,6 +1378,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 | 
			
		|||
	quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
 | 
			
		||||
	all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
 | 
			
		||||
	noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
 | 
			
		||||
	showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests")
 | 
			
		||||
	// FIXME: --viz and --tree are deprecated. Remove them in a future version.
 | 
			
		||||
	flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format")
 | 
			
		||||
	flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format")
 | 
			
		||||
| 
						 | 
				
			
			@ -1504,20 +1505,43 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 | 
			
		|||
 | 
			
		||||
		w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
 | 
			
		||||
		if !*quiet {
 | 
			
		||||
			fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE")
 | 
			
		||||
			if *showDigests {
 | 
			
		||||
				fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE")
 | 
			
		||||
			} else {
 | 
			
		||||
				fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, out := range outs.Data {
 | 
			
		||||
			for _, repotag := range out.GetList("RepoTags") {
 | 
			
		||||
			outID := out.Get("Id")
 | 
			
		||||
			if !*noTrunc {
 | 
			
		||||
				outID = common.TruncateID(outID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Tags referring to this image ID.
 | 
			
		||||
			for _, repotag := range out.GetList("RepoTags") {
 | 
			
		||||
				repo, tag := parsers.ParseRepositoryTag(repotag)
 | 
			
		||||
				outID := out.Get("Id")
 | 
			
		||||
				if !*noTrunc {
 | 
			
		||||
					outID = common.TruncateID(outID)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !*quiet {
 | 
			
		||||
					fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
 | 
			
		||||
					if *showDigests {
 | 
			
		||||
						fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, "<none>", outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
 | 
			
		||||
					} else {
 | 
			
		||||
						fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					fmt.Fprintln(w, outID)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Digests referring to this image ID.
 | 
			
		||||
			for _, repoDigest := range out.GetList("RepoDigests") {
 | 
			
		||||
				repo, digest := parsers.ParseRepositoryTag(repoDigest)
 | 
			
		||||
				if !*quiet {
 | 
			
		||||
					if *showDigests {
 | 
			
		||||
						fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, "<none>", digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
 | 
			
		||||
					} else {
 | 
			
		||||
						fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, "<none>", outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize"))))
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					fmt.Fprintln(w, outID)
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			@ -2208,7 +2232,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
 | 
			
		|||
		if tag == "" {
 | 
			
		||||
			tag = graph.DEFAULTTAG
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(cli.err, "Unable to find image '%s:%s' locally\n", repo, tag)
 | 
			
		||||
		fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", utils.ImageReference(repo, tag))
 | 
			
		||||
 | 
			
		||||
		// we don't want to write to stdout anything apart from container.ID
 | 
			
		||||
		if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/pkg/common"
 | 
			
		||||
	"github.com/docker/docker/pkg/parsers"
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status {
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +49,7 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.
 | 
			
		|||
	img, err := daemon.Repositories().LookupImage(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if r, _ := daemon.Repositories().Get(repoName); r != nil {
 | 
			
		||||
			return fmt.Errorf("No such image: %s:%s", repoName, tag)
 | 
			
		||||
			return fmt.Errorf("No such image: %s", utils.ImageReference(repoName, tag))
 | 
			
		||||
		}
 | 
			
		||||
		return fmt.Errorf("No such image: %s", name)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +103,7 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.
 | 
			
		|||
		}
 | 
			
		||||
		if tagDeleted {
 | 
			
		||||
			out := &engine.Env{}
 | 
			
		||||
			out.Set("Untagged", repoName+":"+tag)
 | 
			
		||||
			out.Set("Untagged", utils.ImageReference(repoName, tag))
 | 
			
		||||
			imgs.Add(out)
 | 
			
		||||
			eng.Job("log", "untag", img.ID, "").Run()
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/docker/docker/graph"
 | 
			
		||||
	"github.com/docker/docker/pkg/graphdb"
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/engine"
 | 
			
		||||
	"github.com/docker/docker/pkg/parsers"
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +132,7 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status {
 | 
			
		|||
		img := container.Config.Image
 | 
			
		||||
		_, tag := parsers.ParseRepositoryTag(container.Config.Image)
 | 
			
		||||
		if tag == "" {
 | 
			
		||||
			img = img + ":" + graph.DEFAULTTAG
 | 
			
		||||
			img = utils.ImageReference(img, graph.DEFAULTTAG)
 | 
			
		||||
		}
 | 
			
		||||
		out.SetJson("Image", img)
 | 
			
		||||
		if len(container.Args) > 0 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ docker-images - List images
 | 
			
		|||
**docker images**
 | 
			
		||||
[**--help**]
 | 
			
		||||
[**-a**|**--all**[=*false*]]
 | 
			
		||||
[**--digests**[=*false*]]
 | 
			
		||||
[**-f**|**--filter**[=*[]*]]
 | 
			
		||||
[**--no-trunc**[=*false*]]
 | 
			
		||||
[**-q**|**--quiet**[=*false*]]
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +34,9 @@ versions.
 | 
			
		|||
**-a**, **--all**=*true*|*false*
 | 
			
		||||
   Show all images (by default filter out the intermediate image layers). The default is *false*.
 | 
			
		||||
 | 
			
		||||
**--digests**=*true*|*false*
 | 
			
		||||
   Show image digests. The default is *false*.
 | 
			
		||||
 | 
			
		||||
**-f**, **--filter**=[]
 | 
			
		||||
   Filters the output. The dangling=true filter finds unused images. While label=com.foo=amd64 filters for images with a com.foo value of amd64. The label=com.foo filter finds images with the label com.foo of any value.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,6 +62,10 @@ You can set ulimit settings to be used within the container.
 | 
			
		|||
**New!**
 | 
			
		||||
This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`. 
 | 
			
		||||
 | 
			
		||||
`GET /images/json`
 | 
			
		||||
 | 
			
		||||
**New!**
 | 
			
		||||
Added a `RepoDigests` field to include image digest information.
 | 
			
		||||
 | 
			
		||||
## v1.17
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1054,6 +1054,45 @@ Status Codes:
 | 
			
		|||
          }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
**Example request, with digest information**:
 | 
			
		||||
 | 
			
		||||
        GET /images/json?digests=1 HTTP/1.1
 | 
			
		||||
 | 
			
		||||
**Example response, with digest information**:
 | 
			
		||||
 | 
			
		||||
        HTTP/1.1 200 OK
 | 
			
		||||
        Content-Type: application/json
 | 
			
		||||
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
            "Created": 1420064636,
 | 
			
		||||
            "Id": "4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125",
 | 
			
		||||
            "ParentId": "ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2",
 | 
			
		||||
            "RepoDigests": [
 | 
			
		||||
              "localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"
 | 
			
		||||
            ],
 | 
			
		||||
            "RepoTags": [
 | 
			
		||||
              "localhost:5000/test/busybox:latest",
 | 
			
		||||
              "playdate:latest"
 | 
			
		||||
            ],
 | 
			
		||||
            "Size": 0,
 | 
			
		||||
            "VirtualSize": 2429728
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
The response shows a single image `Id` associated with two repositories
 | 
			
		||||
(`RepoTags`): `localhost:5000/test/busybox`: and `playdate`. A caller can use
 | 
			
		||||
either of the `RepoTags` values `localhost:5000/test/busybox:latest` or
 | 
			
		||||
`playdate:latest` to reference the image.
 | 
			
		||||
 | 
			
		||||
You can also use `RepoDigests` values to reference an image. In this response,
 | 
			
		||||
the array has only one reference and that is to the
 | 
			
		||||
`localhost:5000/test/busybox` repository; the `playdate` repository has no
 | 
			
		||||
digest. You can reference this digest using the value:
 | 
			
		||||
`localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d...`
 | 
			
		||||
 | 
			
		||||
See the `docker run` and `docker build` commands for examples of digest and tag
 | 
			
		||||
references on the command line.
 | 
			
		||||
 | 
			
		||||
Query Parameters:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -192,6 +192,10 @@ Or
 | 
			
		|||
 | 
			
		||||
    FROM <image>:<tag>
 | 
			
		||||
 | 
			
		||||
Or
 | 
			
		||||
 | 
			
		||||
    FROM <image>@<digest>
 | 
			
		||||
 | 
			
		||||
The `FROM` instruction sets the [*Base Image*](/terms/image/#base-image)
 | 
			
		||||
for subsequent instructions. As such, a valid `Dockerfile` must have `FROM` as
 | 
			
		||||
its first instruction. The image can be any valid image – it is especially easy
 | 
			
		||||
| 
						 | 
				
			
			@ -204,8 +208,9 @@ to start by **pulling an image** from the [*Public Repositories*](
 | 
			
		|||
multiple images. Simply make a note of the last image ID output by the commit
 | 
			
		||||
before each new `FROM` command.
 | 
			
		||||
 | 
			
		||||
If no `tag` is given to the `FROM` instruction, `latest` is assumed. If the
 | 
			
		||||
used tag does not exist, an error will be returned.
 | 
			
		||||
The `tag` or `digest` values are optional. If you omit either of them, the builder
 | 
			
		||||
assumes a `latest` by default. The builder returns an error if it cannot match
 | 
			
		||||
the `tag` value.
 | 
			
		||||
 | 
			
		||||
## MAINTAINER
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1112,7 +1112,9 @@ To see how the `docker:latest` image was built:
 | 
			
		|||
    List images
 | 
			
		||||
 | 
			
		||||
      -a, --all=false      Show all images (default hides intermediate images)
 | 
			
		||||
      --digests=false      Show digests
 | 
			
		||||
      -f, --filter=[]      Filter output based on conditions provided
 | 
			
		||||
      --help=false         Print usage
 | 
			
		||||
      --no-trunc=false     Don't truncate output
 | 
			
		||||
      -q, --quiet=false    Only show numeric IDs
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1161,6 +1163,22 @@ uses up the `VIRTUAL SIZE` listed only once.
 | 
			
		|||
    tryout                        latest              2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074   23 hours ago        131.5 MB
 | 
			
		||||
    <none>                        <none>              5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df   24 hours ago        1.089 GB
 | 
			
		||||
 | 
			
		||||
#### Listing image digests
 | 
			
		||||
 | 
			
		||||
Images that use the v2 or later format have a content-addressable identifier
 | 
			
		||||
called a `digest`. As long as the input used to generate the image is
 | 
			
		||||
unchanged, the digest value is predictable. To list image digest values, use
 | 
			
		||||
the `--digests` flag:
 | 
			
		||||
 | 
			
		||||
    $ sudo docker images --digests | head
 | 
			
		||||
    REPOSITORY                         TAG                 DIGEST                                                                    IMAGE ID            CREATED             VIRTUAL SIZE
 | 
			
		||||
    localhost:5000/test/busybox        <none>              sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf   4986bf8c1536        9 weeks ago         2.43 MB
 | 
			
		||||
 | 
			
		||||
When pushing or pulling to a 2.0 registry, the `push` or `pull` command
 | 
			
		||||
output includes the image digest. You can `pull` using a digest value. You can
 | 
			
		||||
also reference by digest in `create`, `run`, and `rmi` commands, as well as the
 | 
			
		||||
`FROM` image reference in a Dockerfile.
 | 
			
		||||
 | 
			
		||||
#### Filtering
 | 
			
		||||
 | 
			
		||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
 | 
			
		||||
| 
						 | 
				
			
			@ -1563,6 +1581,10 @@ use `docker pull`:
 | 
			
		|||
    $ sudo docker pull debian:testing
 | 
			
		||||
    # will pull the image named debian:testing and any intermediate
 | 
			
		||||
    # layers it is based on.
 | 
			
		||||
    $ sudo docker pull debian@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 | 
			
		||||
    # will pull the image from the debian repository with the digest
 | 
			
		||||
    # sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 | 
			
		||||
    # and any intermediate layers it is based on.
 | 
			
		||||
    # (Typically the empty `scratch` image, a MAINTAINER layer,
 | 
			
		||||
    # and the un-tarred base).
 | 
			
		||||
    $ sudo docker pull --all-tags centos
 | 
			
		||||
| 
						 | 
				
			
			@ -1634,9 +1656,9 @@ deleted.
 | 
			
		|||
 | 
			
		||||
#### Removing tagged images
 | 
			
		||||
 | 
			
		||||
Images can be removed either by their short or long IDs, or their image
 | 
			
		||||
names. If an image has more than one name, each of them needs to be
 | 
			
		||||
removed before the image is removed.
 | 
			
		||||
You can remove an image using its short or long ID, its tag, or its digest. If
 | 
			
		||||
an image has one or more tag or digest reference, you must remove all of them
 | 
			
		||||
before the image is removed.
 | 
			
		||||
 | 
			
		||||
    $ sudo docker images
 | 
			
		||||
    REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
 | 
			
		||||
| 
						 | 
				
			
			@ -1660,6 +1682,20 @@ removed before the image is removed.
 | 
			
		|||
    Untagged: test:latest
 | 
			
		||||
    Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8
 | 
			
		||||
 | 
			
		||||
An image pulled by digest has no tag associated with it:
 | 
			
		||||
 | 
			
		||||
    $ sudo docker images --digests
 | 
			
		||||
    REPOSITORY                     TAG       DIGEST                                                                    IMAGE ID        CREATED         VIRTUAL SIZE
 | 
			
		||||
    localhost:5000/test/busybox    <none>    sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf   4986bf8c1536    9 weeks ago     2.43 MB
 | 
			
		||||
 | 
			
		||||
To remove an image using its digest:
 | 
			
		||||
 | 
			
		||||
    $ sudo docker rmi localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 | 
			
		||||
    Untagged: localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
 | 
			
		||||
    Deleted: 4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125
 | 
			
		||||
    Deleted: ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2
 | 
			
		||||
    Deleted: df7546f9f060a2268024c8a230d8639878585defcc1bc6f79d2728a13957871b
 | 
			
		||||
 | 
			
		||||
## run
 | 
			
		||||
 | 
			
		||||
    Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@ other `docker` command.
 | 
			
		|||
 | 
			
		||||
The basic `docker run` command takes this form:
 | 
			
		||||
 | 
			
		||||
    $ sudo docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
 | 
			
		||||
    $ sudo docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
 | 
			
		||||
 | 
			
		||||
To learn how to interpret the types of `[OPTIONS]`,
 | 
			
		||||
see [*Option types*](/reference/commandline/cli/#option-types).
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +140,12 @@ While not strictly a means of identifying a container, you can specify a version
 | 
			
		|||
image you'd like to run the container with by adding `image[:tag]` to the command. For
 | 
			
		||||
example, `docker run ubuntu:14.04`.
 | 
			
		||||
 | 
			
		||||
### Image[@digest]
 | 
			
		||||
 | 
			
		||||
Images using the v2 or later image format have a content-addressable identifier
 | 
			
		||||
called a digest. As long as the input used to generate the image is unchanged,
 | 
			
		||||
the digest value is predictable and referenceable.
 | 
			
		||||
 | 
			
		||||
## PID Settings (--pid)
 | 
			
		||||
    --pid=""  : Set the PID (Process) Namespace mode for the container,
 | 
			
		||||
           'host': use the host's PID namespace inside the container
 | 
			
		||||
| 
						 | 
				
			
			@ -661,7 +667,7 @@ Dockerfile instruction and how the operator can override that setting.
 | 
			
		|||
Recall the optional `COMMAND` in the Docker
 | 
			
		||||
commandline:
 | 
			
		||||
 | 
			
		||||
    $ sudo docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
 | 
			
		||||
    $ sudo docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
 | 
			
		||||
 | 
			
		||||
This command is optional because the person who created the `IMAGE` may
 | 
			
		||||
have already provided a default `COMMAND` using the Dockerfile `CMD`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/docker/docker/engine"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +25,7 @@ func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
 | 
			
		|||
			if _, exists := lookupMap[id]; !exists {
 | 
			
		||||
				lookupMap[id] = []string{}
 | 
			
		||||
			}
 | 
			
		||||
			lookupMap[id] = append(lookupMap[id], name+":"+tag)
 | 
			
		||||
			lookupMap[id] = append(lookupMap[id], utils.ImageReference(name, tag))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,7 +88,7 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
 | 
			
		|||
	job.Stdout.Write(sf.FormatStatus("", img.ID))
 | 
			
		||||
	logID := img.ID
 | 
			
		||||
	if tag != "" {
 | 
			
		||||
		logID += ":" + tag
 | 
			
		||||
		logID = utils.ImageReference(logID, tag)
 | 
			
		||||
	}
 | 
			
		||||
	if err = job.Eng.Job("log", "import", logID, "").Run(); err != nil {
 | 
			
		||||
		log.Errorf("Error logging event 'import' for %s: %s", logID, err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
package graph
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +8,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/engine"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/pkg/parsers/filters"
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var acceptedImageFilterTags = map[string]struct{}{
 | 
			
		||||
| 
						 | 
				
			
			@ -54,22 +54,27 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 | 
			
		|||
	}
 | 
			
		||||
	lookup := make(map[string]*engine.Env)
 | 
			
		||||
	s.Lock()
 | 
			
		||||
	for name, repository := range s.Repositories {
 | 
			
		||||
	for repoName, repository := range s.Repositories {
 | 
			
		||||
		if job.Getenv("filter") != "" {
 | 
			
		||||
			if match, _ := path.Match(job.Getenv("filter"), name); !match {
 | 
			
		||||
			if match, _ := path.Match(job.Getenv("filter"), repoName); !match {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for tag, id := range repository {
 | 
			
		||||
		for ref, id := range repository {
 | 
			
		||||
			imgRef := utils.ImageReference(repoName, ref)
 | 
			
		||||
			image, err := s.graph.Get(id)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
 | 
			
		||||
				log.Printf("Warning: couldn't load %s from %s: %s", id, imgRef, err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if out, exists := lookup[id]; exists {
 | 
			
		||||
				if filt_tagged {
 | 
			
		||||
					out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag)))
 | 
			
		||||
					if utils.DigestReference(ref) {
 | 
			
		||||
						out.SetList("RepoDigests", append(out.GetList("RepoDigests"), imgRef))
 | 
			
		||||
					} else { // Tag Ref.
 | 
			
		||||
						out.SetList("RepoTags", append(out.GetList("RepoTags"), imgRef))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				// get the boolean list for if only the untagged images are requested
 | 
			
		||||
| 
						 | 
				
			
			@ -80,12 +85,20 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 | 
			
		|||
				if filt_tagged {
 | 
			
		||||
					out := &engine.Env{}
 | 
			
		||||
					out.SetJson("ParentId", image.Parent)
 | 
			
		||||
					out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
 | 
			
		||||
					out.SetJson("Id", image.ID)
 | 
			
		||||
					out.SetInt64("Created", image.Created.Unix())
 | 
			
		||||
					out.SetInt64("Size", image.Size)
 | 
			
		||||
					out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
 | 
			
		||||
					out.SetJson("Labels", image.ContainerConfig.Labels)
 | 
			
		||||
 | 
			
		||||
					if utils.DigestReference(ref) {
 | 
			
		||||
						out.SetList("RepoTags", []string{})
 | 
			
		||||
						out.SetList("RepoDigests", []string{imgRef})
 | 
			
		||||
					} else {
 | 
			
		||||
						out.SetList("RepoTags", []string{imgRef})
 | 
			
		||||
						out.SetList("RepoDigests", []string{})
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					lookup[id] = out
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -108,6 +121,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
 | 
			
		|||
			out := &engine.Env{}
 | 
			
		||||
			out.SetJson("ParentId", image.Parent)
 | 
			
		||||
			out.SetList("RepoTags", []string{"<none>:<none>"})
 | 
			
		||||
			out.SetList("RepoDigests", []string{"<none>@<none>"})
 | 
			
		||||
			out.SetJson("Id", image.ID)
 | 
			
		||||
			out.SetInt64("Created", image.Created.Unix())
 | 
			
		||||
			out.SetInt64("Size", image.Size)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ import (
 | 
			
		|||
 | 
			
		||||
func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
 | 
			
		||||
	if n := len(job.Args); n != 1 && n != 2 {
 | 
			
		||||
		return job.Errorf("Usage: %s IMAGE [TAG]", job.Name)
 | 
			
		||||
		return job.Errorf("Usage: %s IMAGE [TAG|DIGEST]", job.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +46,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
 | 
			
		|||
	job.GetenvJson("authConfig", authConfig)
 | 
			
		||||
	job.GetenvJson("metaHeaders", &metaHeaders)
 | 
			
		||||
 | 
			
		||||
	c, err := s.poolAdd("pull", repoInfo.LocalName+":"+tag)
 | 
			
		||||
	c, err := s.poolAdd("pull", utils.ImageReference(repoInfo.LocalName, tag))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if c != nil {
 | 
			
		||||
			// Another pull of the same repository is already taking place; just wait for it to finish
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
 | 
			
		|||
		}
 | 
			
		||||
		return job.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer s.poolRemove("pull", repoInfo.LocalName+":"+tag)
 | 
			
		||||
	defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag))
 | 
			
		||||
 | 
			
		||||
	log.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
 | 
			
		||||
	endpoint, err := repoInfo.GetEndpoint()
 | 
			
		||||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
 | 
			
		|||
 | 
			
		||||
	logName := repoInfo.LocalName
 | 
			
		||||
	if tag != "" {
 | 
			
		||||
		logName += ":" + tag
 | 
			
		||||
		logName = utils.ImageReference(logName, tag)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(repoInfo.Index.Mirrors) == 0 && ((repoInfo.Official && repoInfo.Index.Official) || endpoint.Version == registry.APIVersion2) {
 | 
			
		||||
| 
						 | 
				
			
			@ -113,7 +113,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
 | 
			
		|||
	repoData, err := r.GetRepositoryData(repoInfo.RemoteName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if strings.Contains(err.Error(), "HTTP code: 404") {
 | 
			
		||||
			return fmt.Errorf("Error: image %s:%s not found", repoInfo.RemoteName, askedTag)
 | 
			
		||||
			return fmt.Errorf("Error: image %s not found", utils.ImageReference(repoInfo.RemoteName, askedTag))
 | 
			
		||||
		}
 | 
			
		||||
		// Unexpected HTTP error
 | 
			
		||||
		return err
 | 
			
		||||
| 
						 | 
				
			
			@ -259,7 +259,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
 | 
			
		|||
 | 
			
		||||
	requestedTag := repoInfo.CanonicalName
 | 
			
		||||
	if len(askedTag) > 0 {
 | 
			
		||||
		requestedTag = repoInfo.CanonicalName + ":" + askedTag
 | 
			
		||||
		requestedTag = utils.ImageReference(repoInfo.CanonicalName, askedTag)
 | 
			
		||||
	}
 | 
			
		||||
	WriteStatus(requestedTag, out, sf, layers_downloaded)
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -421,7 +421,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out
 | 
			
		|||
 | 
			
		||||
	requestedTag := repoInfo.CanonicalName
 | 
			
		||||
	if len(tag) > 0 {
 | 
			
		||||
		requestedTag = repoInfo.CanonicalName + ":" + tag
 | 
			
		||||
		requestedTag = utils.ImageReference(repoInfo.CanonicalName, tag)
 | 
			
		||||
	}
 | 
			
		||||
	WriteStatus(requestedTag, out, sf, layersDownloaded)
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -429,7 +429,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out
 | 
			
		|||
 | 
			
		||||
func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) {
 | 
			
		||||
	log.Debugf("Pulling tag from V2 registry: %q", tag)
 | 
			
		||||
	manifestBytes, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth)
 | 
			
		||||
	manifestBytes, digest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -444,7 +444,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if verified {
 | 
			
		||||
		log.Printf("Image manifest for %s:%s has been verified", repoInfo.CanonicalName, tag)
 | 
			
		||||
		log.Printf("Image manifest for %s has been verified", utils.ImageReference(repoInfo.CanonicalName, tag))
 | 
			
		||||
	}
 | 
			
		||||
	out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -601,11 +601,22 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	if verified && tagUpdated {
 | 
			
		||||
		out.Write(sf.FormatStatus(repoInfo.CanonicalName+":"+tag, "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security."))
 | 
			
		||||
		out.Write(sf.FormatStatus(utils.ImageReference(repoInfo.CanonicalName, tag), "The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security."))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = s.Set(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	if len(digest) > 0 {
 | 
			
		||||
		out.Write(sf.FormatStatus("", "Digest: %s", digest))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if utils.DigestReference(tag) {
 | 
			
		||||
		if err = s.SetDigest(repoInfo.LocalName, tag, downloads[0].img.ID); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest)
 | 
			
		||||
		if err = s.Set(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tagUpdated, nil
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,8 +36,15 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string
 | 
			
		|||
 | 
			
		||||
	for tag, id := range localRepo {
 | 
			
		||||
		if requestedTag != "" && requestedTag != tag {
 | 
			
		||||
			// Include only the requested tag.
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if utils.DigestReference(tag) {
 | 
			
		||||
			// Ignore digest references.
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var imageListForThisTag []string
 | 
			
		||||
 | 
			
		||||
		tagsByImage[id] = append(tagsByImage[id], tag)
 | 
			
		||||
| 
						 | 
				
			
			@ -76,14 +83,16 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string
 | 
			
		|||
func (s *TagStore) getImageTags(localRepo map[string]string, askedTag string) ([]string, error) {
 | 
			
		||||
	log.Debugf("Checking %s against %#v", askedTag, localRepo)
 | 
			
		||||
	if len(askedTag) > 0 {
 | 
			
		||||
		if _, ok := localRepo[askedTag]; !ok {
 | 
			
		||||
		if _, ok := localRepo[askedTag]; !ok || utils.DigestReference(askedTag) {
 | 
			
		||||
			return nil, fmt.Errorf("Tag does not exist: %s", askedTag)
 | 
			
		||||
		}
 | 
			
		||||
		return []string{askedTag}, nil
 | 
			
		||||
	}
 | 
			
		||||
	var tags []string
 | 
			
		||||
	for tag := range localRepo {
 | 
			
		||||
		tags = append(tags, tag)
 | 
			
		||||
		if !utils.DigestReference(tag) {
 | 
			
		||||
			tags = append(tags, tag)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return tags, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -422,9 +431,14 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
 | 
			
		|||
		log.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID())
 | 
			
		||||
 | 
			
		||||
		// push the manifest
 | 
			
		||||
		if err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, bytes.NewReader(signedBody), auth); err != nil {
 | 
			
		||||
		digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, bytes.NewReader(signedBody), auth)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(digest) > 0 {
 | 
			
		||||
			out.Write(sf.FormatStatus("", "Digest: %s", digest))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										143
									
								
								graph/tags.go
									
										
									
									
									
								
							
							
						
						
									
										143
									
								
								graph/tags.go
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -2,6 +2,7 @@ package graph
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
| 
						 | 
				
			
			@ -15,13 +16,16 @@ import (
 | 
			
		|||
	"github.com/docker/docker/pkg/common"
 | 
			
		||||
	"github.com/docker/docker/pkg/parsers"
 | 
			
		||||
	"github.com/docker/docker/registry"
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
	"github.com/docker/libtrust"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const DEFAULTTAG = "latest"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	//FIXME these 2 regexes also exist in registry/v2/regexp.go
 | 
			
		||||
	validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
 | 
			
		||||
	validDigest  = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TagStore struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -107,20 +111,31 @@ func (store *TagStore) reload() error {
 | 
			
		|||
func (store *TagStore) LookupImage(name string) (*image.Image, error) {
 | 
			
		||||
	// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
 | 
			
		||||
	// (so we can pass all errors here)
 | 
			
		||||
	repos, tag := parsers.ParseRepositoryTag(name)
 | 
			
		||||
	if tag == "" {
 | 
			
		||||
		tag = DEFAULTTAG
 | 
			
		||||
	repoName, ref := parsers.ParseRepositoryTag(name)
 | 
			
		||||
	if ref == "" {
 | 
			
		||||
		ref = DEFAULTTAG
 | 
			
		||||
	}
 | 
			
		||||
	img, err := store.GetImage(repos, tag)
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
	var (
 | 
			
		||||
		err error
 | 
			
		||||
		img *image.Image
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	img, err = store.GetImage(repoName, ref)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if img == nil {
 | 
			
		||||
		if img, err = store.graph.Get(name); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if img != nil {
 | 
			
		||||
		return img, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// name must be an image ID.
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
	if img, err = store.graph.Get(name); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return img, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +147,7 @@ func (store *TagStore) ByID() map[string][]string {
 | 
			
		|||
	byID := make(map[string][]string)
 | 
			
		||||
	for repoName, repository := range store.Repositories {
 | 
			
		||||
		for tag, id := range repository {
 | 
			
		||||
			name := repoName + ":" + tag
 | 
			
		||||
			name := utils.ImageReference(repoName, tag)
 | 
			
		||||
			if _, exists := byID[id]; !exists {
 | 
			
		||||
				byID[id] = []string{name}
 | 
			
		||||
			} else {
 | 
			
		||||
| 
						 | 
				
			
			@ -171,32 +186,35 @@ func (store *TagStore) DeleteAll(id string) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (store *TagStore) Delete(repoName, tag string) (bool, error) {
 | 
			
		||||
func (store *TagStore) Delete(repoName, ref string) (bool, error) {
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
	deleted := false
 | 
			
		||||
	if err := store.reload(); err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repoName = registry.NormalizeLocalName(repoName)
 | 
			
		||||
	if r, exists := store.Repositories[repoName]; exists {
 | 
			
		||||
		if tag != "" {
 | 
			
		||||
			if _, exists2 := r[tag]; exists2 {
 | 
			
		||||
				delete(r, tag)
 | 
			
		||||
				if len(r) == 0 {
 | 
			
		||||
					delete(store.Repositories, repoName)
 | 
			
		||||
				}
 | 
			
		||||
				deleted = true
 | 
			
		||||
			} else {
 | 
			
		||||
				return false, fmt.Errorf("No such tag: %s:%s", repoName, tag)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			delete(store.Repositories, repoName)
 | 
			
		||||
			deleted = true
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
	if ref == "" {
 | 
			
		||||
		// Delete the whole repository.
 | 
			
		||||
		delete(store.Repositories, repoName)
 | 
			
		||||
		return true, store.save()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repoRefs, exists := store.Repositories[repoName]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		return false, fmt.Errorf("No such repository: %s", repoName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, exists := repoRefs[ref]; exists {
 | 
			
		||||
		delete(repoRefs, ref)
 | 
			
		||||
		if len(repoRefs) == 0 {
 | 
			
		||||
			delete(store.Repositories, repoName)
 | 
			
		||||
		}
 | 
			
		||||
		deleted = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return deleted, store.save()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -234,6 +252,40 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
 | 
			
		|||
	return store.save()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDigest creates a digest reference to an image ID.
 | 
			
		||||
func (store *TagStore) SetDigest(repoName, digest, imageName string) error {
 | 
			
		||||
	img, err := store.LookupImage(imageName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateRepoName(repoName); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDigest(digest); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
	if err := store.reload(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repoName = registry.NormalizeLocalName(repoName)
 | 
			
		||||
	repoRefs, exists := store.Repositories[repoName]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		repoRefs = Repository{}
 | 
			
		||||
		store.Repositories[repoName] = repoRefs
 | 
			
		||||
	} else if oldID, exists := repoRefs[digest]; exists && oldID != img.ID {
 | 
			
		||||
		return fmt.Errorf("Conflict: Digest %s is already set to image %s", digest, oldID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	repoRefs[digest] = img.ID
 | 
			
		||||
	return store.save()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (store *TagStore) Get(repoName string) (Repository, error) {
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
| 
						 | 
				
			
			@ -247,24 +299,29 @@ func (store *TagStore) Get(repoName string) (Repository, error) {
 | 
			
		|||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (store *TagStore) GetImage(repoName, tagOrID string) (*image.Image, error) {
 | 
			
		||||
func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) {
 | 
			
		||||
	repo, err := store.Get(repoName)
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	} else if repo == nil {
 | 
			
		||||
	}
 | 
			
		||||
	if repo == nil {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	if revision, exists := repo[tagOrID]; exists {
 | 
			
		||||
		return store.graph.Get(revision)
 | 
			
		||||
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
	if imgID, exists := repo[refOrID]; exists {
 | 
			
		||||
		return store.graph.Get(imgID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If no matching tag is found, search through images for a matching image id
 | 
			
		||||
	for _, revision := range repo {
 | 
			
		||||
		if strings.HasPrefix(revision, tagOrID) {
 | 
			
		||||
		if strings.HasPrefix(revision, refOrID) {
 | 
			
		||||
			return store.graph.Get(revision)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -275,7 +332,7 @@ func (store *TagStore) GetRepoRefs() map[string][]string {
 | 
			
		|||
	for name, repository := range store.Repositories {
 | 
			
		||||
		for tag, id := range repository {
 | 
			
		||||
			shortID := common.TruncateID(id)
 | 
			
		||||
			reporefs[shortID] = append(reporefs[shortID], fmt.Sprintf("%s:%s", name, tag))
 | 
			
		||||
			reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	store.Unlock()
 | 
			
		||||
| 
						 | 
				
			
			@ -293,10 +350,10 @@ func validateRepoName(name string) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate the name of a tag
 | 
			
		||||
// ValidateTagName validates the name of a tag
 | 
			
		||||
func ValidateTagName(name string) error {
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		return fmt.Errorf("Tag name can't be empty")
 | 
			
		||||
		return fmt.Errorf("tag name can't be empty")
 | 
			
		||||
	}
 | 
			
		||||
	if !validTagName.MatchString(name) {
 | 
			
		||||
		return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name)
 | 
			
		||||
| 
						 | 
				
			
			@ -304,6 +361,16 @@ func ValidateTagName(name string) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validateDigest(dgst string) error {
 | 
			
		||||
	if dgst == "" {
 | 
			
		||||
		return errors.New("digest can't be empty")
 | 
			
		||||
	}
 | 
			
		||||
	if !validDigest.MatchString(dgst) {
 | 
			
		||||
		return fmt.Errorf("illegal digest (%s): must be of the form [a-zA-Z0-9-_+.]+:[a-fA-F0-9]+", dgst)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (store *TagStore) poolAdd(kind, key string) (chan struct{}, error) {
 | 
			
		||||
	store.Lock()
 | 
			
		||||
	defer store.Unlock()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,8 @@ const (
 | 
			
		|||
	testPrivateImageName     = "127.0.0.1:8000/privateapp"
 | 
			
		||||
	testPrivateImageID       = "5bc255f8699e4ee89ac4469266c3d11515da88fdcbde45d7b069b636ff4efd81"
 | 
			
		||||
	testPrivateImageIDShort  = "5bc255f8699e"
 | 
			
		||||
	testPrivateImageDigest   = "sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb"
 | 
			
		||||
	testPrivateImageTag      = "sometag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func fakeTar() (io.Reader, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +85,9 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
 | 
			
		|||
	if err := store.Set(testPrivateImageName, "", testPrivateImageID, false); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := store.SetDigest(testPrivateImageName, testPrivateImageDigest, testPrivateImageID); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	return store
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -128,6 +133,10 @@ func TestLookupImage(t *testing.T) {
 | 
			
		|||
		"fail:fail",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	digestLookups := []string{
 | 
			
		||||
		testPrivateImageName + "@" + testPrivateImageDigest,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, name := range officialLookups {
 | 
			
		||||
		if img, err := store.LookupImage(name); err != nil {
 | 
			
		||||
			t.Errorf("Error looking up %s: %s", name, err)
 | 
			
		||||
| 
						 | 
				
			
			@ -155,6 +164,16 @@ func TestLookupImage(t *testing.T) {
 | 
			
		|||
			t.Errorf("Expected 0 image, 1 found: %s", name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, name := range digestLookups {
 | 
			
		||||
		if img, err := store.LookupImage(name); err != nil {
 | 
			
		||||
			t.Errorf("Error looking up %s: %s", name, err)
 | 
			
		||||
		} else if img == nil {
 | 
			
		||||
			t.Errorf("Expected 1 image, none found: %s", name)
 | 
			
		||||
		} else if img.ID != testPrivateImageID {
 | 
			
		||||
			t.Errorf("Expected ID '%s' found '%s'", testPrivateImageID, img.ID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValidTagName(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -174,3 +193,24 @@ func TestInvalidTagName(t *testing.T) {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValidateDigest(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		input       string
 | 
			
		||||
		expectError bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"", true},
 | 
			
		||||
		{"latest", true},
 | 
			
		||||
		{"a:b", false},
 | 
			
		||||
		{"aZ0124-.+:bY852-_.+=", false},
 | 
			
		||||
		{"#$%#$^:$%^#$%", true},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, test := range tests {
 | 
			
		||||
		err := validateDigest(test.input)
 | 
			
		||||
		gotError := err != nil
 | 
			
		||||
		if e, a := test.expectError, gotError; e != a {
 | 
			
		||||
			t.Errorf("%d: with input %s, expected error=%t, got %t: %s", i, test.input, test.expectError, gotError, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										535
									
								
								integration-cli/docker_cli_by_digest_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										535
									
								
								integration-cli/docker_cli_by_digest_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,535 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	repoName    = fmt.Sprintf("%v/dockercli/busybox-by-dgst", privateRegistryURL)
 | 
			
		||||
	digestRegex = regexp.MustCompile("Digest: ([^\n]+)")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func setupImage() (string, error) {
 | 
			
		||||
	return setupImageWithTag("latest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupImageWithTag(tag string) (string, error) {
 | 
			
		||||
	containerName := "busyboxbydigest"
 | 
			
		||||
 | 
			
		||||
	c := exec.Command(dockerBinary, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox")
 | 
			
		||||
	if _, err := runCommand(c); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// tag the image to upload it to the private registry
 | 
			
		||||
	repoAndTag := utils.ImageReference(repoName, tag)
 | 
			
		||||
	c = exec.Command(dockerBinary, "commit", containerName, repoAndTag)
 | 
			
		||||
	if out, _, err := runCommandWithOutput(c); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("image tagging failed: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer deleteImages(repoAndTag)
 | 
			
		||||
 | 
			
		||||
	// delete the container as we don't need it any more
 | 
			
		||||
	if err := deleteContainer(containerName); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// push the image
 | 
			
		||||
	c = exec.Command(dockerBinary, "push", repoAndTag)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("pushing the image to the private registry has failed: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// delete our local repo that we previously tagged
 | 
			
		||||
	c = exec.Command(dockerBinary, "rmi", repoAndTag)
 | 
			
		||||
	if out, _, err := runCommandWithOutput(c); err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error deleting images prior to real test: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// the push output includes "Digest: <digest>", so find that
 | 
			
		||||
	matches := digestRegex.FindStringSubmatch(out)
 | 
			
		||||
	if len(matches) != 2 {
 | 
			
		||||
		return "", fmt.Errorf("unable to parse digest from push output: %s", out)
 | 
			
		||||
	}
 | 
			
		||||
	pushDigest := matches[1]
 | 
			
		||||
 | 
			
		||||
	return pushDigest, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPullByTagDisplaysDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	pushDigest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// pull from the registry using the tag
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", repoName)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by tag: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer deleteImages(repoName)
 | 
			
		||||
 | 
			
		||||
	// the pull output includes "Digest: <digest>", so find that
 | 
			
		||||
	matches := digestRegex.FindStringSubmatch(out)
 | 
			
		||||
	if len(matches) != 2 {
 | 
			
		||||
		t.Fatalf("unable to parse digest from pull output: %s", out)
 | 
			
		||||
	}
 | 
			
		||||
	pullDigest := matches[1]
 | 
			
		||||
 | 
			
		||||
	// make sure the pushed and pull digests match
 | 
			
		||||
	if pushDigest != pullDigest {
 | 
			
		||||
		t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - pull by tag displays digest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPullByDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	pushDigest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// pull from the registry using the <name>@<digest> reference
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", imageReference)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer deleteImages(imageReference)
 | 
			
		||||
 | 
			
		||||
	// the pull output includes "Digest: <digest>", so find that
 | 
			
		||||
	matches := digestRegex.FindStringSubmatch(out)
 | 
			
		||||
	if len(matches) != 2 {
 | 
			
		||||
		t.Fatalf("unable to parse digest from pull output: %s", out)
 | 
			
		||||
	}
 | 
			
		||||
	pullDigest := matches[1]
 | 
			
		||||
 | 
			
		||||
	// make sure the pushed and pull digests match
 | 
			
		||||
	if pushDigest != pullDigest {
 | 
			
		||||
		t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - pull by digest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateByDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	pushDigest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
 | 
			
		||||
 | 
			
		||||
	containerName := "createByDigest"
 | 
			
		||||
	c := exec.Command(dockerBinary, "create", "--name", containerName, imageReference)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error creating by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer deleteContainer(containerName)
 | 
			
		||||
 | 
			
		||||
	res, err := inspectField(containerName, "Config.Image")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("failed to get Config.Image: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	if res != imageReference {
 | 
			
		||||
		t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - create by digest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRunByDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	pushDigest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
 | 
			
		||||
 | 
			
		||||
	containerName := "runByDigest"
 | 
			
		||||
	c := exec.Command(dockerBinary, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest")
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error run by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	defer deleteContainer(containerName)
 | 
			
		||||
 | 
			
		||||
	foundRegex := regexp.MustCompile("found=([^\n]+)")
 | 
			
		||||
	matches := foundRegex.FindStringSubmatch(out)
 | 
			
		||||
	if len(matches) != 2 {
 | 
			
		||||
		t.Fatalf("error locating expected 'found=1' output: %s", out)
 | 
			
		||||
	}
 | 
			
		||||
	if matches[1] != "1" {
 | 
			
		||||
		t.Fatalf("Expected %q, got %q", "1", matches[1])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res, err := inspectField(containerName, "Config.Image")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("failed to get Config.Image: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	if res != imageReference {
 | 
			
		||||
		t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - run by digest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRemoveImageByDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	digest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
 | 
			
		||||
 | 
			
		||||
	// pull from the registry using the <name>@<digest> reference
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", imageReference)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure inspect runs ok
 | 
			
		||||
	if _, err := inspectField(imageReference, "Id"); err != nil {
 | 
			
		||||
		t.Fatalf("failed to inspect image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// do the delete
 | 
			
		||||
	if err := deleteImages(imageReference); err != nil {
 | 
			
		||||
		t.Fatalf("unexpected error deleting image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// try to inspect again - it should error this time
 | 
			
		||||
	if _, err := inspectField(imageReference, "Id"); err == nil {
 | 
			
		||||
		t.Fatalf("unexpected nil err trying to inspect what should be a non-existent image")
 | 
			
		||||
	} else if !strings.Contains(err.Error(), "No such image") {
 | 
			
		||||
		t.Fatalf("expected 'No such image' output, got %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - remove image by digest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBuildByDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	digest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
 | 
			
		||||
 | 
			
		||||
	// pull from the registry using the <name>@<digest> reference
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", imageReference)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the image id
 | 
			
		||||
	imageID, err := inspectField(imageReference, "Id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error getting image id: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// do the build
 | 
			
		||||
	name := "buildbydigest"
 | 
			
		||||
	defer deleteImages(name)
 | 
			
		||||
	_, err = buildImage(name, fmt.Sprintf(
 | 
			
		||||
		`FROM %s
 | 
			
		||||
     CMD ["/bin/echo", "Hello World"]`, imageReference),
 | 
			
		||||
		true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get the build's image id
 | 
			
		||||
	res, err := inspectField(name, "Config.Image")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	// make sure they match
 | 
			
		||||
	if res != imageID {
 | 
			
		||||
		t.Fatalf("Image %s, expected %s", res, imageID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - build by digest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTagByDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	digest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
 | 
			
		||||
 | 
			
		||||
	// pull from the registry using the <name>@<digest> reference
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", imageReference)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// tag it
 | 
			
		||||
	tag := "tagbydigest"
 | 
			
		||||
	c = exec.Command(dockerBinary, "tag", imageReference, tag)
 | 
			
		||||
	if _, err := runCommand(c); err != nil {
 | 
			
		||||
		t.Fatalf("unexpected error tagging: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectedID, err := inspectField(imageReference, "Id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error getting original image id: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tagID, err := inspectField(tag, "Id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error getting tagged image id: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if tagID != expectedID {
 | 
			
		||||
		t.Fatalf("expected image id %q, got %q", expectedID, tagID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - tag by digest")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestListImagesWithoutDigests(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	digest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, digest)
 | 
			
		||||
 | 
			
		||||
	// pull from the registry using the <name>@<digest> reference
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", imageReference)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c = exec.Command(dockerBinary, "images")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error listing images: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.Contains(out, "DIGEST") {
 | 
			
		||||
		t.Fatalf("list output should not have contained DIGEST header: %s", out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - list images - digest header not displayed by default")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestListImagesWithDigests(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
	defer deleteImages(repoName+":tag1", repoName+":tag2")
 | 
			
		||||
 | 
			
		||||
	// setup image1
 | 
			
		||||
	digest1, err := setupImageWithTag("tag1")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1)
 | 
			
		||||
	defer deleteImages(imageReference1)
 | 
			
		||||
	t.Logf("imageReference1 = %s", imageReference1)
 | 
			
		||||
 | 
			
		||||
	// pull image1 by digest
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", imageReference1)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// list images
 | 
			
		||||
	c = exec.Command(dockerBinary, "images", "--digests")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error listing images: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure repo shown, tag=<none>, digest = $digest1
 | 
			
		||||
	re1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1 + `\s`)
 | 
			
		||||
	if !re1.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", re1.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// setup image2
 | 
			
		||||
	digest2, err := setupImageWithTag("tag2")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2)
 | 
			
		||||
	defer deleteImages(imageReference2)
 | 
			
		||||
	t.Logf("imageReference2 = %s", imageReference2)
 | 
			
		||||
 | 
			
		||||
	// pull image1 by digest
 | 
			
		||||
	c = exec.Command(dockerBinary, "pull", imageReference1)
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// pull image2 by digest
 | 
			
		||||
	c = exec.Command(dockerBinary, "pull", imageReference2)
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// list images
 | 
			
		||||
	c = exec.Command(dockerBinary, "images", "--digests")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error listing images: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure repo shown, tag=<none>, digest = $digest1
 | 
			
		||||
	if !re1.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", re1.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure repo shown, tag=<none>, digest = $digest2
 | 
			
		||||
	re2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2 + `\s`)
 | 
			
		||||
	if !re2.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", re2.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// pull tag1
 | 
			
		||||
	c = exec.Command(dockerBinary, "pull", repoName+":tag1")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling tag1: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// list images
 | 
			
		||||
	c = exec.Command(dockerBinary, "images", "--digests")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error listing images: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure image 1 has repo, tag, <none> AND repo, <none>, digest
 | 
			
		||||
	reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*<none>\s`)
 | 
			
		||||
	reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest1 + `\s`)
 | 
			
		||||
	if !reWithTag1.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", reWithTag1.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
	if !reWithDigest1.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", reWithDigest1.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
	// make sure image 2 has repo, <none>, digest
 | 
			
		||||
	if !re2.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", re2.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// pull tag 2
 | 
			
		||||
	c = exec.Command(dockerBinary, "pull", repoName+":tag2")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling tag2: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// list images
 | 
			
		||||
	c = exec.Command(dockerBinary, "images", "--digests")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error listing images: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure image 1 has repo, tag, digest
 | 
			
		||||
	if !reWithTag1.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", re1.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure image 2 has repo, tag, digest
 | 
			
		||||
	reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*<none>\s`)
 | 
			
		||||
	reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*<none>\s*` + digest2 + `\s`)
 | 
			
		||||
	if !reWithTag2.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", reWithTag2.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
	if !reWithDigest2.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", reWithDigest2.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// list images
 | 
			
		||||
	c = exec.Command(dockerBinary, "images", "--digests")
 | 
			
		||||
	out, _, err = runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error listing images: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// make sure image 1 has repo, tag, digest
 | 
			
		||||
	if !reWithTag1.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", re1.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
	// make sure image 2 has repo, tag, digest
 | 
			
		||||
	if !reWithTag2.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", re2.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
	// make sure busybox has tag, but not digest
 | 
			
		||||
	busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*<none>\s`)
 | 
			
		||||
	if !busyboxRe.MatchString(out) {
 | 
			
		||||
		t.Fatalf("expected %q: %s", busyboxRe.String(), out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - list images with digests")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDeleteImageByIDOnlyPulledByDigest(t *testing.T) {
 | 
			
		||||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	pushDigest, err := setupImage()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error setting up image: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// pull from the registry using the <name>@<digest> reference
 | 
			
		||||
	imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest)
 | 
			
		||||
	c := exec.Command(dockerBinary, "pull", imageReference)
 | 
			
		||||
	out, _, err := runCommandWithOutput(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error pulling by digest: %s, %v", out, err)
 | 
			
		||||
	}
 | 
			
		||||
	// just in case...
 | 
			
		||||
	defer deleteImages(imageReference)
 | 
			
		||||
 | 
			
		||||
	imageID, err := inspectField(imageReference, ".Id")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error inspecting image id: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c = exec.Command(dockerBinary, "rmi", imageID)
 | 
			
		||||
	if _, err := runCommand(c); err != nil {
 | 
			
		||||
		t.Fatalf("error deleting image by id: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logDone("by_digest - delete image by id only pulled by digest")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ func TestPushBusyboxImage(t *testing.T) {
 | 
			
		|||
	defer setupRegistry(t)()
 | 
			
		||||
 | 
			
		||||
	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
 | 
			
		||||
	// tag the image to upload it tot he private registry
 | 
			
		||||
	// tag the image to upload it to the private registry
 | 
			
		||||
	tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName)
 | 
			
		||||
	if out, _, err := runCommandWithOutput(tagCmd); err != nil {
 | 
			
		||||
		t.Fatalf("image tagging failed: %s, %v", out, err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,11 +62,17 @@ func ParseTCPAddr(addr string, defaultAddr string) (string, error) {
 | 
			
		|||
	return fmt.Sprintf("tcp://%s:%d", host, p), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get a repos name and returns the right reposName + tag
 | 
			
		||||
// Get a repos name and returns the right reposName + tag|digest
 | 
			
		||||
// The tag can be confusing because of a port in a repository name.
 | 
			
		||||
//     Ex: localhost.localdomain:5000/samalba/hipache:latest
 | 
			
		||||
//     Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb
 | 
			
		||||
func ParseRepositoryTag(repos string) (string, string) {
 | 
			
		||||
	n := strings.LastIndex(repos, ":")
 | 
			
		||||
	n := strings.Index(repos, "@")
 | 
			
		||||
	if n >= 0 {
 | 
			
		||||
		parts := strings.Split(repos, "@")
 | 
			
		||||
		return parts[0], parts[1]
 | 
			
		||||
	}
 | 
			
		||||
	n = strings.LastIndex(repos, ":")
 | 
			
		||||
	if n < 0 {
 | 
			
		||||
		return repos, ""
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,18 +49,27 @@ func TestParseRepositoryTag(t *testing.T) {
 | 
			
		|||
	if repo, tag := ParseRepositoryTag("root:tag"); repo != "root" || tag != "tag" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "tag", repo, tag)
 | 
			
		||||
	}
 | 
			
		||||
	if repo, digest := ParseRepositoryTag("root@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "root" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "root", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
 | 
			
		||||
	}
 | 
			
		||||
	if repo, tag := ParseRepositoryTag("user/repo"); repo != "user/repo" || tag != "" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "", repo, tag)
 | 
			
		||||
	}
 | 
			
		||||
	if repo, tag := ParseRepositoryTag("user/repo:tag"); repo != "user/repo" || tag != "tag" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "tag", repo, tag)
 | 
			
		||||
	}
 | 
			
		||||
	if repo, digest := ParseRepositoryTag("user/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "user/repo" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "user/repo", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
 | 
			
		||||
	}
 | 
			
		||||
	if repo, tag := ParseRepositoryTag("url:5000/repo"); repo != "url:5000/repo" || tag != "" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "", repo, tag)
 | 
			
		||||
	}
 | 
			
		||||
	if repo, tag := ParseRepositoryTag("url:5000/repo:tag"); repo != "url:5000/repo" || tag != "tag" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag)
 | 
			
		||||
	}
 | 
			
		||||
	if repo, digest := ParseRepositoryTag("url:5000/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "url:5000/repo" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
 | 
			
		||||
		t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "url:5000/repo", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParsePortMapping(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,8 @@ import (
 | 
			
		|||
	"github.com/docker/docker/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const DockerDigestHeader = "Docker-Content-Digest"
 | 
			
		||||
 | 
			
		||||
func getV2Builder(e *Endpoint) *v2.URLBuilder {
 | 
			
		||||
	if e.URLBuilder == nil {
 | 
			
		||||
		e.URLBuilder = v2.NewURLBuilder(e.URL)
 | 
			
		||||
| 
						 | 
				
			
			@ -63,10 +65,10 @@ func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bo
 | 
			
		|||
//  1.c) if anything else, err
 | 
			
		||||
// 2) PUT the created/signed manifest
 | 
			
		||||
//
 | 
			
		||||
func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) ([]byte, error) {
 | 
			
		||||
func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) ([]byte, string, error) {
 | 
			
		||||
	routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	method := "GET"
 | 
			
		||||
| 
						 | 
				
			
			@ -74,30 +76,30 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
 | 
			
		|||
 | 
			
		||||
	req, err := r.reqFactory.NewRequest(method, routeURL, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, "", err
 | 
			
		||||
	}
 | 
			
		||||
	if err := auth.Authorize(req); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, "", err
 | 
			
		||||
	}
 | 
			
		||||
	res, _, err := r.doRequest(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
		return nil, "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer res.Body.Close()
 | 
			
		||||
	if res.StatusCode != 200 {
 | 
			
		||||
		if res.StatusCode == 401 {
 | 
			
		||||
			return nil, errLoginRequired
 | 
			
		||||
			return nil, "", errLoginRequired
 | 
			
		||||
		} else if res.StatusCode == 404 {
 | 
			
		||||
			return nil, ErrDoesNotExist
 | 
			
		||||
			return nil, "", ErrDoesNotExist
 | 
			
		||||
		}
 | 
			
		||||
		return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
 | 
			
		||||
		return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf, err := ioutil.ReadAll(res.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Error while reading the http response: %s", err)
 | 
			
		||||
		return nil, "", fmt.Errorf("Error while reading the http response: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return buf, nil
 | 
			
		||||
	return buf, res.Header.Get(DockerDigestHeader), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// - Succeeded to head image blob (already exists)
 | 
			
		||||
| 
						 | 
				
			
			@ -261,41 +263,41 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Finally Push the (signed) manifest of the blobs we've just pushed
 | 
			
		||||
func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, manifestRdr io.Reader, auth *RequestAuthorization) error {
 | 
			
		||||
func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, manifestRdr io.Reader, auth *RequestAuthorization) (string, error) {
 | 
			
		||||
	routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	method := "PUT"
 | 
			
		||||
	log.Debugf("[registry] Calling %q %s", method, routeURL)
 | 
			
		||||
	req, err := r.reqFactory.NewRequest(method, routeURL, manifestRdr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if err := auth.Authorize(req); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	res, _, err := r.doRequest(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer res.Body.Close()
 | 
			
		||||
 | 
			
		||||
	// All 2xx and 3xx responses can be accepted for a put.
 | 
			
		||||
	if res.StatusCode >= 400 {
 | 
			
		||||
		if res.StatusCode == 401 {
 | 
			
		||||
			return errLoginRequired
 | 
			
		||||
			return "", errLoginRequired
 | 
			
		||||
		}
 | 
			
		||||
		errBody, err := ioutil.ReadAll(res.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
 | 
			
		||||
		return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
 | 
			
		||||
		return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return res.Header.Get(DockerDigestHeader), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type remoteTags struct {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,3 +17,6 @@ var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentReg
 | 
			
		|||
 | 
			
		||||
// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
 | 
			
		||||
var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
 | 
			
		||||
 | 
			
		||||
// DigestRegexp matches valid digest types.
 | 
			
		||||
var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+`)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,11 +33,11 @@ func Router() *mux.Router {
 | 
			
		|||
		Path("/v2/").
 | 
			
		||||
		Name(RouteNameBase)
 | 
			
		||||
 | 
			
		||||
	// GET      /v2/<name>/manifest/<tag>	Image Manifest	Fetch the image manifest identified by name and tag.
 | 
			
		||||
	// PUT      /v2/<name>/manifest/<tag>	Image Manifest	Upload the image manifest identified by name and tag.
 | 
			
		||||
	// DELETE   /v2/<name>/manifest/<tag>	Image Manifest	Delete the image identified by name and tag.
 | 
			
		||||
	// GET      /v2/<name>/manifest/<reference>	Image Manifest	Fetch the image manifest identified by name and reference where reference can be a tag or digest.
 | 
			
		||||
	// PUT      /v2/<name>/manifest/<reference>	Image Manifest	Upload the image manifest identified by name and reference where reference can be a tag or digest.
 | 
			
		||||
	// DELETE   /v2/<name>/manifest/<reference>	Image Manifest	Delete the image identified by name and reference where reference can be a tag or digest.
 | 
			
		||||
	router.
 | 
			
		||||
		Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{tag:" + TagNameRegexp.String() + "}").
 | 
			
		||||
		Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + DigestRegexp.String() + "}").
 | 
			
		||||
		Name(RouteNameManifest)
 | 
			
		||||
 | 
			
		||||
	// GET	/v2/<name>/tags/list	Tags	Fetch the tags under the repository identified by name.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,16 +55,16 @@ func TestRouter(t *testing.T) {
 | 
			
		|||
			RouteName:  RouteNameManifest,
 | 
			
		||||
			RequestURI: "/v2/foo/manifests/bar",
 | 
			
		||||
			Vars: map[string]string{
 | 
			
		||||
				"name": "foo",
 | 
			
		||||
				"tag":  "bar",
 | 
			
		||||
				"name":      "foo",
 | 
			
		||||
				"reference": "bar",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			RouteName:  RouteNameManifest,
 | 
			
		||||
			RequestURI: "/v2/foo/bar/manifests/tag",
 | 
			
		||||
			Vars: map[string]string{
 | 
			
		||||
				"name": "foo/bar",
 | 
			
		||||
				"tag":  "tag",
 | 
			
		||||
				"name":      "foo/bar",
 | 
			
		||||
				"reference": "tag",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			@ -128,8 +128,8 @@ func TestRouter(t *testing.T) {
 | 
			
		|||
			RouteName:  RouteNameManifest,
 | 
			
		||||
			RequestURI: "/v2/foo/bar/manifests/manifests/tags",
 | 
			
		||||
			Vars: map[string]string{
 | 
			
		||||
				"name": "foo/bar/manifests",
 | 
			
		||||
				"tag":  "tags",
 | 
			
		||||
				"name":      "foo/bar/manifests",
 | 
			
		||||
				"reference": "tags",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,11 +74,11 @@ func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
 | 
			
		|||
	return tagsURL.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildManifestURL constructs a url for the manifest identified by name and tag.
 | 
			
		||||
func (ub *URLBuilder) BuildManifestURL(name, tag string) (string, error) {
 | 
			
		||||
// BuildManifestURL constructs a url for the manifest identified by name and reference.
 | 
			
		||||
func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
 | 
			
		||||
	route := ub.cloneRoute(RouteNameManifest)
 | 
			
		||||
 | 
			
		||||
	manifestURL, err := route.URL("name", name, "tag", tag)
 | 
			
		||||
	manifestURL, err := route.URL("name", name, "reference", reference)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -535,3 +535,20 @@ func (wc *WriteCounter) Write(p []byte) (count int, err error) {
 | 
			
		|||
	wc.Count += int64(count)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ImageReference combines `repo` and `ref` and returns a string representing
 | 
			
		||||
// the combination. If `ref` is a digest (meaning it's of the form
 | 
			
		||||
// <algorithm>:<digest>, the returned string is <repo>@<ref>. Otherwise,
 | 
			
		||||
// ref is assumed to be a tag, and the returned string is <repo>:<tag>.
 | 
			
		||||
func ImageReference(repo, ref string) string {
 | 
			
		||||
	if DigestReference(ref) {
 | 
			
		||||
		return repo + "@" + ref
 | 
			
		||||
	}
 | 
			
		||||
	return repo + ":" + ref
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DigestReference returns true if ref is a digest reference; i.e. if it
 | 
			
		||||
// is of the form <algorithm>:<digest>.
 | 
			
		||||
func DigestReference(ref string) bool {
 | 
			
		||||
	return strings.Contains(ref, ":")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -122,3 +122,33 @@ func TestWriteCounter(t *testing.T) {
 | 
			
		|||
		t.Error("Wrong message written")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestImageReference(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		repo     string
 | 
			
		||||
		ref      string
 | 
			
		||||
		expected string
 | 
			
		||||
	}{
 | 
			
		||||
		{"repo", "tag", "repo:tag"},
 | 
			
		||||
		{"repo", "sha256:c100b11b25d0cacd52c14e0e7bf525e1a4c0e6aec8827ae007055545909d1a64", "repo@sha256:c100b11b25d0cacd52c14e0e7bf525e1a4c0e6aec8827ae007055545909d1a64"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, test := range tests {
 | 
			
		||||
		actual := ImageReference(test.repo, test.ref)
 | 
			
		||||
		if test.expected != actual {
 | 
			
		||||
			t.Errorf("%d: expected %q, got %q", i, test.expected, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDigestReference(t *testing.T) {
 | 
			
		||||
	input := "sha256:c100b11b25d0cacd52c14e0e7bf525e1a4c0e6aec8827ae007055545909d1a64"
 | 
			
		||||
	if !DigestReference(input) {
 | 
			
		||||
		t.Errorf("Expected DigestReference=true for input %q", input)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	input = "latest"
 | 
			
		||||
	if DigestReference(input) {
 | 
			
		||||
		t.Errorf("Unexpected DigestReference=true for input %q", input)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue