Skip to content

Commit

Permalink
Add ability to refer to image by name + digest
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Andy Goldstein committed Mar 17, 2015
1 parent ad56b5c commit a2b0c97
Show file tree
Hide file tree
Showing 28 changed files with 984 additions and 115 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Expand Up @@ -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) \
Expand Down
42 changes: 33 additions & 9 deletions api/client/commands.go
Expand Up @@ -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)

Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 {
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 {
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, "<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)
}
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions daemon/image_delete.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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()
}
Expand Down
3 changes: 2 additions & 1 deletion daemon/list.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions docs/man/docker-images.1.md
Expand Up @@ -8,6 +8,7 @@ docker-images - List images
**docker images**
[**--help**]
[**-a**|**--all**[=*false*]]
[**--digests**[=*false*]]
[**-f**|**--filter**[=*[]*]]
[**--no-trunc**[=*false*]]
[**-q**|**--quiet**[=*false*]]
Expand All @@ -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.

Expand Down
4 changes: 4 additions & 0 deletions docs/sources/reference/api/docker_remote_api.md
Expand Up @@ -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

Expand Down
39 changes: 39 additions & 0 deletions docs/sources/reference/api/docker_remote_api_v1.18.md
Expand Up @@ -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:

Expand Down
9 changes: 7 additions & 2 deletions docs/sources/reference/builder.md
Expand Up @@ -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
Expand All @@ -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

Expand Down
42 changes: 39 additions & 3 deletions docs/sources/reference/commandline/cli.md
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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...]
Expand Down
10 changes: 8 additions & 2 deletions docs/sources/reference/run.md
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down
3 changes: 2 additions & 1 deletion graph/history.go
Expand Up @@ -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 {
Expand All @@ -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))
}
}

Expand Down
2 changes: 1 addition & 1 deletion graph/import.go
Expand Up @@ -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)
Expand Down

0 comments on commit a2b0c97

Please sign in to comment.