Add distribution package for managing pulls and pushes. This is based on
the old code in the graph package, with major changes to work with the
new image/layer model.
Add v1 migration code.
Update registry, api/*, and daemon packages to use the reference
package's types where applicable.
Update daemon package to use image/layer/tag stores instead of the graph
package
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -18,16 +18,15 @@ import ( |
| 18 | 18 |
"strconv" |
| 19 | 19 |
"strings" |
| 20 | 20 |
|
| 21 |
+ "github.com/docker/distribution/reference" |
|
| 21 | 22 |
"github.com/docker/docker/api" |
| 22 | 23 |
Cli "github.com/docker/docker/cli" |
| 23 |
- "github.com/docker/docker/graph/tags" |
|
| 24 | 24 |
"github.com/docker/docker/opts" |
| 25 | 25 |
"github.com/docker/docker/pkg/archive" |
| 26 | 26 |
"github.com/docker/docker/pkg/fileutils" |
| 27 | 27 |
"github.com/docker/docker/pkg/httputils" |
| 28 | 28 |
"github.com/docker/docker/pkg/jsonmessage" |
| 29 | 29 |
flag "github.com/docker/docker/pkg/mflag" |
| 30 |
- "github.com/docker/docker/pkg/parsers" |
|
| 31 | 30 |
"github.com/docker/docker/pkg/progressreader" |
| 32 | 31 |
"github.com/docker/docker/pkg/streamformatter" |
| 33 | 32 |
"github.com/docker/docker/pkg/ulimit" |
| ... | ... |
@@ -35,6 +34,7 @@ import ( |
| 35 | 35 |
"github.com/docker/docker/pkg/urlutil" |
| 36 | 36 |
"github.com/docker/docker/registry" |
| 37 | 37 |
"github.com/docker/docker/runconfig" |
| 38 |
+ tagpkg "github.com/docker/docker/tag" |
|
| 38 | 39 |
"github.com/docker/docker/utils" |
| 39 | 40 |
) |
| 40 | 41 |
|
| ... | ... |
@@ -323,7 +323,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 323 | 323 |
// Since the build was successful, now we must tag any of the resolved |
| 324 | 324 |
// images from the above Dockerfile rewrite. |
| 325 | 325 |
for _, resolved := range resolvedTags {
|
| 326 |
- if err := cli.tagTrusted(resolved.repoInfo, resolved.digestRef, resolved.tagRef); err != nil {
|
|
| 326 |
+ if err := cli.tagTrusted(resolved.digestRef, resolved.tagRef); err != nil {
|
|
| 327 | 327 |
return err |
| 328 | 328 |
} |
| 329 | 329 |
} |
| ... | ... |
@@ -333,16 +333,12 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 333 | 333 |
|
| 334 | 334 |
// validateTag checks if the given image name can be resolved. |
| 335 | 335 |
func validateTag(rawRepo string) (string, error) {
|
| 336 |
- repository, tag := parsers.ParseRepositoryTag(rawRepo) |
|
| 337 |
- if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 336 |
+ ref, err := reference.ParseNamed(rawRepo) |
|
| 337 |
+ if err != nil {
|
|
| 338 | 338 |
return "", err |
| 339 | 339 |
} |
| 340 | 340 |
|
| 341 |
- if len(tag) == 0 {
|
|
| 342 |
- return rawRepo, nil |
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- if err := tags.ValidateTagName(tag); err != nil {
|
|
| 341 |
+ if err := registry.ValidateRepositoryName(ref); err != nil {
|
|
| 346 | 342 |
return "", err |
| 347 | 343 |
} |
| 348 | 344 |
|
| ... | ... |
@@ -565,15 +561,16 @@ func (td *trustedDockerfile) Close() error {
|
| 565 | 565 |
// resolvedTag records the repository, tag, and resolved digest reference |
| 566 | 566 |
// from a Dockerfile rewrite. |
| 567 | 567 |
type resolvedTag struct {
|
| 568 |
- repoInfo *registry.RepositoryInfo |
|
| 569 |
- digestRef, tagRef registry.Reference |
|
| 568 |
+ repoInfo *registry.RepositoryInfo |
|
| 569 |
+ digestRef reference.Canonical |
|
| 570 |
+ tagRef reference.NamedTagged |
|
| 570 | 571 |
} |
| 571 | 572 |
|
| 572 | 573 |
// rewriteDockerfileFrom rewrites the given Dockerfile by resolving images in |
| 573 | 574 |
// "FROM <image>" instructions to a digest reference. `translator` is a |
| 574 | 575 |
// function that takes a repository name and tag reference and returns a |
| 575 | 576 |
// trusted digest reference. |
| 576 |
-func rewriteDockerfileFrom(dockerfileName string, translator func(string, registry.Reference) (registry.Reference, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
|
|
| 577 |
+func rewriteDockerfileFrom(dockerfileName string, translator func(reference.NamedTagged) (reference.Canonical, error)) (newDockerfile *trustedDockerfile, resolvedTags []*resolvedTag, err error) {
|
|
| 577 | 578 |
dockerfile, err := os.Open(dockerfileName) |
| 578 | 579 |
if err != nil {
|
| 579 | 580 |
return nil, nil, fmt.Errorf("unable to open Dockerfile: %v", err)
|
| ... | ... |
@@ -607,29 +604,39 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist |
| 607 | 607 |
matches := dockerfileFromLinePattern.FindStringSubmatch(line) |
| 608 | 608 |
if matches != nil && matches[1] != "scratch" {
|
| 609 | 609 |
// Replace the line with a resolved "FROM repo@digest" |
| 610 |
- repo, tag := parsers.ParseRepositoryTag(matches[1]) |
|
| 611 |
- if tag == "" {
|
|
| 612 |
- tag = tags.DefaultTag |
|
| 610 |
+ ref, err := reference.ParseNamed(matches[1]) |
|
| 611 |
+ if err != nil {
|
|
| 612 |
+ return nil, nil, err |
|
| 613 | 613 |
} |
| 614 | 614 |
|
| 615 |
- repoInfo, err := registry.ParseRepositoryInfo(repo) |
|
| 616 |
- if err != nil {
|
|
| 617 |
- return nil, nil, fmt.Errorf("unable to parse repository info %q: %v", repo, err)
|
|
| 615 |
+ digested := false |
|
| 616 |
+ switch ref.(type) {
|
|
| 617 |
+ case reference.Tagged: |
|
| 618 |
+ case reference.Digested: |
|
| 619 |
+ digested = true |
|
| 620 |
+ default: |
|
| 621 |
+ ref, err = reference.WithTag(ref, tagpkg.DefaultTag) |
|
| 622 |
+ if err != nil {
|
|
| 623 |
+ return nil, nil, err |
|
| 624 |
+ } |
|
| 618 | 625 |
} |
| 619 | 626 |
|
| 620 |
- ref := registry.ParseReference(tag) |
|
| 627 |
+ repoInfo, err := registry.ParseRepositoryInfo(ref) |
|
| 628 |
+ if err != nil {
|
|
| 629 |
+ return nil, nil, fmt.Errorf("unable to parse repository info %q: %v", ref.String(), err)
|
|
| 630 |
+ } |
|
| 621 | 631 |
|
| 622 |
- if !ref.HasDigest() && isTrusted() {
|
|
| 623 |
- trustedRef, err := translator(repo, ref) |
|
| 632 |
+ if !digested && isTrusted() {
|
|
| 633 |
+ trustedRef, err := translator(ref.(reference.NamedTagged)) |
|
| 624 | 634 |
if err != nil {
|
| 625 | 635 |
return nil, nil, err |
| 626 | 636 |
} |
| 627 | 637 |
|
| 628 |
- line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.ImageName(repo)))
|
|
| 638 |
+ line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.String()))
|
|
| 629 | 639 |
resolvedTags = append(resolvedTags, &resolvedTag{
|
| 630 | 640 |
repoInfo: repoInfo, |
| 631 | 641 |
digestRef: trustedRef, |
| 632 |
- tagRef: ref, |
|
| 642 |
+ tagRef: ref.(reference.NamedTagged), |
|
| 633 | 643 |
}) |
| 634 | 644 |
} |
| 635 | 645 |
} |
| ... | ... |
@@ -2,14 +2,15 @@ package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 |
+ "errors" |
|
| 5 | 6 |
"fmt" |
| 6 | 7 |
"net/url" |
| 7 | 8 |
|
| 9 |
+ "github.com/docker/distribution/reference" |
|
| 8 | 10 |
"github.com/docker/docker/api/types" |
| 9 | 11 |
Cli "github.com/docker/docker/cli" |
| 10 | 12 |
"github.com/docker/docker/opts" |
| 11 | 13 |
flag "github.com/docker/docker/pkg/mflag" |
| 12 |
- "github.com/docker/docker/pkg/parsers" |
|
| 13 | 14 |
"github.com/docker/docker/registry" |
| 14 | 15 |
"github.com/docker/docker/runconfig" |
| 15 | 16 |
) |
| ... | ... |
@@ -32,20 +33,35 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
| 32 | 32 |
cmd.ParseFlags(args, true) |
| 33 | 33 |
|
| 34 | 34 |
var ( |
| 35 |
- name = cmd.Arg(0) |
|
| 36 |
- repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) |
|
| 35 |
+ name = cmd.Arg(0) |
|
| 36 |
+ repositoryAndTag = cmd.Arg(1) |
|
| 37 |
+ repositoryName string |
|
| 38 |
+ tag string |
|
| 37 | 39 |
) |
| 38 | 40 |
|
| 39 | 41 |
//Check if the given image name can be resolved |
| 40 |
- if repository != "" {
|
|
| 41 |
- if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 42 |
+ if repositoryAndTag != "" {
|
|
| 43 |
+ ref, err := reference.ParseNamed(repositoryAndTag) |
|
| 44 |
+ if err != nil {
|
|
| 42 | 45 |
return err |
| 43 | 46 |
} |
| 47 |
+ if err := registry.ValidateRepositoryName(ref); err != nil {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ repositoryName = ref.Name() |
|
| 52 |
+ |
|
| 53 |
+ switch x := ref.(type) {
|
|
| 54 |
+ case reference.Digested: |
|
| 55 |
+ return errors.New("cannot commit to digest reference")
|
|
| 56 |
+ case reference.Tagged: |
|
| 57 |
+ tag = x.Tag() |
|
| 58 |
+ } |
|
| 44 | 59 |
} |
| 45 | 60 |
|
| 46 | 61 |
v := url.Values{}
|
| 47 | 62 |
v.Set("container", name)
|
| 48 |
- v.Set("repo", repository)
|
|
| 63 |
+ v.Set("repo", repositoryName)
|
|
| 49 | 64 |
v.Set("tag", tag)
|
| 50 | 65 |
v.Set("comment", *flComment)
|
| 51 | 66 |
v.Set("author", *flAuthor)
|
| ... | ... |
@@ -9,12 +9,12 @@ import ( |
| 9 | 9 |
"os" |
| 10 | 10 |
"strings" |
| 11 | 11 |
|
| 12 |
+ "github.com/docker/distribution/reference" |
|
| 12 | 13 |
"github.com/docker/docker/api/types" |
| 13 | 14 |
Cli "github.com/docker/docker/cli" |
| 14 |
- "github.com/docker/docker/graph/tags" |
|
| 15 |
- "github.com/docker/docker/pkg/parsers" |
|
| 16 | 15 |
"github.com/docker/docker/registry" |
| 17 | 16 |
"github.com/docker/docker/runconfig" |
| 17 |
+ tagpkg "github.com/docker/docker/tag" |
|
| 18 | 18 |
) |
| 19 | 19 |
|
| 20 | 20 |
func (cli *DockerCli) pullImage(image string) error {
|
| ... | ... |
@@ -23,16 +23,28 @@ func (cli *DockerCli) pullImage(image string) error {
|
| 23 | 23 |
|
| 24 | 24 |
func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
| 25 | 25 |
v := url.Values{}
|
| 26 |
- repos, tag := parsers.ParseRepositoryTag(image) |
|
| 27 |
- // pull only the image tagged 'latest' if no tag was specified |
|
| 28 |
- if tag == "" {
|
|
| 29 |
- tag = tags.DefaultTag |
|
| 26 |
+ |
|
| 27 |
+ ref, err := reference.ParseNamed(image) |
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ return err |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ var tag string |
|
| 33 |
+ switch x := ref.(type) {
|
|
| 34 |
+ case reference.Digested: |
|
| 35 |
+ tag = x.Digest().String() |
|
| 36 |
+ case reference.Tagged: |
|
| 37 |
+ tag = x.Tag() |
|
| 38 |
+ default: |
|
| 39 |
+ // pull only the image tagged 'latest' if no tag was specified |
|
| 40 |
+ tag = tagpkg.DefaultTag |
|
| 30 | 41 |
} |
| 31 |
- v.Set("fromImage", repos)
|
|
| 42 |
+ |
|
| 43 |
+ v.Set("fromImage", ref.Name())
|
|
| 32 | 44 |
v.Set("tag", tag)
|
| 33 | 45 |
|
| 34 | 46 |
// Resolve the Repository name from fqn to RepositoryInfo |
| 35 |
- repoInfo, err := registry.ParseRepositoryInfo(repos) |
|
| 47 |
+ repoInfo, err := registry.ParseRepositoryInfo(ref) |
|
| 36 | 48 |
if err != nil {
|
| 37 | 49 |
return err |
| 38 | 50 |
} |
| ... | ... |
@@ -94,39 +106,46 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc |
| 94 | 94 |
defer containerIDFile.Close() |
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 |
- repo, tag := parsers.ParseRepositoryTag(config.Image) |
|
| 98 |
- if tag == "" {
|
|
| 99 |
- tag = tags.DefaultTag |
|
| 97 |
+ ref, err := reference.ParseNamed(config.Image) |
|
| 98 |
+ if err != nil {
|
|
| 99 |
+ return nil, err |
|
| 100 | 100 |
} |
| 101 | 101 |
|
| 102 |
- ref := registry.ParseReference(tag) |
|
| 103 |
- var trustedRef registry.Reference |
|
| 102 |
+ isDigested := false |
|
| 103 |
+ switch ref.(type) {
|
|
| 104 |
+ case reference.Tagged: |
|
| 105 |
+ case reference.Digested: |
|
| 106 |
+ isDigested = true |
|
| 107 |
+ default: |
|
| 108 |
+ ref, err = reference.WithTag(ref, tagpkg.DefaultTag) |
|
| 109 |
+ if err != nil {
|
|
| 110 |
+ return nil, err |
|
| 111 |
+ } |
|
| 112 |
+ } |
|
| 104 | 113 |
|
| 105 |
- if isTrusted() && !ref.HasDigest() {
|
|
| 114 |
+ var trustedRef reference.Canonical |
|
| 115 |
+ |
|
| 116 |
+ if isTrusted() && !isDigested {
|
|
| 106 | 117 |
var err error |
| 107 |
- trustedRef, err = cli.trustedReference(repo, ref) |
|
| 118 |
+ trustedRef, err = cli.trustedReference(ref.(reference.NamedTagged)) |
|
| 108 | 119 |
if err != nil {
|
| 109 | 120 |
return nil, err |
| 110 | 121 |
} |
| 111 |
- config.Image = trustedRef.ImageName(repo) |
|
| 122 |
+ config.Image = trustedRef.String() |
|
| 112 | 123 |
} |
| 113 | 124 |
|
| 114 | 125 |
//create the container |
| 115 | 126 |
serverResp, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil)
|
| 116 | 127 |
//if image not found try to pull it |
| 117 | 128 |
if serverResp.statusCode == 404 && strings.Contains(err.Error(), config.Image) {
|
| 118 |
- fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.ImageName(repo)) |
|
| 129 |
+ fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", ref.String()) |
|
| 119 | 130 |
|
| 120 | 131 |
// we don't want to write to stdout anything apart from container.ID |
| 121 | 132 |
if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil {
|
| 122 | 133 |
return nil, err |
| 123 | 134 |
} |
| 124 |
- if trustedRef != nil && !ref.HasDigest() {
|
|
| 125 |
- repoInfo, err := registry.ParseRepositoryInfo(repo) |
|
| 126 |
- if err != nil {
|
|
| 127 |
- return nil, err |
|
| 128 |
- } |
|
| 129 |
- if err := cli.tagTrusted(repoInfo, trustedRef, ref); err != nil {
|
|
| 135 |
+ if trustedRef != nil && !isDigested {
|
|
| 136 |
+ if err := cli.tagTrusted(trustedRef, ref.(reference.NamedTagged)); err != nil {
|
|
| 130 | 137 |
return nil, err |
| 131 | 138 |
} |
| 132 | 139 |
} |
| ... | ... |
@@ -4,18 +4,18 @@ import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"net/url" |
| 7 |
+ "strings" |
|
| 7 | 8 |
"text/tabwriter" |
| 8 | 9 |
"time" |
| 9 | 10 |
|
| 11 |
+ "github.com/docker/distribution/reference" |
|
| 10 | 12 |
"github.com/docker/docker/api/types" |
| 11 | 13 |
Cli "github.com/docker/docker/cli" |
| 12 | 14 |
"github.com/docker/docker/opts" |
| 13 | 15 |
flag "github.com/docker/docker/pkg/mflag" |
| 14 |
- "github.com/docker/docker/pkg/parsers" |
|
| 15 | 16 |
"github.com/docker/docker/pkg/parsers/filters" |
| 16 | 17 |
"github.com/docker/docker/pkg/stringid" |
| 17 | 18 |
"github.com/docker/docker/pkg/units" |
| 18 |
- "github.com/docker/docker/utils" |
|
| 19 | 19 |
) |
| 20 | 20 |
|
| 21 | 21 |
// CmdImages lists the images in a specified repository, or all top-level images if no repository is specified. |
| ... | ... |
@@ -78,9 +78,9 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
| 78 | 78 |
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) |
| 79 | 79 |
if !*quiet {
|
| 80 | 80 |
if *showDigests {
|
| 81 |
- fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 81 |
+ fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tSIZE") |
|
| 82 | 82 |
} else {
|
| 83 |
- fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") |
|
| 83 |
+ fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE") |
|
| 84 | 84 |
} |
| 85 | 85 |
} |
| 86 | 86 |
|
| ... | ... |
@@ -101,21 +101,31 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
| 101 | 101 |
// combine the tags and digests lists |
| 102 | 102 |
tagsAndDigests := append(repoTags, repoDigests...) |
| 103 | 103 |
for _, repoAndRef := range tagsAndDigests {
|
| 104 |
- repo, ref := parsers.ParseRepositoryTag(repoAndRef) |
|
| 105 |
- // default tag and digest to none - if there's a value, it'll be set below |
|
| 104 |
+ // default repo, tag, and digest to none - if there's a value, it'll be set below |
|
| 105 |
+ repo := "<none>" |
|
| 106 | 106 |
tag := "<none>" |
| 107 | 107 |
digest := "<none>" |
| 108 |
- if utils.DigestReference(ref) {
|
|
| 109 |
- digest = ref |
|
| 110 |
- } else {
|
|
| 111 |
- tag = ref |
|
| 108 |
+ |
|
| 109 |
+ if !strings.HasPrefix(repoAndRef, "<none>") {
|
|
| 110 |
+ ref, err := reference.ParseNamed(repoAndRef) |
|
| 111 |
+ if err != nil {
|
|
| 112 |
+ return err |
|
| 113 |
+ } |
|
| 114 |
+ repo = ref.Name() |
|
| 115 |
+ |
|
| 116 |
+ switch x := ref.(type) {
|
|
| 117 |
+ case reference.Digested: |
|
| 118 |
+ digest = x.Digest().String() |
|
| 119 |
+ case reference.Tagged: |
|
| 120 |
+ tag = x.Tag() |
|
| 121 |
+ } |
|
| 112 | 122 |
} |
| 113 | 123 |
|
| 114 | 124 |
if !*quiet {
|
| 115 | 125 |
if *showDigests {
|
| 116 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) |
|
| 126 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.Size))) |
|
| 117 | 127 |
} else {
|
| 118 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) |
|
| 128 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.Size))) |
|
| 119 | 129 |
} |
| 120 | 130 |
} else {
|
| 121 | 131 |
fmt.Fprintln(w, ID) |
| ... | ... |
@@ -6,10 +6,10 @@ import ( |
| 6 | 6 |
"net/url" |
| 7 | 7 |
"os" |
| 8 | 8 |
|
| 9 |
+ "github.com/docker/distribution/reference" |
|
| 9 | 10 |
Cli "github.com/docker/docker/cli" |
| 10 | 11 |
"github.com/docker/docker/opts" |
| 11 | 12 |
flag "github.com/docker/docker/pkg/mflag" |
| 12 |
- "github.com/docker/docker/pkg/parsers" |
|
| 13 | 13 |
"github.com/docker/docker/pkg/urlutil" |
| 14 | 14 |
"github.com/docker/docker/registry" |
| 15 | 15 |
) |
| ... | ... |
@@ -47,8 +47,11 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
| 47 | 47 |
|
| 48 | 48 |
if repository != "" {
|
| 49 | 49 |
//Check if the given image name can be resolved |
| 50 |
- repo, _ := parsers.ParseRepositoryTag(repository) |
|
| 51 |
- if err := registry.ValidateRepositoryName(repo); err != nil {
|
|
| 50 |
+ ref, err := reference.ParseNamed(repository) |
|
| 51 |
+ if err != nil {
|
|
| 52 |
+ return err |
|
| 53 |
+ } |
|
| 54 |
+ if err := registry.ValidateRepositoryName(ref); err != nil {
|
|
| 52 | 55 |
return err |
| 53 | 56 |
} |
| 54 | 57 |
} |
| ... | ... |
@@ -62,8 +62,8 @@ func (c *containerContext) Image() string {
|
| 62 | 62 |
return "<no image>" |
| 63 | 63 |
} |
| 64 | 64 |
if c.trunc {
|
| 65 |
- if stringid.TruncateID(c.c.ImageID) == stringid.TruncateID(c.c.Image) {
|
|
| 66 |
- return stringutils.Truncate(c.c.Image, 12) |
|
| 65 |
+ if trunc := stringid.TruncateID(c.c.ImageID); trunc == stringid.TruncateID(c.c.Image) {
|
|
| 66 |
+ return trunc |
|
| 67 | 67 |
} |
| 68 | 68 |
} |
| 69 | 69 |
return c.c.Image |
| ... | ... |
@@ -1,16 +1,19 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "errors" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"net/url" |
| 6 | 7 |
|
| 8 |
+ "github.com/docker/distribution/reference" |
|
| 7 | 9 |
Cli "github.com/docker/docker/cli" |
| 8 |
- "github.com/docker/docker/graph/tags" |
|
| 9 | 10 |
flag "github.com/docker/docker/pkg/mflag" |
| 10 |
- "github.com/docker/docker/pkg/parsers" |
|
| 11 | 11 |
"github.com/docker/docker/registry" |
| 12 |
+ tagpkg "github.com/docker/docker/tag" |
|
| 12 | 13 |
) |
| 13 | 14 |
|
| 15 |
+var errTagCantBeUsed = errors.New("tag can't be used with --all-tags/-a")
|
|
| 16 |
+ |
|
| 14 | 17 |
// CmdPull pulls an image or a repository from the registry. |
| 15 | 18 |
// |
| 16 | 19 |
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST] |
| ... | ... |
@@ -23,18 +26,38 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
| 23 | 23 |
cmd.ParseFlags(args, true) |
| 24 | 24 |
remote := cmd.Arg(0) |
| 25 | 25 |
|
| 26 |
- taglessRemote, tag := parsers.ParseRepositoryTag(remote) |
|
| 27 |
- if tag == "" && !*allTags {
|
|
| 28 |
- tag = tags.DefaultTag |
|
| 29 |
- fmt.Fprintf(cli.out, "Using default tag: %s\n", tag) |
|
| 30 |
- } else if tag != "" && *allTags {
|
|
| 31 |
- return fmt.Errorf("tag can't be used with --all-tags/-a")
|
|
| 26 |
+ distributionRef, err := reference.ParseNamed(remote) |
|
| 27 |
+ if err != nil {
|
|
| 28 |
+ return err |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ var tag string |
|
| 32 |
+ switch x := distributionRef.(type) {
|
|
| 33 |
+ case reference.Digested: |
|
| 34 |
+ if *allTags {
|
|
| 35 |
+ return errTagCantBeUsed |
|
| 36 |
+ } |
|
| 37 |
+ tag = x.Digest().String() |
|
| 38 |
+ case reference.Tagged: |
|
| 39 |
+ if *allTags {
|
|
| 40 |
+ return errTagCantBeUsed |
|
| 41 |
+ } |
|
| 42 |
+ tag = x.Tag() |
|
| 43 |
+ default: |
|
| 44 |
+ if !*allTags {
|
|
| 45 |
+ tag = tagpkg.DefaultTag |
|
| 46 |
+ distributionRef, err = reference.WithTag(distributionRef, tag) |
|
| 47 |
+ if err != nil {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 50 |
+ fmt.Fprintf(cli.out, "Using default tag: %s\n", tag) |
|
| 51 |
+ } |
|
| 32 | 52 |
} |
| 33 | 53 |
|
| 34 | 54 |
ref := registry.ParseReference(tag) |
| 35 | 55 |
|
| 36 | 56 |
// Resolve the Repository name from fqn to RepositoryInfo |
| 37 |
- repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) |
|
| 57 |
+ repoInfo, err := registry.ParseRepositoryInfo(distributionRef) |
|
| 38 | 58 |
if err != nil {
|
| 39 | 59 |
return err |
| 40 | 60 |
} |
| ... | ... |
@@ -46,7 +69,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
| 46 | 46 |
} |
| 47 | 47 |
|
| 48 | 48 |
v := url.Values{}
|
| 49 |
- v.Set("fromImage", ref.ImageName(taglessRemote))
|
|
| 49 |
+ v.Set("fromImage", distributionRef.String())
|
|
| 50 | 50 |
|
| 51 | 51 |
_, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull")
|
| 52 | 52 |
return err |
| ... | ... |
@@ -1,12 +1,13 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "errors" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"net/url" |
| 6 | 7 |
|
| 8 |
+ "github.com/docker/distribution/reference" |
|
| 7 | 9 |
Cli "github.com/docker/docker/cli" |
| 8 | 10 |
flag "github.com/docker/docker/pkg/mflag" |
| 9 |
- "github.com/docker/docker/pkg/parsers" |
|
| 10 | 11 |
"github.com/docker/docker/registry" |
| 11 | 12 |
) |
| 12 | 13 |
|
| ... | ... |
@@ -20,10 +21,21 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
| 20 | 20 |
|
| 21 | 21 |
cmd.ParseFlags(args, true) |
| 22 | 22 |
|
| 23 |
- remote, tag := parsers.ParseRepositoryTag(cmd.Arg(0)) |
|
| 23 |
+ ref, err := reference.ParseNamed(cmd.Arg(0)) |
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ return err |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ var tag string |
|
| 29 |
+ switch x := ref.(type) {
|
|
| 30 |
+ case reference.Digested: |
|
| 31 |
+ return errors.New("cannot push a digest reference")
|
|
| 32 |
+ case reference.Tagged: |
|
| 33 |
+ tag = x.Tag() |
|
| 34 |
+ } |
|
| 24 | 35 |
|
| 25 | 36 |
// Resolve the Repository name from fqn to RepositoryInfo |
| 26 |
- repoInfo, err := registry.ParseRepositoryInfo(remote) |
|
| 37 |
+ repoInfo, err := registry.ParseRepositoryInfo(ref) |
|
| 27 | 38 |
if err != nil {
|
| 28 | 39 |
return err |
| 29 | 40 |
} |
| ... | ... |
@@ -48,6 +60,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
| 48 | 48 |
v := url.Values{}
|
| 49 | 49 |
v.Set("tag", tag)
|
| 50 | 50 |
|
| 51 |
- _, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
|
| 51 |
+ _, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+ref.Name()+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
|
| 52 | 52 |
return err |
| 53 | 53 |
} |
| ... | ... |
@@ -10,7 +10,6 @@ import ( |
| 10 | 10 |
|
| 11 | 11 |
Cli "github.com/docker/docker/cli" |
| 12 | 12 |
flag "github.com/docker/docker/pkg/mflag" |
| 13 |
- "github.com/docker/docker/pkg/parsers" |
|
| 14 | 13 |
"github.com/docker/docker/pkg/stringutils" |
| 15 | 14 |
"github.com/docker/docker/registry" |
| 16 | 15 |
) |
| ... | ... |
@@ -38,10 +37,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
| 38 | 38 |
v := url.Values{}
|
| 39 | 39 |
v.Set("term", name)
|
| 40 | 40 |
|
| 41 |
- // Resolve the Repository name from fqn to hostname + name |
|
| 42 |
- taglessRemote, _ := parsers.ParseRepositoryTag(name) |
|
| 43 |
- |
|
| 44 |
- indexInfo, err := registry.ParseIndexInfo(taglessRemote) |
|
| 41 |
+ indexInfo, err := registry.ParseSearchIndexInfo(name) |
|
| 45 | 42 |
if err != nil {
|
| 46 | 43 |
return err |
| 47 | 44 |
} |
| ... | ... |
@@ -1,11 +1,12 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "errors" |
|
| 4 | 5 |
"net/url" |
| 5 | 6 |
|
| 7 |
+ "github.com/docker/distribution/reference" |
|
| 6 | 8 |
Cli "github.com/docker/docker/cli" |
| 7 | 9 |
flag "github.com/docker/docker/pkg/mflag" |
| 8 |
- "github.com/docker/docker/pkg/parsers" |
|
| 9 | 10 |
"github.com/docker/docker/registry" |
| 10 | 11 |
) |
| 11 | 12 |
|
| ... | ... |
@@ -19,16 +20,28 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
| 19 | 19 |
|
| 20 | 20 |
cmd.ParseFlags(args, true) |
| 21 | 21 |
|
| 22 |
- var ( |
|
| 23 |
- repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) |
|
| 24 |
- v = url.Values{}
|
|
| 25 |
- ) |
|
| 22 |
+ v := url.Values{}
|
|
| 23 |
+ ref, err := reference.ParseNamed(cmd.Arg(1)) |
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ return err |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ _, isDigested := ref.(reference.Digested) |
|
| 29 |
+ if isDigested {
|
|
| 30 |
+ return errors.New("refusing to create a tag with a digest reference")
|
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ tag := "" |
|
| 34 |
+ tagged, isTagged := ref.(reference.Tagged) |
|
| 35 |
+ if isTagged {
|
|
| 36 |
+ tag = tagged.Tag() |
|
| 37 |
+ } |
|
| 26 | 38 |
|
| 27 | 39 |
//Check if the given image name can be resolved |
| 28 |
- if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 40 |
+ if err := registry.ValidateRepositoryName(ref); err != nil {
|
|
| 29 | 41 |
return err |
| 30 | 42 |
} |
| 31 |
- v.Set("repo", repository)
|
|
| 43 |
+ v.Set("repo", ref.Name())
|
|
| 32 | 44 |
v.Set("tag", tag)
|
| 33 | 45 |
|
| 34 | 46 |
if *force {
|
| ... | ... |
@@ -19,6 +19,7 @@ import ( |
| 19 | 19 |
|
| 20 | 20 |
"github.com/Sirupsen/logrus" |
| 21 | 21 |
"github.com/docker/distribution/digest" |
| 22 |
+ "github.com/docker/distribution/reference" |
|
| 22 | 23 |
"github.com/docker/distribution/registry/client/auth" |
| 23 | 24 |
"github.com/docker/distribution/registry/client/transport" |
| 24 | 25 |
"github.com/docker/docker/cliconfig" |
| ... | ... |
@@ -163,12 +164,12 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut |
| 163 | 163 |
} |
| 164 | 164 |
|
| 165 | 165 |
creds := simpleCredentialStore{auth: authConfig}
|
| 166 |
- tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName, "push", "pull") |
|
| 166 |
+ tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName.Name(), "push", "pull") |
|
| 167 | 167 |
basicHandler := auth.NewBasicHandler(creds) |
| 168 | 168 |
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) |
| 169 | 169 |
tr := transport.NewTransport(base, modifiers...) |
| 170 | 170 |
|
| 171 |
- return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName, server, tr, cli.getPassphraseRetriever()) |
|
| 171 |
+ return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName.Name(), server, tr, cli.getPassphraseRetriever()) |
|
| 172 | 172 |
} |
| 173 | 173 |
|
| 174 | 174 |
func convertTarget(t client.Target) (target, error) {
|
| ... | ... |
@@ -219,8 +220,8 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
| 219 | 219 |
} |
| 220 | 220 |
} |
| 221 | 221 |
|
| 222 |
-func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (registry.Reference, error) {
|
|
| 223 |
- repoInfo, err := registry.ParseRepositoryInfo(repo) |
|
| 222 |
+func (cli *DockerCli) trustedReference(ref reference.NamedTagged) (reference.Canonical, error) {
|
|
| 223 |
+ repoInfo, err := registry.ParseRepositoryInfo(ref) |
|
| 224 | 224 |
if err != nil {
|
| 225 | 225 |
return nil, err |
| 226 | 226 |
} |
| ... | ... |
@@ -234,7 +235,7 @@ func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (reg |
| 234 | 234 |
return nil, err |
| 235 | 235 |
} |
| 236 | 236 |
|
| 237 |
- t, err := notaryRepo.GetTargetByName(ref.String()) |
|
| 237 |
+ t, err := notaryRepo.GetTargetByName(ref.Tag()) |
|
| 238 | 238 |
if err != nil {
|
| 239 | 239 |
return nil, err |
| 240 | 240 |
} |
| ... | ... |
@@ -244,18 +245,17 @@ func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (reg |
| 244 | 244 |
|
| 245 | 245 |
} |
| 246 | 246 |
|
| 247 |
- return registry.DigestReference(r.digest), nil |
|
| 247 |
+ return reference.WithDigest(ref, r.digest) |
|
| 248 | 248 |
} |
| 249 | 249 |
|
| 250 |
-func (cli *DockerCli) tagTrusted(repoInfo *registry.RepositoryInfo, trustedRef, ref registry.Reference) error {
|
|
| 251 |
- fullName := trustedRef.ImageName(repoInfo.LocalName) |
|
| 252 |
- fmt.Fprintf(cli.out, "Tagging %s as %s\n", fullName, ref.ImageName(repoInfo.LocalName)) |
|
| 250 |
+func (cli *DockerCli) tagTrusted(trustedRef reference.Canonical, ref reference.NamedTagged) error {
|
|
| 251 |
+ fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String()) |
|
| 253 | 252 |
tv := url.Values{}
|
| 254 |
- tv.Set("repo", repoInfo.LocalName)
|
|
| 255 |
- tv.Set("tag", ref.String())
|
|
| 253 |
+ tv.Set("repo", trustedRef.Name())
|
|
| 254 |
+ tv.Set("tag", ref.Tag())
|
|
| 256 | 255 |
tv.Set("force", "1")
|
| 257 | 256 |
|
| 258 |
- if _, _, err := readBody(cli.call("POST", "/images/"+fullName+"/tag?"+tv.Encode(), nil, nil)); err != nil {
|
|
| 257 |
+ if _, _, err := readBody(cli.call("POST", "/images/"+trustedRef.String()+"/tag?"+tv.Encode(), nil, nil)); err != nil {
|
|
| 259 | 258 |
return err |
| 260 | 259 |
} |
| 261 | 260 |
|
| ... | ... |
@@ -317,7 +317,7 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr |
| 317 | 317 |
refs = append(refs, r) |
| 318 | 318 |
} |
| 319 | 319 |
|
| 320 |
- v.Set("fromImage", repoInfo.LocalName)
|
|
| 320 |
+ v.Set("fromImage", repoInfo.LocalName.Name())
|
|
| 321 | 321 |
for i, r := range refs {
|
| 322 | 322 |
displayTag := r.reference.String() |
| 323 | 323 |
if displayTag != "" {
|
| ... | ... |
@@ -333,7 +333,12 @@ func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registr |
| 333 | 333 |
|
| 334 | 334 |
// If reference is not trusted, tag by trusted reference |
| 335 | 335 |
if !r.reference.HasDigest() {
|
| 336 |
- if err := cli.tagTrusted(repoInfo, registry.DigestReference(r.digest), r.reference); err != nil {
|
|
| 336 |
+ tagged, err := reference.WithTag(repoInfo.LocalName, r.reference.String()) |
|
| 337 |
+ if err != nil {
|
|
| 338 |
+ return err |
|
| 339 |
+ } |
|
| 340 |
+ trustedRef, err := reference.WithDigest(repoInfo.LocalName, r.digest) |
|
| 341 |
+ if err := cli.tagTrusted(trustedRef, tagged); err != nil {
|
|
| 337 | 342 |
return err |
| 338 | 343 |
|
| 339 | 344 |
} |
| ... | ... |
@@ -386,7 +391,7 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, |
| 386 | 386 |
v := url.Values{}
|
| 387 | 387 |
v.Set("tag", tag)
|
| 388 | 388 |
|
| 389 |
- _, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
|
|
| 389 |
+ _, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName.Name()+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
|
|
| 390 | 390 |
// Close stream channel to finish target parsing |
| 391 | 391 |
if err := streamOut.Close(); err != nil {
|
| 392 | 392 |
return err |
| ... | ... |
@@ -10,6 +10,8 @@ import ( |
| 10 | 10 |
"strings" |
| 11 | 11 |
|
| 12 | 12 |
"github.com/Sirupsen/logrus" |
| 13 |
+ "github.com/docker/distribution/digest" |
|
| 14 |
+ "github.com/docker/distribution/reference" |
|
| 13 | 15 |
"github.com/docker/docker/api/server/httputils" |
| 14 | 16 |
"github.com/docker/docker/api/types" |
| 15 | 17 |
"github.com/docker/docker/builder" |
| ... | ... |
@@ -17,17 +19,14 @@ import ( |
| 17 | 17 |
"github.com/docker/docker/cliconfig" |
| 18 | 18 |
"github.com/docker/docker/daemon/daemonbuilder" |
| 19 | 19 |
derr "github.com/docker/docker/errors" |
| 20 |
- "github.com/docker/docker/graph" |
|
| 21 |
- "github.com/docker/docker/graph/tags" |
|
| 22 | 20 |
"github.com/docker/docker/pkg/archive" |
| 23 | 21 |
"github.com/docker/docker/pkg/chrootarchive" |
| 24 | 22 |
"github.com/docker/docker/pkg/ioutils" |
| 25 |
- "github.com/docker/docker/pkg/parsers" |
|
| 26 | 23 |
"github.com/docker/docker/pkg/progressreader" |
| 27 | 24 |
"github.com/docker/docker/pkg/streamformatter" |
| 28 | 25 |
"github.com/docker/docker/pkg/ulimit" |
| 29 |
- "github.com/docker/docker/registry" |
|
| 30 | 26 |
"github.com/docker/docker/runconfig" |
| 27 |
+ tagpkg "github.com/docker/docker/tag" |
|
| 31 | 28 |
"github.com/docker/docker/utils" |
| 32 | 29 |
"golang.org/x/net/context" |
| 33 | 30 |
) |
| ... | ... |
@@ -110,26 +109,55 @@ func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r |
| 110 | 110 |
w.Header().Set("Content-Type", "application/json")
|
| 111 | 111 |
|
| 112 | 112 |
if image != "" { //pull
|
| 113 |
- if tag == "" {
|
|
| 114 |
- image, tag = parsers.ParseRepositoryTag(image) |
|
| 115 |
- } |
|
| 116 |
- metaHeaders := map[string][]string{}
|
|
| 117 |
- for k, v := range r.Header {
|
|
| 118 |
- if strings.HasPrefix(k, "X-Meta-") {
|
|
| 119 |
- metaHeaders[k] = v |
|
| 113 |
+ // Special case: "pull -a" may send an image name with a |
|
| 114 |
+ // trailing :. This is ugly, but let's not break API |
|
| 115 |
+ // compatibility. |
|
| 116 |
+ image = strings.TrimSuffix(image, ":") |
|
| 117 |
+ |
|
| 118 |
+ var ref reference.Named |
|
| 119 |
+ ref, err = reference.ParseNamed(image) |
|
| 120 |
+ if err == nil {
|
|
| 121 |
+ if tag != "" {
|
|
| 122 |
+ // The "tag" could actually be a digest. |
|
| 123 |
+ var dgst digest.Digest |
|
| 124 |
+ dgst, err = digest.ParseDigest(tag) |
|
| 125 |
+ if err == nil {
|
|
| 126 |
+ ref, err = reference.WithDigest(ref, dgst) |
|
| 127 |
+ } else {
|
|
| 128 |
+ ref, err = reference.WithTag(ref, tag) |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+ if err == nil {
|
|
| 132 |
+ metaHeaders := map[string][]string{}
|
|
| 133 |
+ for k, v := range r.Header {
|
|
| 134 |
+ if strings.HasPrefix(k, "X-Meta-") {
|
|
| 135 |
+ metaHeaders[k] = v |
|
| 136 |
+ } |
|
| 137 |
+ } |
|
| 138 |
+ |
|
| 139 |
+ err = s.daemon.PullImage(ref, metaHeaders, authConfig, output) |
|
| 120 | 140 |
} |
| 121 | 141 |
} |
| 142 |
+ } else { //import
|
|
| 143 |
+ var newRef reference.Named |
|
| 144 |
+ if repo != "" {
|
|
| 145 |
+ var err error |
|
| 146 |
+ newRef, err = reference.ParseNamed(repo) |
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ return err |
|
| 149 |
+ } |
|
| 122 | 150 |
|
| 123 |
- imagePullConfig := &graph.ImagePullConfig{
|
|
| 124 |
- MetaHeaders: metaHeaders, |
|
| 125 |
- AuthConfig: authConfig, |
|
| 126 |
- OutStream: output, |
|
| 127 |
- } |
|
| 151 |
+ switch newRef.(type) {
|
|
| 152 |
+ case reference.Digested: |
|
| 153 |
+ return errors.New("cannot import digest reference")
|
|
| 154 |
+ } |
|
| 128 | 155 |
|
| 129 |
- err = s.daemon.PullImage(image, tag, imagePullConfig) |
|
| 130 |
- } else { //import
|
|
| 131 |
- if tag == "" {
|
|
| 132 |
- repo, tag = parsers.ParseRepositoryTag(repo) |
|
| 156 |
+ if tag != "" {
|
|
| 157 |
+ newRef, err = reference.WithTag(newRef, tag) |
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ return err |
|
| 160 |
+ } |
|
| 161 |
+ } |
|
| 133 | 162 |
} |
| 134 | 163 |
|
| 135 | 164 |
src := r.Form.Get("fromSrc")
|
| ... | ... |
@@ -143,7 +171,7 @@ func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r |
| 143 | 143 |
return err |
| 144 | 144 |
} |
| 145 | 145 |
|
| 146 |
- err = s.daemon.ImportImage(src, repo, tag, message, r.Body, output, newConfig) |
|
| 146 |
+ err = s.daemon.ImportImage(src, newRef, message, r.Body, output, newConfig) |
|
| 147 | 147 |
} |
| 148 | 148 |
if err != nil {
|
| 149 | 149 |
if !output.Flushed() {
|
| ... | ... |
@@ -183,19 +211,25 @@ func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *h |
| 183 | 183 |
} |
| 184 | 184 |
} |
| 185 | 185 |
|
| 186 |
- name := vars["name"] |
|
| 186 |
+ ref, err := reference.ParseNamed(vars["name"]) |
|
| 187 |
+ if err != nil {
|
|
| 188 |
+ return err |
|
| 189 |
+ } |
|
| 190 |
+ tag := r.Form.Get("tag")
|
|
| 191 |
+ if tag != "" {
|
|
| 192 |
+ // Push by digest is not supported, so only tags are supported. |
|
| 193 |
+ ref, err = reference.WithTag(ref, tag) |
|
| 194 |
+ if err != nil {
|
|
| 195 |
+ return err |
|
| 196 |
+ } |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 187 | 199 |
output := ioutils.NewWriteFlusher(w) |
| 188 | 200 |
defer output.Close() |
| 189 |
- imagePushConfig := &graph.ImagePushConfig{
|
|
| 190 |
- MetaHeaders: metaHeaders, |
|
| 191 |
- AuthConfig: authConfig, |
|
| 192 |
- Tag: r.Form.Get("tag"),
|
|
| 193 |
- OutStream: output, |
|
| 194 |
- } |
|
| 195 | 201 |
|
| 196 | 202 |
w.Header().Set("Content-Type", "application/json")
|
| 197 | 203 |
|
| 198 |
- if err := s.daemon.PushImage(name, imagePushConfig); err != nil {
|
|
| 204 |
+ if err := s.daemon.PushImage(ref, metaHeaders, authConfig, output); err != nil {
|
|
| 199 | 205 |
if !output.Flushed() {
|
| 200 | 206 |
return err |
| 201 | 207 |
} |
| ... | ... |
@@ -428,7 +462,7 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R |
| 428 | 428 |
} |
| 429 | 429 |
|
| 430 | 430 |
for _, rt := range repoAndTags {
|
| 431 |
- if err := s.daemon.TagImage(rt.repo, rt.tag, string(imgID), true); err != nil {
|
|
| 431 |
+ if err := s.daemon.TagImage(rt, imgID, true); err != nil {
|
|
| 432 | 432 |
return errf(err) |
| 433 | 433 |
} |
| 434 | 434 |
} |
| ... | ... |
@@ -436,43 +470,38 @@ func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.R |
| 436 | 436 |
return nil |
| 437 | 437 |
} |
| 438 | 438 |
|
| 439 |
-// repoAndTag is a helper struct for holding the parsed repositories and tags of |
|
| 440 |
-// the input "t" argument. |
|
| 441 |
-type repoAndTag struct {
|
|
| 442 |
- repo, tag string |
|
| 443 |
-} |
|
| 444 |
- |
|
| 445 | 439 |
// sanitizeRepoAndTags parses the raw "t" parameter received from the client |
| 446 | 440 |
// to a slice of repoAndTag. |
| 447 | 441 |
// It also validates each repoName and tag. |
| 448 |
-func sanitizeRepoAndTags(names []string) ([]repoAndTag, error) {
|
|
| 442 |
+func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
|
|
| 449 | 443 |
var ( |
| 450 |
- repoAndTags []repoAndTag |
|
| 444 |
+ repoAndTags []reference.Named |
|
| 451 | 445 |
// This map is used for deduplicating the "-t" paramter. |
| 452 | 446 |
uniqNames = make(map[string]struct{})
|
| 453 | 447 |
) |
| 454 | 448 |
for _, repo := range names {
|
| 455 |
- name, tag := parsers.ParseRepositoryTag(repo) |
|
| 456 |
- if name == "" {
|
|
| 449 |
+ if repo == "" {
|
|
| 457 | 450 |
continue |
| 458 | 451 |
} |
| 459 | 452 |
|
| 460 |
- if err := registry.ValidateRepositoryName(name); err != nil {
|
|
| 453 |
+ ref, err := reference.ParseNamed(repo) |
|
| 454 |
+ if err != nil {
|
|
| 461 | 455 |
return nil, err |
| 462 | 456 |
} |
| 463 | 457 |
|
| 464 |
- nameWithTag := name |
|
| 465 |
- if len(tag) > 0 {
|
|
| 466 |
- if err := tags.ValidateTagName(tag); err != nil {
|
|
| 467 |
- return nil, err |
|
| 468 |
- } |
|
| 469 |
- nameWithTag += ":" + tag |
|
| 470 |
- } else {
|
|
| 471 |
- nameWithTag += ":" + tags.DefaultTag |
|
| 458 |
+ if _, isDigested := ref.(reference.Digested); isDigested {
|
|
| 459 |
+ return nil, errors.New("build tag cannot be a digest")
|
|
| 460 |
+ } |
|
| 461 |
+ |
|
| 462 |
+ if _, isTagged := ref.(reference.Tagged); !isTagged {
|
|
| 463 |
+ ref, err = reference.WithTag(ref, tagpkg.DefaultTag) |
|
| 472 | 464 |
} |
| 465 |
+ |
|
| 466 |
+ nameWithTag := ref.String() |
|
| 467 |
+ |
|
| 473 | 468 |
if _, exists := uniqNames[nameWithTag]; !exists {
|
| 474 | 469 |
uniqNames[nameWithTag] = struct{}{}
|
| 475 |
- repoAndTags = append(repoAndTags, repoAndTag{repo: name, tag: tag})
|
|
| 470 |
+ repoAndTags = append(repoAndTags, ref) |
|
| 476 | 471 |
} |
| 477 | 472 |
} |
| 478 | 473 |
return repoAndTags, nil |
| ... | ... |
@@ -484,7 +513,7 @@ func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *ht |
| 484 | 484 |
} |
| 485 | 485 |
|
| 486 | 486 |
// FIXME: The filter parameter could just be a match filter |
| 487 |
- images, err := s.daemon.ListImages(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
|
|
| 487 |
+ images, err := s.daemon.Images(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
|
|
| 488 | 488 |
if err != nil {
|
| 489 | 489 |
return err |
| 490 | 490 |
} |
| ... | ... |
@@ -508,9 +537,17 @@ func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *ht |
| 508 | 508 |
} |
| 509 | 509 |
repo := r.Form.Get("repo")
|
| 510 | 510 |
tag := r.Form.Get("tag")
|
| 511 |
- name := vars["name"] |
|
| 511 |
+ newTag, err := reference.WithName(repo) |
|
| 512 |
+ if err != nil {
|
|
| 513 |
+ return err |
|
| 514 |
+ } |
|
| 515 |
+ if tag != "" {
|
|
| 516 |
+ if newTag, err = reference.WithTag(newTag, tag); err != nil {
|
|
| 517 |
+ return err |
|
| 518 |
+ } |
|
| 519 |
+ } |
|
| 512 | 520 |
force := httputils.BoolValue(r, "force") |
| 513 |
- if err := s.daemon.TagImage(repo, tag, name, force); err != nil {
|
|
| 521 |
+ if err := s.daemon.TagImage(newTag, vars["name"], force); err != nil {
|
|
| 514 | 522 |
return err |
| 515 | 523 |
} |
| 516 | 524 |
w.WriteHeader(http.StatusCreated) |
| ... | ... |
@@ -125,7 +125,7 @@ type Docker interface {
|
| 125 | 125 |
// Remove removes a container specified by `id`. |
| 126 | 126 |
Remove(id string, cfg *daemon.ContainerRmConfig) error |
| 127 | 127 |
// Commit creates a new Docker image from an existing Docker container. |
| 128 |
- Commit(string, *daemon.ContainerCommitConfig) (*image.Image, error) |
|
| 128 |
+ Commit(string, *daemon.ContainerCommitConfig) (string, error) |
|
| 129 | 129 |
// Copy copies/extracts a source FileInfo to a destination path inside a container |
| 130 | 130 |
// specified by a container object. |
| 131 | 131 |
// TODO: make an Extract method instead of passing `decompress` |
| ... | ... |
@@ -277,9 +277,9 @@ func Commit(containerName string, d *daemon.Daemon, c *CommitConfig) (string, er |
| 277 | 277 |
MergeConfigs: true, |
| 278 | 278 |
} |
| 279 | 279 |
|
| 280 |
- img, err := d.Commit(containerName, commitCfg) |
|
| 280 |
+ imgID, err := d.Commit(containerName, commitCfg) |
|
| 281 | 281 |
if err != nil {
|
| 282 | 282 |
return "", err |
| 283 | 283 |
} |
| 284 |
- return img.ID, nil |
|
| 284 |
+ return imgID, nil |
|
| 285 | 285 |
} |
| ... | ... |
@@ -83,13 +83,13 @@ func (b *Builder) commit(id string, autoCmd *stringutils.StrSlice, comment strin |
| 83 | 83 |
} |
| 84 | 84 |
|
| 85 | 85 |
// Commit the container |
| 86 |
- image, err := b.docker.Commit(id, commitCfg) |
|
| 86 |
+ imageID, err := b.docker.Commit(id, commitCfg) |
|
| 87 | 87 |
if err != nil {
|
| 88 | 88 |
return err |
| 89 | 89 |
} |
| 90 |
- b.docker.Retain(b.id, image.ID) |
|
| 91 |
- b.activeImages = append(b.activeImages, image.ID) |
|
| 92 |
- b.image = image.ID |
|
| 90 |
+ b.docker.Retain(b.id, imageID) |
|
| 91 |
+ b.activeImages = append(b.activeImages, imageID) |
|
| 92 |
+ b.image = imageID |
|
| 93 | 93 |
return nil |
| 94 | 94 |
} |
| 95 | 95 |
|
| ... | ... |
@@ -412,7 +412,7 @@ func containsWildcards(name string) bool {
|
| 412 | 412 |
} |
| 413 | 413 |
|
| 414 | 414 |
func (b *Builder) processImageFrom(img *image.Image) error {
|
| 415 |
- b.image = img.ID |
|
| 415 |
+ b.image = img.ID().String() |
|
| 416 | 416 |
|
| 417 | 417 |
if img.Config != nil {
|
| 418 | 418 |
b.runConfig = img.Config |
| ... | ... |
@@ -1,10 +1,16 @@ |
| 1 | 1 |
package daemon |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"runtime" |
| 7 |
+ "strings" |
|
| 8 |
+ "time" |
|
| 6 | 9 |
|
| 10 |
+ "github.com/docker/distribution/reference" |
|
| 11 |
+ "github.com/docker/docker/dockerversion" |
|
| 7 | 12 |
"github.com/docker/docker/image" |
| 13 |
+ "github.com/docker/docker/layer" |
|
| 8 | 14 |
"github.com/docker/docker/pkg/archive" |
| 9 | 15 |
"github.com/docker/docker/pkg/ioutils" |
| 10 | 16 |
"github.com/docker/docker/runconfig" |
| ... | ... |
@@ -25,15 +31,15 @@ type ContainerCommitConfig struct {
|
| 25 | 25 |
|
| 26 | 26 |
// Commit creates a new filesystem image from the current state of a container. |
| 27 | 27 |
// The image can optionally be tagged into a repository. |
| 28 |
-func (daemon *Daemon) Commit(name string, c *ContainerCommitConfig) (*image.Image, error) {
|
|
| 28 |
+func (daemon *Daemon) Commit(name string, c *ContainerCommitConfig) (string, error) {
|
|
| 29 | 29 |
container, err := daemon.Get(name) |
| 30 | 30 |
if err != nil {
|
| 31 |
- return nil, err |
|
| 31 |
+ return "", err |
|
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 | 34 |
// It is not possible to commit a running container on Windows |
| 35 | 35 |
if runtime.GOOS == "windows" && container.IsRunning() {
|
| 36 |
- return nil, fmt.Errorf("Windows does not support commit of a running container")
|
|
| 36 |
+ return "", fmt.Errorf("Windows does not support commit of a running container")
|
|
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 | 39 |
if c.Pause && !container.isPaused() {
|
| ... | ... |
@@ -43,13 +49,13 @@ func (daemon *Daemon) Commit(name string, c *ContainerCommitConfig) (*image.Imag |
| 43 | 43 |
|
| 44 | 44 |
if c.MergeConfigs {
|
| 45 | 45 |
if err := runconfig.Merge(c.Config, container.Config); err != nil {
|
| 46 |
- return nil, err |
|
| 46 |
+ return "", err |
|
| 47 | 47 |
} |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 | 50 |
rwTar, err := daemon.exportContainerRw(container) |
| 51 | 51 |
if err != nil {
|
| 52 |
- return nil, err |
|
| 52 |
+ return "", err |
|
| 53 | 53 |
} |
| 54 | 54 |
defer func() {
|
| 55 | 55 |
if rwTar != nil {
|
| ... | ... |
@@ -57,31 +63,99 @@ func (daemon *Daemon) Commit(name string, c *ContainerCommitConfig) (*image.Imag |
| 57 | 57 |
} |
| 58 | 58 |
}() |
| 59 | 59 |
|
| 60 |
- // Create a new image from the container's base layers + a new layer from container changes |
|
| 61 |
- img, err := daemon.graph.Create(rwTar, container.ID, container.ImageID, c.Comment, c.Author, container.Config, c.Config) |
|
| 60 |
+ var history []image.History |
|
| 61 |
+ rootFS := image.NewRootFS() |
|
| 62 |
+ |
|
| 63 |
+ if container.ImageID != "" {
|
|
| 64 |
+ img, err := daemon.imageStore.Get(container.ImageID) |
|
| 65 |
+ if err != nil {
|
|
| 66 |
+ return "", err |
|
| 67 |
+ } |
|
| 68 |
+ history = img.History |
|
| 69 |
+ rootFS = img.RootFS |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID()) |
|
| 62 | 73 |
if err != nil {
|
| 63 |
- return nil, err |
|
| 74 |
+ return "", err |
|
| 75 |
+ } |
|
| 76 |
+ defer layer.ReleaseAndLog(daemon.layerStore, l) |
|
| 77 |
+ |
|
| 78 |
+ h := image.History{
|
|
| 79 |
+ Author: c.Author, |
|
| 80 |
+ Created: time.Now().UTC(), |
|
| 81 |
+ CreatedBy: strings.Join(container.Config.Cmd.Slice(), " "), |
|
| 82 |
+ Comment: c.Comment, |
|
| 83 |
+ EmptyLayer: true, |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 86 |
+ if diffID := l.DiffID(); layer.DigestSHA256EmptyTar != diffID {
|
|
| 87 |
+ h.EmptyLayer = false |
|
| 88 |
+ rootFS.Append(diffID) |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ history = append(history, h) |
|
| 92 |
+ |
|
| 93 |
+ config, err := json.Marshal(&image.Image{
|
|
| 94 |
+ V1Image: image.V1Image{
|
|
| 95 |
+ DockerVersion: dockerversion.Version, |
|
| 96 |
+ Config: c.Config, |
|
| 97 |
+ Architecture: runtime.GOARCH, |
|
| 98 |
+ OS: runtime.GOOS, |
|
| 99 |
+ Container: container.ID, |
|
| 100 |
+ ContainerConfig: *container.Config, |
|
| 101 |
+ Author: c.Author, |
|
| 102 |
+ Created: h.Created, |
|
| 103 |
+ }, |
|
| 104 |
+ RootFS: rootFS, |
|
| 105 |
+ History: history, |
|
| 106 |
+ }) |
|
| 107 |
+ |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ return "", err |
|
| 110 |
+ } |
|
| 111 |
+ |
|
| 112 |
+ id, err := daemon.imageStore.Create(config) |
|
| 113 |
+ if err != nil {
|
|
| 114 |
+ return "", err |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ if container.ImageID != "" {
|
|
| 118 |
+ if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil {
|
|
| 119 |
+ return "", err |
|
| 120 |
+ } |
|
| 64 | 121 |
} |
| 65 | 122 |
|
| 66 |
- // Register the image if needed |
|
| 67 | 123 |
if c.Repo != "" {
|
| 68 |
- if err := daemon.repositories.Tag(c.Repo, c.Tag, img.ID, true); err != nil {
|
|
| 69 |
- return img, err |
|
| 124 |
+ newTag, err := reference.WithName(c.Repo) // todo: should move this to API layer |
|
| 125 |
+ if err != nil {
|
|
| 126 |
+ return "", err |
|
| 127 |
+ } |
|
| 128 |
+ if c.Tag != "" {
|
|
| 129 |
+ if newTag, err = reference.WithTag(newTag, c.Tag); err != nil {
|
|
| 130 |
+ return "", err |
|
| 131 |
+ } |
|
| 132 |
+ } |
|
| 133 |
+ if err := daemon.TagImage(newTag, id.String(), true); err != nil {
|
|
| 134 |
+ return "", err |
|
| 70 | 135 |
} |
| 71 | 136 |
} |
| 72 | 137 |
|
| 73 | 138 |
daemon.LogContainerEvent(container, "commit") |
| 74 |
- return img, nil |
|
| 139 |
+ return id.String(), nil |
|
| 75 | 140 |
} |
| 76 | 141 |
|
| 77 | 142 |
func (daemon *Daemon) exportContainerRw(container *Container) (archive.Archive, error) {
|
| 78 |
- archive, err := daemon.diff(container) |
|
| 143 |
+ if err := daemon.Mount(container); err != nil {
|
|
| 144 |
+ return nil, err |
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ archive, err := container.rwlayer.TarStream() |
|
| 79 | 148 |
if err != nil {
|
| 80 | 149 |
return nil, err |
| 81 | 150 |
} |
| 82 | 151 |
return ioutils.NewReadCloserWrapper(archive, func() error {
|
| 83 |
- err := archive.Close() |
|
| 84 |
- return err |
|
| 152 |
+ return daemon.layerStore.Unmount(container.ID) |
|
| 85 | 153 |
}), |
| 86 | 154 |
nil |
| 87 | 155 |
} |
| ... | ... |
@@ -20,6 +20,8 @@ import ( |
| 20 | 20 |
"github.com/docker/docker/daemon/logger/jsonfilelog" |
| 21 | 21 |
"github.com/docker/docker/daemon/network" |
| 22 | 22 |
derr "github.com/docker/docker/errors" |
| 23 |
+ "github.com/docker/docker/image" |
|
| 24 |
+ "github.com/docker/docker/layer" |
|
| 23 | 25 |
"github.com/docker/docker/pkg/nat" |
| 24 | 26 |
"github.com/docker/docker/pkg/promise" |
| 25 | 27 |
"github.com/docker/docker/pkg/signal" |
| ... | ... |
@@ -29,6 +31,8 @@ import ( |
| 29 | 29 |
"github.com/docker/docker/volume" |
| 30 | 30 |
) |
| 31 | 31 |
|
| 32 |
+const configFileName = "config.v2.json" |
|
| 33 |
+ |
|
| 32 | 34 |
var ( |
| 33 | 35 |
// ErrRootFSReadOnly is returned when a container |
| 34 | 36 |
// rootfs is marked readonly. |
| ... | ... |
@@ -43,12 +47,13 @@ type CommonContainer struct {
|
| 43 | 43 |
*State `json:"State"` // Needed for remote api version <= 1.11 |
| 44 | 44 |
root string // Path to the "home" of the container, including metadata. |
| 45 | 45 |
basefs string // Path to the graphdriver mountpoint |
| 46 |
+ rwlayer layer.RWLayer |
|
| 46 | 47 |
ID string |
| 47 | 48 |
Created time.Time |
| 48 | 49 |
Path string |
| 49 | 50 |
Args []string |
| 50 | 51 |
Config *runconfig.Config |
| 51 |
- ImageID string `json:"Image"` |
|
| 52 |
+ ImageID image.ID `json:"Image"` |
|
| 52 | 53 |
NetworkSettings *network.Settings |
| 53 | 54 |
LogPath string |
| 54 | 55 |
Name string |
| ... | ... |
@@ -256,7 +261,7 @@ func (container *Container) hostConfigPath() (string, error) {
|
| 256 | 256 |
} |
| 257 | 257 |
|
| 258 | 258 |
func (container *Container) jsonPath() (string, error) {
|
| 259 |
- return container.getRootResourcePath("config.json")
|
|
| 259 |
+ return container.getRootResourcePath(configFileName) |
|
| 260 | 260 |
} |
| 261 | 261 |
|
| 262 | 262 |
// This directory is only usable when the container is running |
| ... | ... |
@@ -301,7 +306,7 @@ func (container *Container) StartLogger(cfg runconfig.LogConfig) (logger.Logger, |
| 301 | 301 |
ContainerName: container.Name, |
| 302 | 302 |
ContainerEntrypoint: container.Path, |
| 303 | 303 |
ContainerArgs: container.Args, |
| 304 |
- ContainerImageID: container.ImageID, |
|
| 304 |
+ ContainerImageID: container.ImageID.String(), |
|
| 305 | 305 |
ContainerImageName: container.Config.Image, |
| 306 | 306 |
ContainerCreated: container.Created, |
| 307 | 307 |
ContainerEnv: container.Config.Env, |
| ... | ... |
@@ -99,7 +99,7 @@ func TestContainerInitDNS(t *testing.T) {
|
| 99 | 99 |
"Name":"/ubuntu","Driver":"aufs","MountLabel":"","ProcessLabel":"","AppArmorProfile":"","RestartCount":0, |
| 100 | 100 |
"UpdateDns":false,"Volumes":{},"VolumesRW":{},"AppliedVolumesFrom":null}`
|
| 101 | 101 |
|
| 102 |
- if err = ioutil.WriteFile(filepath.Join(containerPath, "config.json"), []byte(config), 0644); err != nil {
|
|
| 102 |
+ if err = ioutil.WriteFile(filepath.Join(containerPath, configFileName), []byte(config), 0644); err != nil {
|
|
| 103 | 103 |
t.Fatal(err) |
| 104 | 104 |
} |
| 105 | 105 |
|
| ... | ... |
@@ -19,7 +19,6 @@ import ( |
| 19 | 19 |
"github.com/docker/docker/daemon/links" |
| 20 | 20 |
"github.com/docker/docker/daemon/network" |
| 21 | 21 |
derr "github.com/docker/docker/errors" |
| 22 |
- "github.com/docker/docker/pkg/directory" |
|
| 23 | 22 |
"github.com/docker/docker/pkg/fileutils" |
| 24 | 23 |
"github.com/docker/docker/pkg/idtools" |
| 25 | 24 |
"github.com/docker/docker/pkg/mount" |
| ... | ... |
@@ -388,8 +387,7 @@ func (daemon *Daemon) getSize(container *Container) (int64, int64) {
|
| 388 | 388 |
} |
| 389 | 389 |
defer daemon.Unmount(container) |
| 390 | 390 |
|
| 391 |
- initID := fmt.Sprintf("%s-init", container.ID)
|
|
| 392 |
- sizeRw, err = daemon.driver.DiffSize(container.ID, initID) |
|
| 391 |
+ sizeRw, err = container.rwlayer.Size() |
|
| 393 | 392 |
if err != nil {
|
| 394 | 393 |
logrus.Errorf("Driver %s couldn't return diff size of container %s: %s", daemon.driver, container.ID, err)
|
| 395 | 394 |
// FIXME: GetSize should return an error. Not changing it now in case |
| ... | ... |
@@ -397,9 +395,12 @@ func (daemon *Daemon) getSize(container *Container) (int64, int64) {
|
| 397 | 397 |
sizeRw = -1 |
| 398 | 398 |
} |
| 399 | 399 |
|
| 400 |
- if _, err = os.Stat(container.basefs); err == nil {
|
|
| 401 |
- if sizeRootfs, err = directory.Size(container.basefs); err != nil {
|
|
| 400 |
+ if parent := container.rwlayer.Parent(); parent != nil {
|
|
| 401 |
+ sizeRootfs, err = parent.Size() |
|
| 402 |
+ if err != nil {
|
|
| 402 | 403 |
sizeRootfs = -1 |
| 404 |
+ } else if sizeRw != -1 {
|
|
| 405 |
+ sizeRootfs += sizeRw |
|
| 403 | 406 |
} |
| 404 | 407 |
} |
| 405 | 408 |
return sizeRw, sizeRootfs |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/daemon/execdriver" |
| 9 | 9 |
derr "github.com/docker/docker/errors" |
| 10 |
+ "github.com/docker/docker/layer" |
|
| 10 | 11 |
"github.com/docker/docker/volume" |
| 11 | 12 |
"github.com/docker/libnetwork" |
| 12 | 13 |
) |
| ... | ... |
@@ -98,22 +99,25 @@ func (daemon *Daemon) populateCommand(c *Container, env []string) error {
|
| 98 | 98 |
processConfig.Env = env |
| 99 | 99 |
|
| 100 | 100 |
var layerPaths []string |
| 101 |
- img, err := daemon.graph.Get(c.ImageID) |
|
| 101 |
+ img, err := daemon.imageStore.Get(c.ImageID) |
|
| 102 | 102 |
if err != nil {
|
| 103 | 103 |
return derr.ErrorCodeGetGraph.WithArgs(c.ImageID, err) |
| 104 | 104 |
} |
| 105 |
- for i := img; i != nil && err == nil; i, err = daemon.graph.GetParent(i) {
|
|
| 106 |
- lp, err := daemon.driver.Get(i.ID, "") |
|
| 107 |
- if err != nil {
|
|
| 108 |
- return derr.ErrorCodeGetLayer.WithArgs(daemon.driver.String(), i.ID, err) |
|
| 109 |
- } |
|
| 110 |
- layerPaths = append(layerPaths, lp) |
|
| 111 |
- err = daemon.driver.Put(i.ID) |
|
| 112 |
- if err != nil {
|
|
| 113 |
- return derr.ErrorCodePutLayer.WithArgs(daemon.driver.String(), i.ID, err) |
|
| 105 |
+ |
|
| 106 |
+ if img.RootFS != nil && img.RootFS.Type == "layers+base" {
|
|
| 107 |
+ max := len(img.RootFS.DiffIDs) |
|
| 108 |
+ for i := 0; i <= max; i++ {
|
|
| 109 |
+ img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i] |
|
| 110 |
+ path, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID()) |
|
| 111 |
+ if err != nil {
|
|
| 112 |
+ return derr.ErrorCodeGetLayer.WithArgs(err) |
|
| 113 |
+ } |
|
| 114 |
+ // Reverse order, expecting parent most first |
|
| 115 |
+ layerPaths = append([]string{path}, layerPaths...)
|
|
| 114 | 116 |
} |
| 115 | 117 |
} |
| 116 |
- m, err := daemon.driver.GetMetadata(c.ID) |
|
| 118 |
+ |
|
| 119 |
+ m, err := layer.RWLayerMetadata(daemon.layerStore, c.ID) |
|
| 117 | 120 |
if err != nil {
|
| 118 | 121 |
return derr.ErrorCodeGetLayerMetadata.WithArgs(err) |
| 119 | 122 |
} |
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"github.com/docker/docker/api/types" |
| 6 | 6 |
derr "github.com/docker/docker/errors" |
| 7 | 7 |
"github.com/docker/docker/image" |
| 8 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 8 | 9 |
"github.com/docker/docker/pkg/stringid" |
| 9 | 10 |
"github.com/docker/docker/runconfig" |
| 10 | 11 |
"github.com/docker/docker/volume" |
| ... | ... |
@@ -34,7 +35,7 @@ func (daemon *Daemon) ContainerCreate(params *ContainerCreateConfig) (types.Cont |
| 34 | 34 |
|
| 35 | 35 |
container, err := daemon.create(params) |
| 36 | 36 |
if err != nil {
|
| 37 |
- return types.ContainerCreateResponse{ID: "", Warnings: warnings}, daemon.graphNotExistToErrcode(params.Config.Image, err)
|
|
| 37 |
+ return types.ContainerCreateResponse{ID: "", Warnings: warnings}, daemon.imageNotExistToErrcode(err)
|
|
| 38 | 38 |
} |
| 39 | 39 |
|
| 40 | 40 |
return types.ContainerCreateResponse{ID: container.ID, Warnings: warnings}, nil
|
| ... | ... |
@@ -45,19 +46,16 @@ func (daemon *Daemon) create(params *ContainerCreateConfig) (retC *Container, re |
| 45 | 45 |
var ( |
| 46 | 46 |
container *Container |
| 47 | 47 |
img *image.Image |
| 48 |
- imgID string |
|
| 48 |
+ imgID image.ID |
|
| 49 | 49 |
err error |
| 50 | 50 |
) |
| 51 | 51 |
|
| 52 | 52 |
if params.Config.Image != "" {
|
| 53 |
- img, err = daemon.repositories.LookupImage(params.Config.Image) |
|
| 53 |
+ img, err = daemon.GetImage(params.Config.Image) |
|
| 54 | 54 |
if err != nil {
|
| 55 | 55 |
return nil, err |
| 56 | 56 |
} |
| 57 |
- if err = daemon.graph.CheckDepth(img); err != nil {
|
|
| 58 |
- return nil, err |
|
| 59 |
- } |
|
| 60 |
- imgID = img.ID |
|
| 57 |
+ imgID = img.ID() |
|
| 61 | 58 |
} |
| 62 | 59 |
|
| 63 | 60 |
if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
|
| ... | ... |
@@ -87,15 +85,14 @@ func (daemon *Daemon) create(params *ContainerCreateConfig) (retC *Container, re |
| 87 | 87 |
if err := daemon.Register(container); err != nil {
|
| 88 | 88 |
return nil, err |
| 89 | 89 |
} |
| 90 |
- container.Lock() |
|
| 91 |
- if err := parseSecurityOpt(container, params.HostConfig); err != nil {
|
|
| 92 |
- container.Unlock() |
|
| 90 |
+ rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) |
|
| 91 |
+ if err != nil {
|
|
| 93 | 92 |
return nil, err |
| 94 | 93 |
} |
| 95 |
- container.Unlock() |
|
| 96 |
- if err := daemon.createRootfs(container); err != nil {
|
|
| 94 |
+ if err := idtools.MkdirAs(container.root, 0700, rootUID, rootGID); err != nil {
|
|
| 97 | 95 |
return nil, err |
| 98 | 96 |
} |
| 97 |
+ |
|
| 99 | 98 |
if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
|
| 100 | 99 |
return nil, err |
| 101 | 100 |
} |
| ... | ... |
@@ -18,6 +18,8 @@ import ( |
| 18 | 18 |
"time" |
| 19 | 19 |
|
| 20 | 20 |
"github.com/Sirupsen/logrus" |
| 21 |
+ "github.com/docker/distribution/digest" |
|
| 22 |
+ "github.com/docker/distribution/reference" |
|
| 21 | 23 |
"github.com/docker/docker/api" |
| 22 | 24 |
"github.com/docker/docker/api/types" |
| 23 | 25 |
"github.com/docker/docker/cliconfig" |
| ... | ... |
@@ -29,9 +31,13 @@ import ( |
| 29 | 29 |
_ "github.com/docker/docker/daemon/graphdriver/vfs" // register vfs |
| 30 | 30 |
"github.com/docker/docker/daemon/logger" |
| 31 | 31 |
"github.com/docker/docker/daemon/network" |
| 32 |
+ "github.com/docker/docker/distribution" |
|
| 33 |
+ dmetadata "github.com/docker/docker/distribution/metadata" |
|
| 32 | 34 |
derr "github.com/docker/docker/errors" |
| 33 |
- "github.com/docker/docker/graph" |
|
| 34 | 35 |
"github.com/docker/docker/image" |
| 36 |
+ "github.com/docker/docker/image/tarexport" |
|
| 37 |
+ "github.com/docker/docker/layer" |
|
| 38 |
+ "github.com/docker/docker/migrate/v1" |
|
| 35 | 39 |
"github.com/docker/docker/pkg/archive" |
| 36 | 40 |
"github.com/docker/docker/pkg/discovery" |
| 37 | 41 |
"github.com/docker/docker/pkg/fileutils" |
| ... | ... |
@@ -50,12 +56,14 @@ import ( |
| 50 | 50 |
"github.com/docker/docker/pkg/truncindex" |
| 51 | 51 |
"github.com/docker/docker/registry" |
| 52 | 52 |
"github.com/docker/docker/runconfig" |
| 53 |
+ "github.com/docker/docker/tag" |
|
| 53 | 54 |
"github.com/docker/docker/utils" |
| 54 | 55 |
volumedrivers "github.com/docker/docker/volume/drivers" |
| 55 | 56 |
"github.com/docker/docker/volume/local" |
| 56 | 57 |
"github.com/docker/docker/volume/store" |
| 57 | 58 |
"github.com/docker/libnetwork" |
| 58 | 59 |
lntypes "github.com/docker/libnetwork/types" |
| 60 |
+ "github.com/docker/libtrust" |
|
| 59 | 61 |
"github.com/opencontainers/runc/libcontainer" |
| 60 | 62 |
) |
| 61 | 63 |
|
| ... | ... |
@@ -66,6 +74,15 @@ var ( |
| 66 | 66 |
errSystemNotSupported = errors.New("The Docker daemon is not supported on this platform.")
|
| 67 | 67 |
) |
| 68 | 68 |
|
| 69 |
+// ErrImageDoesNotExist is error returned when no image can be found for a reference. |
|
| 70 |
+type ErrImageDoesNotExist struct {
|
|
| 71 |
+ RefOrID string |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+func (e ErrImageDoesNotExist) Error() string {
|
|
| 75 |
+ return fmt.Sprintf("no such id: %s", e.RefOrID)
|
|
| 76 |
+} |
|
| 77 |
+ |
|
| 69 | 78 |
type contStore struct {
|
| 70 | 79 |
s map[string]*Container |
| 71 | 80 |
sync.Mutex |
| ... | ... |
@@ -103,29 +120,33 @@ func (c *contStore) List() []*Container {
|
| 103 | 103 |
|
| 104 | 104 |
// Daemon holds information about the Docker daemon. |
| 105 | 105 |
type Daemon struct {
|
| 106 |
- ID string |
|
| 107 |
- repository string |
|
| 108 |
- sysInitPath string |
|
| 109 |
- containers *contStore |
|
| 110 |
- execCommands *exec.Store |
|
| 111 |
- graph *graph.Graph |
|
| 112 |
- repositories *graph.TagStore |
|
| 113 |
- idIndex *truncindex.TruncIndex |
|
| 114 |
- configStore *Config |
|
| 115 |
- containerGraphDB *graphdb.Database |
|
| 116 |
- driver graphdriver.Driver |
|
| 117 |
- execDriver execdriver.Driver |
|
| 118 |
- statsCollector *statsCollector |
|
| 119 |
- defaultLogConfig runconfig.LogConfig |
|
| 120 |
- RegistryService *registry.Service |
|
| 121 |
- EventsService *events.Events |
|
| 122 |
- netController libnetwork.NetworkController |
|
| 123 |
- volumes *store.VolumeStore |
|
| 124 |
- discoveryWatcher discovery.Watcher |
|
| 125 |
- root string |
|
| 126 |
- shutdown bool |
|
| 127 |
- uidMaps []idtools.IDMap |
|
| 128 |
- gidMaps []idtools.IDMap |
|
| 106 |
+ ID string |
|
| 107 |
+ repository string |
|
| 108 |
+ sysInitPath string |
|
| 109 |
+ containers *contStore |
|
| 110 |
+ execCommands *exec.Store |
|
| 111 |
+ tagStore tag.Store |
|
| 112 |
+ distributionPool *distribution.Pool |
|
| 113 |
+ distributionMetadataStore dmetadata.Store |
|
| 114 |
+ trustKey libtrust.PrivateKey |
|
| 115 |
+ idIndex *truncindex.TruncIndex |
|
| 116 |
+ configStore *Config |
|
| 117 |
+ containerGraphDB *graphdb.Database |
|
| 118 |
+ driver graphdriver.Driver |
|
| 119 |
+ execDriver execdriver.Driver |
|
| 120 |
+ statsCollector *statsCollector |
|
| 121 |
+ defaultLogConfig runconfig.LogConfig |
|
| 122 |
+ RegistryService *registry.Service |
|
| 123 |
+ EventsService *events.Events |
|
| 124 |
+ netController libnetwork.NetworkController |
|
| 125 |
+ volumes *store.VolumeStore |
|
| 126 |
+ discoveryWatcher discovery.Watcher |
|
| 127 |
+ root string |
|
| 128 |
+ shutdown bool |
|
| 129 |
+ uidMaps []idtools.IDMap |
|
| 130 |
+ gidMaps []idtools.IDMap |
|
| 131 |
+ layerStore layer.Store |
|
| 132 |
+ imageStore image.Store |
|
| 129 | 133 |
} |
| 130 | 134 |
|
| 131 | 135 |
// Get looks for a container using the provided information, which could be |
| ... | ... |
@@ -229,9 +250,7 @@ func (daemon *Daemon) Register(container *Container) error {
|
| 229 | 229 |
|
| 230 | 230 |
container.unmountIpcMounts(mount.Unmount) |
| 231 | 231 |
|
| 232 |
- if err := daemon.Unmount(container); err != nil {
|
|
| 233 |
- logrus.Debugf("unmount error %s", err)
|
|
| 234 |
- } |
|
| 232 |
+ daemon.Unmount(container) |
|
| 235 | 233 |
if err := container.toDiskLocking(); err != nil {
|
| 236 | 234 |
logrus.Errorf("Error saving stopped state to disk: %v", err)
|
| 237 | 235 |
} |
| ... | ... |
@@ -456,7 +475,7 @@ func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *stringutils.StrSlic |
| 456 | 456 |
return cmdSlice[0], cmdSlice[1:] |
| 457 | 457 |
} |
| 458 | 458 |
|
| 459 |
-func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID string) (*Container, error) {
|
|
| 459 |
+func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID image.ID) (*Container, error) {
|
|
| 460 | 460 |
var ( |
| 461 | 461 |
id string |
| 462 | 462 |
err error |
| ... | ... |
@@ -542,7 +561,7 @@ func (daemon *Daemon) GetLabels(id string) map[string]string {
|
| 542 | 542 |
return container.Config.Labels |
| 543 | 543 |
} |
| 544 | 544 |
|
| 545 |
- img, err := daemon.repositories.LookupImage(id) |
|
| 545 |
+ img, err := daemon.GetImage(id) |
|
| 546 | 546 |
if err == nil {
|
| 547 | 547 |
return img.ContainerConfig.Labels |
| 548 | 548 |
} |
| ... | ... |
@@ -702,8 +721,25 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo |
| 702 | 702 |
return nil, err |
| 703 | 703 |
} |
| 704 | 704 |
|
| 705 |
- logrus.Debug("Creating images graph")
|
|
| 706 |
- g, err := graph.NewGraph(filepath.Join(config.Root, "graph"), d.driver, uidMaps, gidMaps) |
|
| 705 |
+ imageRoot := filepath.Join(config.Root, "image", d.driver.String()) |
|
| 706 |
+ fms, err := layer.NewFSMetadataStore(filepath.Join(imageRoot, "layerdb")) |
|
| 707 |
+ if err != nil {
|
|
| 708 |
+ return nil, err |
|
| 709 |
+ } |
|
| 710 |
+ |
|
| 711 |
+ d.layerStore, err = layer.NewStore(fms, d.driver) |
|
| 712 |
+ if err != nil {
|
|
| 713 |
+ return nil, err |
|
| 714 |
+ } |
|
| 715 |
+ |
|
| 716 |
+ distributionPool := distribution.NewPool() |
|
| 717 |
+ |
|
| 718 |
+ ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb")) |
|
| 719 |
+ if err != nil {
|
|
| 720 |
+ return nil, err |
|
| 721 |
+ } |
|
| 722 |
+ |
|
| 723 |
+ d.imageStore, err = image.NewImageStore(ifs, d.layerStore) |
|
| 707 | 724 |
if err != nil {
|
| 708 | 725 |
return nil, err |
| 709 | 726 |
} |
| ... | ... |
@@ -725,23 +761,24 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo |
| 725 | 725 |
return nil, err |
| 726 | 726 |
} |
| 727 | 727 |
|
| 728 |
+ distributionMetadataStore, err := dmetadata.NewFSMetadataStore(filepath.Join(imageRoot, "distribution")) |
|
| 729 |
+ if err != nil {
|
|
| 730 |
+ return nil, err |
|
| 731 |
+ } |
|
| 732 |
+ |
|
| 728 | 733 |
eventsService := events.New() |
| 729 |
- logrus.Debug("Creating repository list")
|
|
| 730 |
- tagCfg := &graph.TagStoreConfig{
|
|
| 731 |
- Graph: g, |
|
| 732 |
- Key: trustKey, |
|
| 733 |
- Registry: registryService, |
|
| 734 |
- Events: eventsService, |
|
| 735 |
- } |
|
| 736 |
- repositories, err := graph.NewTagStore(filepath.Join(config.Root, "repositories-"+d.driver.String()), tagCfg) |
|
| 734 |
+ |
|
| 735 |
+ tagStore, err := tag.NewTagStore(filepath.Join(imageRoot, "repositories.json")) |
|
| 737 | 736 |
if err != nil {
|
| 738 |
- return nil, fmt.Errorf("Couldn't create Tag store repositories-%s: %s", d.driver.String(), err)
|
|
| 737 |
+ return nil, fmt.Errorf("Couldn't create Tag store repositories: %s", err)
|
|
| 739 | 738 |
} |
| 740 | 739 |
|
| 741 |
- if restorer, ok := d.driver.(graphdriver.ImageRestorer); ok {
|
|
| 742 |
- if _, err := restorer.RestoreCustomImages(repositories, g); err != nil {
|
|
| 743 |
- return nil, fmt.Errorf("Couldn't restore custom images: %s", err)
|
|
| 744 |
- } |
|
| 740 |
+ if err := restoreCustomImage(d.driver, d.imageStore, d.layerStore, tagStore); err != nil {
|
|
| 741 |
+ return nil, fmt.Errorf("Couldn't restore custom images: %s", err)
|
|
| 742 |
+ } |
|
| 743 |
+ |
|
| 744 |
+ if err := v1.Migrate(config.Root, d.driver.String(), d.layerStore, d.imageStore, tagStore, distributionMetadataStore); err != nil {
|
|
| 745 |
+ return nil, err |
|
| 745 | 746 |
} |
| 746 | 747 |
|
| 747 | 748 |
// Discovery is only enabled when the daemon is launched with an address to advertise. When |
| ... | ... |
@@ -792,8 +829,10 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo |
| 792 | 792 |
d.repository = daemonRepo |
| 793 | 793 |
d.containers = &contStore{s: make(map[string]*Container)}
|
| 794 | 794 |
d.execCommands = exec.NewStore() |
| 795 |
- d.graph = g |
|
| 796 |
- d.repositories = repositories |
|
| 795 |
+ d.tagStore = tagStore |
|
| 796 |
+ d.distributionPool = distributionPool |
|
| 797 |
+ d.distributionMetadataStore = distributionMetadataStore |
|
| 798 |
+ d.trustKey = trustKey |
|
| 797 | 799 |
d.idIndex = truncindex.NewTruncIndex([]string{})
|
| 798 | 800 |
d.configStore = config |
| 799 | 801 |
d.sysInitPath = sysInitPath |
| ... | ... |
@@ -910,28 +949,44 @@ func (daemon *Daemon) Shutdown() error {
|
| 910 | 910 |
// Mount sets container.basefs |
| 911 | 911 |
// (is it not set coming in? why is it unset?) |
| 912 | 912 |
func (daemon *Daemon) Mount(container *Container) error {
|
| 913 |
- dir, err := daemon.driver.Get(container.ID, container.getMountLabel()) |
|
| 913 |
+ var layerID layer.ChainID |
|
| 914 |
+ if container.ImageID != "" {
|
|
| 915 |
+ img, err := daemon.imageStore.Get(container.ImageID) |
|
| 916 |
+ if err != nil {
|
|
| 917 |
+ return err |
|
| 918 |
+ } |
|
| 919 |
+ layerID = img.RootFS.ChainID() |
|
| 920 |
+ } |
|
| 921 |
+ rwlayer, err := daemon.layerStore.Mount(container.ID, layerID, container.getMountLabel(), daemon.setupInitLayer) |
|
| 914 | 922 |
if err != nil {
|
| 915 |
- return fmt.Errorf("Error getting container %s from driver %s: %s", container.ID, daemon.driver, err)
|
|
| 923 |
+ return err |
|
| 924 |
+ } |
|
| 925 |
+ dir, err := rwlayer.Path() |
|
| 926 |
+ if err != nil {
|
|
| 927 |
+ return err |
|
| 916 | 928 |
} |
| 929 |
+ logrus.Debugf("container mounted via layerStore: %v", dir)
|
|
| 917 | 930 |
|
| 918 | 931 |
if container.basefs != dir {
|
| 919 | 932 |
// The mount path reported by the graph driver should always be trusted on Windows, since the |
| 920 | 933 |
// volume path for a given mounted layer may change over time. This should only be an error |
| 921 | 934 |
// on non-Windows operating systems. |
| 922 | 935 |
if container.basefs != "" && runtime.GOOS != "windows" {
|
| 923 |
- daemon.driver.Put(container.ID) |
|
| 936 |
+ daemon.Unmount(container) |
|
| 924 | 937 |
return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
|
| 925 | 938 |
daemon.driver, container.ID, container.basefs, dir) |
| 926 | 939 |
} |
| 927 | 940 |
} |
| 928 |
- container.basefs = dir |
|
| 941 |
+ container.basefs = dir // TODO: combine these fields |
|
| 942 |
+ container.rwlayer = rwlayer |
|
| 929 | 943 |
return nil |
| 930 | 944 |
} |
| 931 | 945 |
|
| 932 | 946 |
// Unmount unsets the container base filesystem |
| 933 |
-func (daemon *Daemon) Unmount(container *Container) error {
|
|
| 934 |
- return daemon.driver.Put(container.ID) |
|
| 947 |
+func (daemon *Daemon) Unmount(container *Container) {
|
|
| 948 |
+ if err := daemon.layerStore.Unmount(container.ID); err != nil {
|
|
| 949 |
+ logrus.Errorf("Error unmounting container %s: %s", container.ID, err)
|
|
| 950 |
+ } |
|
| 935 | 951 |
} |
| 936 | 952 |
|
| 937 | 953 |
// Run uses the execution driver to run a given container |
| ... | ... |
@@ -962,82 +1017,46 @@ func (daemon *Daemon) unsubscribeToContainerStats(c *Container, ch chan interfac |
| 962 | 962 |
} |
| 963 | 963 |
|
| 964 | 964 |
func (daemon *Daemon) changes(container *Container) ([]archive.Change, error) {
|
| 965 |
- initID := fmt.Sprintf("%s-init", container.ID)
|
|
| 966 |
- return daemon.driver.Changes(container.ID, initID) |
|
| 967 |
-} |
|
| 968 |
- |
|
| 969 |
-func (daemon *Daemon) diff(container *Container) (archive.Archive, error) {
|
|
| 970 |
- initID := fmt.Sprintf("%s-init", container.ID)
|
|
| 971 |
- return daemon.driver.Diff(container.ID, initID) |
|
| 965 |
+ return daemon.layerStore.Changes(container.ID) |
|
| 972 | 966 |
} |
| 973 | 967 |
|
| 974 |
-func (daemon *Daemon) createRootfs(container *Container) error {
|
|
| 975 |
- // Step 1: create the container directory. |
|
| 976 |
- // This doubles as a barrier to avoid race conditions. |
|
| 977 |
- rootUID, rootGID, err := idtools.GetRootUIDGID(daemon.uidMaps, daemon.gidMaps) |
|
| 978 |
- if err != nil {
|
|
| 979 |
- return err |
|
| 968 |
+// TagImage creates a tag in the repository reponame, pointing to the image named |
|
| 969 |
+// imageName. If force is true, an existing tag with the same name may be |
|
| 970 |
+// overwritten. |
|
| 971 |
+func (daemon *Daemon) TagImage(newTag reference.Named, imageName string, force bool) error {
|
|
| 972 |
+ if _, isDigested := newTag.(reference.Digested); isDigested {
|
|
| 973 |
+ return errors.New("refusing to create a tag with a digest reference")
|
|
| 980 | 974 |
} |
| 981 |
- if err := idtools.MkdirAs(container.root, 0700, rootUID, rootGID); err != nil {
|
|
| 982 |
- return err |
|
| 975 |
+ if newTag.Name() == string(digest.Canonical) {
|
|
| 976 |
+ return errors.New("refusing to create an ambiguous tag using digest algorithm as name")
|
|
| 983 | 977 |
} |
| 984 |
- initID := fmt.Sprintf("%s-init", container.ID)
|
|
| 985 | 978 |
|
| 986 |
- if err := daemon.driver.Create(initID, container.ImageID, container.getMountLabel()); err != nil {
|
|
| 987 |
- return err |
|
| 988 |
- } |
|
| 989 |
- initPath, err := daemon.driver.Get(initID, "") |
|
| 979 |
+ newTag = registry.NormalizeLocalReference(newTag) |
|
| 980 |
+ imageID, err := daemon.GetImageID(imageName) |
|
| 990 | 981 |
if err != nil {
|
| 991 | 982 |
return err |
| 992 | 983 |
} |
| 993 |
- |
|
| 994 |
- if err := setupInitLayer(initPath, rootUID, rootGID); err != nil {
|
|
| 995 |
- if err := daemon.driver.Put(initID); err != nil {
|
|
| 996 |
- logrus.Errorf("Failed to Put init layer: %v", err)
|
|
| 997 |
- } |
|
| 998 |
- return err |
|
| 999 |
- } |
|
| 1000 |
- |
|
| 1001 |
- // We want to unmount init layer before we take snapshot of it |
|
| 1002 |
- // for the actual container. |
|
| 1003 |
- if err := daemon.driver.Put(initID); err != nil {
|
|
| 1004 |
- return err |
|
| 1005 |
- } |
|
| 1006 |
- |
|
| 1007 |
- if err := daemon.driver.Create(container.ID, initID, ""); err != nil {
|
|
| 1008 |
- return err |
|
| 1009 |
- } |
|
| 1010 |
- return nil |
|
| 1011 |
-} |
|
| 1012 |
- |
|
| 1013 |
-// Graph returns *graph.Graph which can be using for layers graph operations. |
|
| 1014 |
-func (daemon *Daemon) Graph() *graph.Graph {
|
|
| 1015 |
- return daemon.graph |
|
| 1016 |
-} |
|
| 1017 |
- |
|
| 1018 |
-// TagImage creates a tag in the repository reponame, pointing to the image named |
|
| 1019 |
-// imageName. If force is true, an existing tag with the same name may be |
|
| 1020 |
-// overwritten. |
|
| 1021 |
-func (daemon *Daemon) TagImage(repoName, tag, imageName string, force bool) error {
|
|
| 1022 |
- if err := daemon.repositories.Tag(repoName, tag, imageName, force); err != nil {
|
|
| 1023 |
- return err |
|
| 1024 |
- } |
|
| 1025 |
- daemon.EventsService.Log("tag", utils.ImageReference(repoName, tag), "")
|
|
| 1026 |
- return nil |
|
| 984 |
+ daemon.EventsService.Log("tag", newTag.String(), "")
|
|
| 985 |
+ return daemon.tagStore.Add(newTag, imageID, force) |
|
| 1027 | 986 |
} |
| 1028 | 987 |
|
| 1029 | 988 |
// PullImage initiates a pull operation. image is the repository name to pull, and |
| 1030 | 989 |
// tag may be either empty, or indicate a specific tag to pull. |
| 1031 |
-func (daemon *Daemon) PullImage(image string, tag string, imagePullConfig *graph.ImagePullConfig) error {
|
|
| 1032 |
- return daemon.repositories.Pull(image, tag, imagePullConfig) |
|
| 1033 |
-} |
|
| 990 |
+func (daemon *Daemon) PullImage(ref reference.Named, metaHeaders map[string][]string, authConfig *cliconfig.AuthConfig, outStream io.Writer) error {
|
|
| 991 |
+ imagePullConfig := &distribution.ImagePullConfig{
|
|
| 992 |
+ MetaHeaders: metaHeaders, |
|
| 993 |
+ AuthConfig: authConfig, |
|
| 994 |
+ OutStream: outStream, |
|
| 995 |
+ RegistryService: daemon.RegistryService, |
|
| 996 |
+ EventsService: daemon.EventsService, |
|
| 997 |
+ MetadataStore: daemon.distributionMetadataStore, |
|
| 998 |
+ LayerStore: daemon.layerStore, |
|
| 999 |
+ ImageStore: daemon.imageStore, |
|
| 1000 |
+ TagStore: daemon.tagStore, |
|
| 1001 |
+ Pool: daemon.distributionPool, |
|
| 1002 |
+ } |
|
| 1034 | 1003 |
|
| 1035 |
-// ImportImage imports an image, getting the archived layer data either from |
|
| 1036 |
-// inConfig (if src is "-"), or from a URI specified in src. Progress output is |
|
| 1037 |
-// written to outStream. Repository and tag names can optionally be given in |
|
| 1038 |
-// the repo and tag arguments, respectively. |
|
| 1039 |
-func (daemon *Daemon) ImportImage(src, repo, tag, msg string, inConfig io.ReadCloser, outStream io.Writer, containerConfig *runconfig.Config) error {
|
|
| 1040 |
- return daemon.repositories.Import(src, repo, tag, msg, inConfig, outStream, containerConfig) |
|
| 1004 |
+ return distribution.Pull(ref, imagePullConfig) |
|
| 1041 | 1005 |
} |
| 1042 | 1006 |
|
| 1043 | 1007 |
// ExportImage exports a list of images to the given output stream. The |
| ... | ... |
@@ -1046,47 +1065,214 @@ func (daemon *Daemon) ImportImage(src, repo, tag, msg string, inConfig io.ReadCl |
| 1046 | 1046 |
// the same tag are exported. names is the set of tags to export, and |
| 1047 | 1047 |
// outStream is the writer which the images are written to. |
| 1048 | 1048 |
func (daemon *Daemon) ExportImage(names []string, outStream io.Writer) error {
|
| 1049 |
- return daemon.repositories.ImageExport(names, outStream) |
|
| 1049 |
+ imageExporter := tarexport.NewTarExporter(daemon.imageStore, daemon.layerStore, daemon.tagStore) |
|
| 1050 |
+ return imageExporter.Save(names, outStream) |
|
| 1050 | 1051 |
} |
| 1051 | 1052 |
|
| 1052 | 1053 |
// PushImage initiates a push operation on the repository named localName. |
| 1053 |
-func (daemon *Daemon) PushImage(localName string, imagePushConfig *graph.ImagePushConfig) error {
|
|
| 1054 |
- return daemon.repositories.Push(localName, imagePushConfig) |
|
| 1054 |
+func (daemon *Daemon) PushImage(ref reference.Named, metaHeaders map[string][]string, authConfig *cliconfig.AuthConfig, outStream io.Writer) error {
|
|
| 1055 |
+ imagePushConfig := &distribution.ImagePushConfig{
|
|
| 1056 |
+ MetaHeaders: metaHeaders, |
|
| 1057 |
+ AuthConfig: authConfig, |
|
| 1058 |
+ OutStream: outStream, |
|
| 1059 |
+ RegistryService: daemon.RegistryService, |
|
| 1060 |
+ EventsService: daemon.EventsService, |
|
| 1061 |
+ MetadataStore: daemon.distributionMetadataStore, |
|
| 1062 |
+ LayerStore: daemon.layerStore, |
|
| 1063 |
+ ImageStore: daemon.imageStore, |
|
| 1064 |
+ TagStore: daemon.tagStore, |
|
| 1065 |
+ TrustKey: daemon.trustKey, |
|
| 1066 |
+ } |
|
| 1067 |
+ |
|
| 1068 |
+ return distribution.Push(ref, imagePushConfig) |
|
| 1055 | 1069 |
} |
| 1056 | 1070 |
|
| 1057 | 1071 |
// LookupImage looks up an image by name and returns it as an ImageInspect |
| 1058 | 1072 |
// structure. |
| 1059 | 1073 |
func (daemon *Daemon) LookupImage(name string) (*types.ImageInspect, error) {
|
| 1060 |
- return daemon.repositories.Lookup(name) |
|
| 1074 |
+ img, err := daemon.GetImage(name) |
|
| 1075 |
+ if err != nil {
|
|
| 1076 |
+ return nil, fmt.Errorf("No such image: %s", name)
|
|
| 1077 |
+ } |
|
| 1078 |
+ |
|
| 1079 |
+ refs := daemon.tagStore.References(img.ID()) |
|
| 1080 |
+ repoTags := []string{}
|
|
| 1081 |
+ repoDigests := []string{}
|
|
| 1082 |
+ for _, ref := range refs {
|
|
| 1083 |
+ switch ref.(type) {
|
|
| 1084 |
+ case reference.Tagged: |
|
| 1085 |
+ repoTags = append(repoTags, ref.String()) |
|
| 1086 |
+ case reference.Digested: |
|
| 1087 |
+ repoDigests = append(repoDigests, ref.String()) |
|
| 1088 |
+ } |
|
| 1089 |
+ } |
|
| 1090 |
+ |
|
| 1091 |
+ var size int64 |
|
| 1092 |
+ var layerMetadata map[string]string |
|
| 1093 |
+ layerID := img.RootFS.ChainID() |
|
| 1094 |
+ if layerID != "" {
|
|
| 1095 |
+ l, err := daemon.layerStore.Get(layerID) |
|
| 1096 |
+ if err != nil {
|
|
| 1097 |
+ return nil, err |
|
| 1098 |
+ } |
|
| 1099 |
+ defer layer.ReleaseAndLog(daemon.layerStore, l) |
|
| 1100 |
+ size, err = l.Size() |
|
| 1101 |
+ if err != nil {
|
|
| 1102 |
+ return nil, err |
|
| 1103 |
+ } |
|
| 1104 |
+ |
|
| 1105 |
+ layerMetadata, err = l.Metadata() |
|
| 1106 |
+ if err != nil {
|
|
| 1107 |
+ return nil, err |
|
| 1108 |
+ } |
|
| 1109 |
+ } |
|
| 1110 |
+ |
|
| 1111 |
+ imageInspect := &types.ImageInspect{
|
|
| 1112 |
+ ID: img.ID().String(), |
|
| 1113 |
+ RepoTags: repoTags, |
|
| 1114 |
+ RepoDigests: repoDigests, |
|
| 1115 |
+ Parent: img.Parent.String(), |
|
| 1116 |
+ Comment: img.Comment, |
|
| 1117 |
+ Created: img.Created.Format(time.RFC3339Nano), |
|
| 1118 |
+ Container: img.Container, |
|
| 1119 |
+ ContainerConfig: &img.ContainerConfig, |
|
| 1120 |
+ DockerVersion: img.DockerVersion, |
|
| 1121 |
+ Author: img.Author, |
|
| 1122 |
+ Config: img.Config, |
|
| 1123 |
+ Architecture: img.Architecture, |
|
| 1124 |
+ Os: img.OS, |
|
| 1125 |
+ Size: size, |
|
| 1126 |
+ VirtualSize: size, // TODO: field unused, deprecate |
|
| 1127 |
+ } |
|
| 1128 |
+ |
|
| 1129 |
+ imageInspect.GraphDriver.Name = daemon.driver.String() |
|
| 1130 |
+ |
|
| 1131 |
+ imageInspect.GraphDriver.Data = layerMetadata |
|
| 1132 |
+ |
|
| 1133 |
+ return imageInspect, nil |
|
| 1061 | 1134 |
} |
| 1062 | 1135 |
|
| 1063 | 1136 |
// LoadImage uploads a set of images into the repository. This is the |
| 1064 | 1137 |
// complement of ImageExport. The input stream is an uncompressed tar |
| 1065 | 1138 |
// ball containing images and metadata. |
| 1066 | 1139 |
func (daemon *Daemon) LoadImage(inTar io.ReadCloser, outStream io.Writer) error {
|
| 1067 |
- return daemon.repositories.Load(inTar, outStream) |
|
| 1068 |
-} |
|
| 1069 |
- |
|
| 1070 |
-// ListImages returns a filtered list of images. filterArgs is a JSON-encoded set |
|
| 1071 |
-// of filter arguments which will be interpreted by pkg/parsers/filters. |
|
| 1072 |
-// filter is a shell glob string applied to repository names. The argument |
|
| 1073 |
-// named all controls whether all images in the graph are filtered, or just |
|
| 1074 |
-// the heads. |
|
| 1075 |
-func (daemon *Daemon) ListImages(filterArgs, filter string, all bool) ([]*types.Image, error) {
|
|
| 1076 |
- return daemon.repositories.Images(filterArgs, filter, all) |
|
| 1140 |
+ imageExporter := tarexport.NewTarExporter(daemon.imageStore, daemon.layerStore, daemon.tagStore) |
|
| 1141 |
+ return imageExporter.Load(inTar, outStream) |
|
| 1077 | 1142 |
} |
| 1078 | 1143 |
|
| 1079 | 1144 |
// ImageHistory returns a slice of ImageHistory structures for the specified image |
| 1080 | 1145 |
// name by walking the image lineage. |
| 1081 | 1146 |
func (daemon *Daemon) ImageHistory(name string) ([]*types.ImageHistory, error) {
|
| 1082 |
- return daemon.repositories.History(name) |
|
| 1147 |
+ img, err := daemon.GetImage(name) |
|
| 1148 |
+ if err != nil {
|
|
| 1149 |
+ return nil, err |
|
| 1150 |
+ } |
|
| 1151 |
+ |
|
| 1152 |
+ history := []*types.ImageHistory{}
|
|
| 1153 |
+ |
|
| 1154 |
+ layerCounter := 0 |
|
| 1155 |
+ rootFS := *img.RootFS |
|
| 1156 |
+ rootFS.DiffIDs = nil |
|
| 1157 |
+ |
|
| 1158 |
+ for _, h := range img.History {
|
|
| 1159 |
+ var layerSize int64 |
|
| 1160 |
+ |
|
| 1161 |
+ if !h.EmptyLayer {
|
|
| 1162 |
+ if len(img.RootFS.DiffIDs) <= layerCounter {
|
|
| 1163 |
+ return nil, errors.New("too many non-empty layers in History section")
|
|
| 1164 |
+ } |
|
| 1165 |
+ |
|
| 1166 |
+ rootFS.Append(img.RootFS.DiffIDs[layerCounter]) |
|
| 1167 |
+ l, err := daemon.layerStore.Get(rootFS.ChainID()) |
|
| 1168 |
+ if err != nil {
|
|
| 1169 |
+ return nil, err |
|
| 1170 |
+ } |
|
| 1171 |
+ layerSize, err = l.DiffSize() |
|
| 1172 |
+ layer.ReleaseAndLog(daemon.layerStore, l) |
|
| 1173 |
+ if err != nil {
|
|
| 1174 |
+ return nil, err |
|
| 1175 |
+ } |
|
| 1176 |
+ |
|
| 1177 |
+ layerCounter++ |
|
| 1178 |
+ } |
|
| 1179 |
+ |
|
| 1180 |
+ history = append([]*types.ImageHistory{{
|
|
| 1181 |
+ ID: "<missing>", |
|
| 1182 |
+ Created: h.Created.Unix(), |
|
| 1183 |
+ CreatedBy: h.CreatedBy, |
|
| 1184 |
+ Comment: h.Comment, |
|
| 1185 |
+ Size: layerSize, |
|
| 1186 |
+ }}, history...) |
|
| 1187 |
+ } |
|
| 1188 |
+ |
|
| 1189 |
+ // Fill in image IDs and tags |
|
| 1190 |
+ histImg := img |
|
| 1191 |
+ id := img.ID() |
|
| 1192 |
+ for _, h := range history {
|
|
| 1193 |
+ h.ID = id.String() |
|
| 1194 |
+ |
|
| 1195 |
+ var tags []string |
|
| 1196 |
+ for _, r := range daemon.tagStore.References(id) {
|
|
| 1197 |
+ if _, ok := r.(reference.NamedTagged); ok {
|
|
| 1198 |
+ tags = append(tags, r.String()) |
|
| 1199 |
+ } |
|
| 1200 |
+ } |
|
| 1201 |
+ |
|
| 1202 |
+ h.Tags = tags |
|
| 1203 |
+ |
|
| 1204 |
+ id = histImg.Parent |
|
| 1205 |
+ if id == "" {
|
|
| 1206 |
+ break |
|
| 1207 |
+ } |
|
| 1208 |
+ histImg, err = daemon.GetImage(id.String()) |
|
| 1209 |
+ if err != nil {
|
|
| 1210 |
+ break |
|
| 1211 |
+ } |
|
| 1212 |
+ } |
|
| 1213 |
+ |
|
| 1214 |
+ return history, nil |
|
| 1083 | 1215 |
} |
| 1084 | 1216 |
|
| 1085 |
-// GetImage returns pointer to an Image struct corresponding to the given |
|
| 1086 |
-// name. The name can include an optional tag; otherwise the default tag will |
|
| 1087 |
-// be used. |
|
| 1088 |
-func (daemon *Daemon) GetImage(name string) (*image.Image, error) {
|
|
| 1089 |
- return daemon.repositories.LookupImage(name) |
|
| 1217 |
+// GetImageID returns an image ID corresponding to the image referred to by |
|
| 1218 |
+// refOrID. |
|
| 1219 |
+func (daemon *Daemon) GetImageID(refOrID string) (image.ID, error) {
|
|
| 1220 |
+ // Treat as an ID |
|
| 1221 |
+ if id, err := digest.ParseDigest(refOrID); err == nil {
|
|
| 1222 |
+ return image.ID(id), nil |
|
| 1223 |
+ } |
|
| 1224 |
+ |
|
| 1225 |
+ // Treat it as a possible tag or digest reference |
|
| 1226 |
+ if ref, err := reference.ParseNamed(refOrID); err == nil {
|
|
| 1227 |
+ ref = registry.NormalizeLocalReference(ref) |
|
| 1228 |
+ if id, err := daemon.tagStore.Get(ref); err == nil {
|
|
| 1229 |
+ return id, nil |
|
| 1230 |
+ } |
|
| 1231 |
+ if tagged, ok := ref.(reference.Tagged); ok {
|
|
| 1232 |
+ if id, err := daemon.imageStore.Search(tagged.Tag()); err == nil {
|
|
| 1233 |
+ for _, namedRef := range daemon.tagStore.References(id) {
|
|
| 1234 |
+ if namedRef.Name() == ref.Name() {
|
|
| 1235 |
+ return id, nil |
|
| 1236 |
+ } |
|
| 1237 |
+ } |
|
| 1238 |
+ } |
|
| 1239 |
+ } |
|
| 1240 |
+ } |
|
| 1241 |
+ |
|
| 1242 |
+ // Search based on ID |
|
| 1243 |
+ if id, err := daemon.imageStore.Search(refOrID); err == nil {
|
|
| 1244 |
+ return id, nil |
|
| 1245 |
+ } |
|
| 1246 |
+ |
|
| 1247 |
+ return "", ErrImageDoesNotExist{refOrID}
|
|
| 1248 |
+} |
|
| 1249 |
+ |
|
| 1250 |
+// GetImage returns an image corresponding to the image referred to by refOrID. |
|
| 1251 |
+func (daemon *Daemon) GetImage(refOrID string) (*image.Image, error) {
|
|
| 1252 |
+ imgID, err := daemon.GetImageID(refOrID) |
|
| 1253 |
+ if err != nil {
|
|
| 1254 |
+ return nil, err |
|
| 1255 |
+ } |
|
| 1256 |
+ return daemon.imageStore.Get(imgID) |
|
| 1090 | 1257 |
} |
| 1091 | 1258 |
|
| 1092 | 1259 |
func (daemon *Daemon) config() *Config {
|
| ... | ... |
@@ -1132,33 +1318,23 @@ func (daemon *Daemon) GetRemappedUIDGID() (int, int) {
|
| 1132 | 1132 |
// of the image with imgID, that had the same config when it was |
| 1133 | 1133 |
// created. nil is returned if a child cannot be found. An error is |
| 1134 | 1134 |
// returned if the parent image cannot be found. |
| 1135 |
-func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*image.Image, error) {
|
|
| 1136 |
- // for now just exit if imgID has no children. |
|
| 1137 |
- // maybe parentRefs in graph could be used to store |
|
| 1138 |
- // the Image obj children for faster lookup below but this can |
|
| 1139 |
- // be quite memory hungry. |
|
| 1140 |
- if !daemon.Graph().HasChildren(imgID) {
|
|
| 1141 |
- return nil, nil |
|
| 1142 |
- } |
|
| 1143 |
- |
|
| 1135 |
+func (daemon *Daemon) ImageGetCached(imgID image.ID, config *runconfig.Config) (*image.Image, error) {
|
|
| 1144 | 1136 |
// Retrieve all images |
| 1145 |
- images := daemon.Graph().Map() |
|
| 1137 |
+ imgs := daemon.Map() |
|
| 1146 | 1138 |
|
| 1147 |
- // Store the tree in a map of map (map[parentId][childId]) |
|
| 1148 |
- imageMap := make(map[string]map[string]struct{})
|
|
| 1149 |
- for _, img := range images {
|
|
| 1150 |
- if _, exists := imageMap[img.Parent]; !exists {
|
|
| 1151 |
- imageMap[img.Parent] = make(map[string]struct{})
|
|
| 1139 |
+ var siblings []image.ID |
|
| 1140 |
+ for id, img := range imgs {
|
|
| 1141 |
+ if img.Parent == imgID {
|
|
| 1142 |
+ siblings = append(siblings, id) |
|
| 1152 | 1143 |
} |
| 1153 |
- imageMap[img.Parent][img.ID] = struct{}{}
|
|
| 1154 | 1144 |
} |
| 1155 | 1145 |
|
| 1156 | 1146 |
// Loop on the children of the given image and check the config |
| 1157 | 1147 |
var match *image.Image |
| 1158 |
- for elem := range imageMap[imgID] {
|
|
| 1159 |
- img, ok := images[elem] |
|
| 1148 |
+ for _, id := range siblings {
|
|
| 1149 |
+ img, ok := imgs[id] |
|
| 1160 | 1150 |
if !ok {
|
| 1161 |
- return nil, fmt.Errorf("unable to find image %q", elem)
|
|
| 1151 |
+ return nil, fmt.Errorf("unable to find image %q", id)
|
|
| 1162 | 1152 |
} |
| 1163 | 1153 |
if runconfig.Compare(&img.ContainerConfig, config) {
|
| 1164 | 1154 |
if match == nil || match.Created.Before(img.Created) {
|
| ... | ... |
@@ -1179,6 +1355,12 @@ func tempDir(rootDir string, rootUID, rootGID int) (string, error) {
|
| 1179 | 1179 |
} |
| 1180 | 1180 |
|
| 1181 | 1181 |
func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error {
|
| 1182 |
+ container.Lock() |
|
| 1183 |
+ if err := parseSecurityOpt(container, hostConfig); err != nil {
|
|
| 1184 |
+ container.Unlock() |
|
| 1185 |
+ return err |
|
| 1186 |
+ } |
|
| 1187 |
+ container.Unlock() |
|
| 1182 | 1188 |
|
| 1183 | 1189 |
// Do not lock while creating volumes since this could be calling out to external plugins |
| 1184 | 1190 |
// Don't want to block other actions, like `docker ps` because we're waiting on an external plugin |
| ... | ... |
@@ -1199,6 +1381,11 @@ func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig. |
| 1199 | 1199 |
return nil |
| 1200 | 1200 |
} |
| 1201 | 1201 |
|
| 1202 |
+func (daemon *Daemon) setupInitLayer(initPath string) error {
|
|
| 1203 |
+ rootUID, rootGID := daemon.GetRemappedUIDGID() |
|
| 1204 |
+ return setupInitLayer(initPath, rootUID, rootGID) |
|
| 1205 |
+} |
|
| 1206 |
+ |
|
| 1202 | 1207 |
func setDefaultMtu(config *Config) {
|
| 1203 | 1208 |
// do nothing if the config does not have the default 0 value. |
| 1204 | 1209 |
if config.Mtu != 0 {
|
| ... | ... |
@@ -14,12 +14,15 @@ import ( |
| 14 | 14 |
"github.com/Sirupsen/logrus" |
| 15 | 15 |
"github.com/docker/docker/daemon/graphdriver" |
| 16 | 16 |
derr "github.com/docker/docker/errors" |
| 17 |
+ "github.com/docker/docker/image" |
|
| 18 |
+ "github.com/docker/docker/layer" |
|
| 17 | 19 |
pblkiodev "github.com/docker/docker/pkg/blkiodev" |
| 18 | 20 |
"github.com/docker/docker/pkg/idtools" |
| 19 | 21 |
"github.com/docker/docker/pkg/parsers" |
| 20 | 22 |
"github.com/docker/docker/pkg/parsers/kernel" |
| 21 | 23 |
"github.com/docker/docker/pkg/sysinfo" |
| 22 | 24 |
"github.com/docker/docker/runconfig" |
| 25 |
+ "github.com/docker/docker/tag" |
|
| 23 | 26 |
"github.com/docker/libnetwork" |
| 24 | 27 |
nwconfig "github.com/docker/libnetwork/config" |
| 25 | 28 |
"github.com/docker/libnetwork/drivers/bridge" |
| ... | ... |
@@ -601,9 +604,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *Container) error {
|
| 601 | 601 |
// conditionalUnmountOnCleanup is a platform specific helper function called |
| 602 | 602 |
// during the cleanup of a container to unmount. |
| 603 | 603 |
func (daemon *Daemon) conditionalUnmountOnCleanup(container *Container) {
|
| 604 |
- if err := daemon.Unmount(container); err != nil {
|
|
| 605 |
- logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
|
|
| 606 |
- } |
|
| 604 |
+ daemon.Unmount(container) |
|
| 607 | 605 |
} |
| 608 | 606 |
|
| 609 | 607 |
// getDefaultRouteMtu returns the MTU for the default route's interface. |
| ... | ... |
@@ -624,3 +625,8 @@ func getDefaultRouteMtu() (int, error) {
|
| 624 | 624 |
} |
| 625 | 625 |
return 0, errNoDefaultRoute |
| 626 | 626 |
} |
| 627 |
+ |
|
| 628 |
+func restoreCustomImage(driver graphdriver.Driver, is image.Store, ls layer.Store, ts tag.Store) error {
|
|
| 629 |
+ // Unix has no custom images to register |
|
| 630 |
+ return nil |
|
| 631 |
+} |
| ... | ... |
@@ -1,12 +1,22 @@ |
| 1 | 1 |
package daemon |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 5 |
+ "errors" |
|
| 4 | 6 |
"fmt" |
| 7 |
+ "path/filepath" |
|
| 8 |
+ "runtime" |
|
| 9 |
+ "strings" |
|
| 5 | 10 |
|
| 6 | 11 |
"github.com/Sirupsen/logrus" |
| 12 |
+ "github.com/docker/distribution/reference" |
|
| 7 | 13 |
"github.com/docker/docker/daemon/graphdriver" |
| 14 |
+ "github.com/docker/docker/dockerversion" |
|
| 15 |
+ "github.com/docker/docker/image" |
|
| 16 |
+ "github.com/docker/docker/layer" |
|
| 17 |
+ "github.com/docker/docker/tag" |
|
| 8 | 18 |
// register the windows graph driver |
| 9 |
- _ "github.com/docker/docker/daemon/graphdriver/windows" |
|
| 19 |
+ "github.com/docker/docker/daemon/graphdriver/windows" |
|
| 10 | 20 |
"github.com/docker/docker/pkg/system" |
| 11 | 21 |
"github.com/docker/docker/runconfig" |
| 12 | 22 |
"github.com/docker/libnetwork" |
| ... | ... |
@@ -128,8 +138,71 @@ func (daemon *Daemon) conditionalMountOnStart(container *Container) error {
|
| 128 | 128 |
func (daemon *Daemon) conditionalUnmountOnCleanup(container *Container) {
|
| 129 | 129 |
// We do not unmount if a Hyper-V container |
| 130 | 130 |
if !container.hostConfig.Isolation.IsHyperV() {
|
| 131 |
- if err := daemon.Unmount(container); err != nil {
|
|
| 132 |
- logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
|
|
| 131 |
+ daemon.Unmount(container) |
|
| 132 |
+ } |
|
| 133 |
+} |
|
| 134 |
+ |
|
| 135 |
+func restoreCustomImage(driver graphdriver.Driver, is image.Store, ls layer.Store, ts tag.Store) error {
|
|
| 136 |
+ if wd, ok := driver.(*windows.Driver); ok {
|
|
| 137 |
+ imageInfos, err := wd.GetCustomImageInfos() |
|
| 138 |
+ if err != nil {
|
|
| 139 |
+ return err |
|
| 133 | 140 |
} |
| 141 |
+ |
|
| 142 |
+ // Convert imageData to valid image configuration |
|
| 143 |
+ for i := range imageInfos {
|
|
| 144 |
+ name := strings.ToLower(imageInfos[i].Name) |
|
| 145 |
+ |
|
| 146 |
+ type registrar interface {
|
|
| 147 |
+ RegisterDiffID(graphID string, size int64) (layer.Layer, error) |
|
| 148 |
+ } |
|
| 149 |
+ r, ok := ls.(registrar) |
|
| 150 |
+ if !ok {
|
|
| 151 |
+ return errors.New("Layerstore doesn't support RegisterDiffID")
|
|
| 152 |
+ } |
|
| 153 |
+ if _, err := r.RegisterDiffID(imageInfos[i].ID, imageInfos[i].Size); err != nil {
|
|
| 154 |
+ return err |
|
| 155 |
+ } |
|
| 156 |
+ // layer is intentionally not released |
|
| 157 |
+ |
|
| 158 |
+ rootFS := image.NewRootFS() |
|
| 159 |
+ rootFS.BaseLayer = filepath.Base(imageInfos[i].Path) |
|
| 160 |
+ |
|
| 161 |
+ // Create history for base layer |
|
| 162 |
+ config, err := json.Marshal(&image.Image{
|
|
| 163 |
+ V1Image: image.V1Image{
|
|
| 164 |
+ DockerVersion: dockerversion.Version, |
|
| 165 |
+ Architecture: runtime.GOARCH, |
|
| 166 |
+ OS: runtime.GOOS, |
|
| 167 |
+ Created: imageInfos[i].CreatedTime, |
|
| 168 |
+ }, |
|
| 169 |
+ RootFS: rootFS, |
|
| 170 |
+ History: []image.History{},
|
|
| 171 |
+ }) |
|
| 172 |
+ |
|
| 173 |
+ named, err := reference.ParseNamed(name) |
|
| 174 |
+ if err != nil {
|
|
| 175 |
+ return err |
|
| 176 |
+ } |
|
| 177 |
+ |
|
| 178 |
+ ref, err := reference.WithTag(named, imageInfos[i].Version) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ return err |
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ id, err := is.Create(config) |
|
| 184 |
+ if err != nil {
|
|
| 185 |
+ return err |
|
| 186 |
+ } |
|
| 187 |
+ |
|
| 188 |
+ if err := ts.Add(ref, id, true); err != nil {
|
|
| 189 |
+ return err |
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ logrus.Debugf("Registered base layer %s as %s", ref, id)
|
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 134 | 195 |
} |
| 196 |
+ |
|
| 197 |
+ return nil |
|
| 135 | 198 |
} |
| ... | ... |
@@ -9,17 +9,16 @@ import ( |
| 9 | 9 |
"strings" |
| 10 | 10 |
|
| 11 | 11 |
"github.com/Sirupsen/logrus" |
| 12 |
+ "github.com/docker/distribution/reference" |
|
| 12 | 13 |
"github.com/docker/docker/api" |
| 13 | 14 |
"github.com/docker/docker/builder" |
| 14 | 15 |
"github.com/docker/docker/cliconfig" |
| 15 | 16 |
"github.com/docker/docker/daemon" |
| 16 |
- "github.com/docker/docker/graph" |
|
| 17 | 17 |
"github.com/docker/docker/image" |
| 18 | 18 |
"github.com/docker/docker/pkg/archive" |
| 19 | 19 |
"github.com/docker/docker/pkg/httputils" |
| 20 | 20 |
"github.com/docker/docker/pkg/idtools" |
| 21 | 21 |
"github.com/docker/docker/pkg/ioutils" |
| 22 |
- "github.com/docker/docker/pkg/parsers" |
|
| 23 | 22 |
"github.com/docker/docker/pkg/progressreader" |
| 24 | 23 |
"github.com/docker/docker/pkg/urlutil" |
| 25 | 24 |
"github.com/docker/docker/registry" |
| ... | ... |
@@ -44,15 +43,24 @@ func (d Docker) LookupImage(name string) (*image.Image, error) {
|
| 44 | 44 |
|
| 45 | 45 |
// Pull tells Docker to pull image referenced by `name`. |
| 46 | 46 |
func (d Docker) Pull(name string) (*image.Image, error) {
|
| 47 |
- remote, tag := parsers.ParseRepositoryTag(name) |
|
| 48 |
- if tag == "" {
|
|
| 49 |
- tag = "latest" |
|
| 47 |
+ ref, err := reference.ParseNamed(name) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return nil, err |
|
| 50 |
+ } |
|
| 51 |
+ switch ref.(type) {
|
|
| 52 |
+ case reference.Tagged: |
|
| 53 |
+ case reference.Digested: |
|
| 54 |
+ default: |
|
| 55 |
+ ref, err = reference.WithTag(ref, "latest") |
|
| 56 |
+ if err != nil {
|
|
| 57 |
+ return nil, err |
|
| 58 |
+ } |
|
| 50 | 59 |
} |
| 51 | 60 |
|
| 52 | 61 |
pullRegistryAuth := &cliconfig.AuthConfig{}
|
| 53 | 62 |
if len(d.AuthConfigs) > 0 {
|
| 54 | 63 |
// The request came with a full auth config file, we prefer to use that |
| 55 |
- repoInfo, err := d.Daemon.RegistryService.ResolveRepository(remote) |
|
| 64 |
+ repoInfo, err := d.Daemon.RegistryService.ResolveRepository(ref) |
|
| 56 | 65 |
if err != nil {
|
| 57 | 66 |
return nil, err |
| 58 | 67 |
} |
| ... | ... |
@@ -64,12 +72,7 @@ func (d Docker) Pull(name string) (*image.Image, error) {
|
| 64 | 64 |
pullRegistryAuth = &resolvedConfig |
| 65 | 65 |
} |
| 66 | 66 |
|
| 67 |
- imagePullConfig := &graph.ImagePullConfig{
|
|
| 68 |
- AuthConfig: pullRegistryAuth, |
|
| 69 |
- OutStream: ioutils.NopWriteCloser(d.OutOld), |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- if err := d.Daemon.PullImage(remote, tag, imagePullConfig); err != nil {
|
|
| 67 |
+ if err := d.Daemon.PullImage(ref, nil, pullRegistryAuth, ioutils.NopWriteCloser(d.OutOld)); err != nil {
|
|
| 73 | 68 |
return nil, err |
| 74 | 69 |
} |
| 75 | 70 |
|
| ... | ... |
@@ -106,18 +109,20 @@ func (d Docker) Remove(id string, cfg *daemon.ContainerRmConfig) error {
|
| 106 | 106 |
} |
| 107 | 107 |
|
| 108 | 108 |
// Commit creates a new Docker image from an existing Docker container. |
| 109 |
-func (d Docker) Commit(name string, cfg *daemon.ContainerCommitConfig) (*image.Image, error) {
|
|
| 109 |
+func (d Docker) Commit(name string, cfg *daemon.ContainerCommitConfig) (string, error) {
|
|
| 110 | 110 |
return d.Daemon.Commit(name, cfg) |
| 111 | 111 |
} |
| 112 | 112 |
|
| 113 | 113 |
// Retain retains an image avoiding it to be removed or overwritten until a corresponding Release() call. |
| 114 | 114 |
func (d Docker) Retain(sessionID, imgID string) {
|
| 115 |
- d.Daemon.Graph().Retain(sessionID, imgID) |
|
| 115 |
+ // FIXME: This will be solved with tags in client-side builder |
|
| 116 |
+ //d.Daemon.Graph().Retain(sessionID, imgID) |
|
| 116 | 117 |
} |
| 117 | 118 |
|
| 118 | 119 |
// Release releases a list of images that were retained for the time of a build. |
| 119 | 120 |
func (d Docker) Release(sessionID string, activeImages []string) {
|
| 120 |
- d.Daemon.Graph().Release(sessionID, activeImages...) |
|
| 121 |
+ // FIXME: This will be solved with tags in client-side builder |
|
| 122 |
+ //d.Daemon.Graph().Release(sessionID, activeImages...) |
|
| 121 | 123 |
} |
| 122 | 124 |
|
| 123 | 125 |
// Copy copies/extracts a source FileInfo to a destination path inside a container |
| ... | ... |
@@ -199,11 +204,11 @@ func (d Docker) Copy(c *daemon.Container, destPath string, src builder.FileInfo, |
| 199 | 199 |
// GetCachedImage returns a reference to a cached image whose parent equals `parent` |
| 200 | 200 |
// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error. |
| 201 | 201 |
func (d Docker) GetCachedImage(imgID string, cfg *runconfig.Config) (string, error) {
|
| 202 |
- cache, err := d.Daemon.ImageGetCached(imgID, cfg) |
|
| 202 |
+ cache, err := d.Daemon.ImageGetCached(image.ID(imgID), cfg) |
|
| 203 | 203 |
if cache == nil || err != nil {
|
| 204 | 204 |
return "", err |
| 205 | 205 |
} |
| 206 |
- return cache.ID, nil |
|
| 206 |
+ return cache.ID().String(), nil |
|
| 207 | 207 |
} |
| 208 | 208 |
|
| 209 | 209 |
// Kill stops the container execution abruptly. |
| ... | ... |
@@ -218,7 +223,8 @@ func (d Docker) Mount(c *daemon.Container) error {
|
| 218 | 218 |
|
| 219 | 219 |
// Unmount unmounts the root filesystem for the container. |
| 220 | 220 |
func (d Docker) Unmount(c *daemon.Container) error {
|
| 221 |
- return d.Daemon.Unmount(c) |
|
| 221 |
+ d.Daemon.Unmount(c) |
|
| 222 |
+ return nil |
|
| 222 | 223 |
} |
| 223 | 224 |
|
| 224 | 225 |
// Start starts a container |
| ... | ... |
@@ -1,12 +1,12 @@ |
| 1 | 1 |
package daemon |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 | 4 |
"os" |
| 6 | 5 |
"path" |
| 7 | 6 |
|
| 8 | 7 |
"github.com/Sirupsen/logrus" |
| 9 | 8 |
derr "github.com/docker/docker/errors" |
| 9 |
+ "github.com/docker/docker/layer" |
|
| 10 | 10 |
volumestore "github.com/docker/docker/volume/store" |
| 11 | 11 |
) |
| 12 | 12 |
|
| ... | ... |
@@ -119,15 +119,12 @@ func (daemon *Daemon) rm(container *Container, forceRemove bool) (err error) {
|
| 119 | 119 |
logrus.Debugf("Unable to remove container from link graph: %s", err)
|
| 120 | 120 |
} |
| 121 | 121 |
|
| 122 |
- if err = daemon.driver.Remove(container.ID); err != nil {
|
|
| 122 |
+ metadata, err := daemon.layerStore.DeleteMount(container.ID) |
|
| 123 |
+ layer.LogReleaseMetadata(metadata) |
|
| 124 |
+ if err != nil {
|
|
| 123 | 125 |
return derr.ErrorCodeRmDriverFS.WithArgs(daemon.driver, container.ID, err) |
| 124 | 126 |
} |
| 125 | 127 |
|
| 126 |
- initID := fmt.Sprintf("%s-init", container.ID)
|
|
| 127 |
- if err := daemon.driver.Remove(initID); err != nil {
|
|
| 128 |
- return derr.ErrorCodeRmInit.WithArgs(daemon.driver, initID, err) |
|
| 129 |
- } |
|
| 130 |
- |
|
| 131 | 128 |
if err = os.RemoveAll(container.root); err != nil {
|
| 132 | 129 |
return derr.ErrorCodeRmFS.WithArgs(container.ID, err) |
| 133 | 130 |
} |
| ... | ... |
@@ -3,21 +3,25 @@ package daemon |
| 3 | 3 |
import ( |
| 4 | 4 |
"strings" |
| 5 | 5 |
|
| 6 |
+ "github.com/docker/distribution/reference" |
|
| 6 | 7 |
derr "github.com/docker/docker/errors" |
| 7 |
- "github.com/docker/docker/graph/tags" |
|
| 8 |
- "github.com/docker/docker/pkg/parsers" |
|
| 8 |
+ tagpkg "github.com/docker/docker/tag" |
|
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 |
-func (d *Daemon) graphNotExistToErrcode(imageName string, err error) error {
|
|
| 12 |
- if d.Graph().IsNotExist(err, imageName) {
|
|
| 13 |
- if strings.Contains(imageName, "@") {
|
|
| 14 |
- return derr.ErrorCodeNoSuchImageHash.WithArgs(imageName) |
|
| 11 |
+func (d *Daemon) imageNotExistToErrcode(err error) error {
|
|
| 12 |
+ if dne, isDNE := err.(ErrImageDoesNotExist); isDNE {
|
|
| 13 |
+ if strings.Contains(dne.RefOrID, "@") {
|
|
| 14 |
+ return derr.ErrorCodeNoSuchImageHash.WithArgs(dne.RefOrID) |
|
| 15 | 15 |
} |
| 16 |
- img, tag := parsers.ParseRepositoryTag(imageName) |
|
| 17 |
- if tag == "" {
|
|
| 18 |
- tag = tags.DefaultTag |
|
| 16 |
+ tag := tagpkg.DefaultTag |
|
| 17 |
+ ref, err := reference.ParseNamed(dne.RefOrID) |
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ return derr.ErrorCodeNoSuchImageTag.WithArgs(dne.RefOrID, tag) |
|
| 19 | 20 |
} |
| 20 |
- return derr.ErrorCodeNoSuchImageTag.WithArgs(img, tag) |
|
| 21 |
+ if tagged, isTagged := ref.(reference.Tagged); isTagged {
|
|
| 22 |
+ tag = tagged.Tag() |
|
| 23 |
+ } |
|
| 24 |
+ return derr.ErrorCodeNoSuchImageTag.WithArgs(ref.Name(), tag) |
|
| 21 | 25 |
} |
| 22 | 26 |
return err |
| 23 | 27 |
} |
| ... | ... |
@@ -1,8 +1,8 @@ |
| 1 | 1 |
package events |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "github.com/docker/distribution/reference" |
|
| 4 | 5 |
"github.com/docker/docker/pkg/jsonmessage" |
| 5 |
- "github.com/docker/docker/pkg/parsers" |
|
| 6 | 6 |
"github.com/docker/docker/pkg/parsers/filters" |
| 7 | 7 |
) |
| 8 | 8 |
|
| ... | ... |
@@ -38,8 +38,11 @@ func (ef *Filter) isLabelFieldIncluded(id string) bool {
|
| 38 | 38 |
// against the stripped repo name without any tags. |
| 39 | 39 |
func (ef *Filter) isImageIncluded(eventID string, eventFrom string) bool {
|
| 40 | 40 |
stripTag := func(image string) string {
|
| 41 |
- repo, _ := parsers.ParseRepositoryTag(image) |
|
| 42 |
- return repo |
|
| 41 |
+ ref, err := reference.ParseNamed(image) |
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ return image |
|
| 44 |
+ } |
|
| 45 |
+ return ref.Name() |
|
| 43 | 46 |
} |
| 44 | 47 |
|
| 45 | 48 |
return isFieldIncluded(eventID, ef.filter["image"]) || |
| ... | ... |
@@ -1,5 +1,3 @@ |
| 1 |
-// +build daemon |
|
| 2 |
- |
|
| 3 | 1 |
package graphdriver |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| ... | ... |
@@ -13,6 +11,12 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/pkg/ioutils" |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
+var ( |
|
| 17 |
+ // ApplyUncompressedLayer defines the unpack method used by the graph |
|
| 18 |
+ // driver. |
|
| 19 |
+ ApplyUncompressedLayer = chrootarchive.ApplyUncompressedLayer |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 16 | 22 |
// NaiveDiffDriver takes a ProtoDriver and adds the |
| 17 | 23 |
// capability of the Diffing methods which it may or may not |
| 18 | 24 |
// support on its own. See the comment on the exported |
| ... | ... |
@@ -129,7 +133,7 @@ func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, diff archive.Reader) (s |
| 129 | 129 |
GIDMaps: gdw.gidMaps} |
| 130 | 130 |
start := time.Now().UTC() |
| 131 | 131 |
logrus.Debugf("Start untar layer")
|
| 132 |
- if size, err = chrootarchive.ApplyUncompressedLayer(layerFs, diff, options); err != nil {
|
|
| 132 |
+ if size, err = ApplyUncompressedLayer(layerFs, diff, options); err != nil {
|
|
| 133 | 133 |
return |
| 134 | 134 |
} |
| 135 | 135 |
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
| 136 | 136 |
deleted file mode 100644 |
| ... | ... |
@@ -1,31 +0,0 @@ |
| 1 |
-package graphdriver |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io" |
|
| 5 |
- |
|
| 6 |
- "github.com/docker/docker/image" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// NOTE: These interfaces are used for implementing specific features of the Windows |
|
| 10 |
-// graphdriver implementation. The current versions are a short-term solution and |
|
| 11 |
-// likely to change or possibly be eliminated, so avoid using them outside of the Windows |
|
| 12 |
-// graphdriver code. |
|
| 13 |
- |
|
| 14 |
-// ImageRestorer interface allows the implementer to add a custom image to |
|
| 15 |
-// the graph and tagstore. |
|
| 16 |
-type ImageRestorer interface {
|
|
| 17 |
- RestoreCustomImages(tagger Tagger, recorder Recorder) ([]string, error) |
|
| 18 |
-} |
|
| 19 |
- |
|
| 20 |
-// Tagger is an interface that exposes the TagStore.Tag function without needing |
|
| 21 |
-// to import graph. |
|
| 22 |
-type Tagger interface {
|
|
| 23 |
- Tag(repoName, tag, imageName string, force bool) error |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// Recorder is an interface that exposes the Graph.Register and Graph.Exists |
|
| 27 |
-// functions without needing to import graph. |
|
| 28 |
-type Recorder interface {
|
|
| 29 |
- Exists(id string) bool |
|
| 30 |
- Register(img image.Descriptor, layerData io.Reader) error |
|
| 31 |
-} |
| ... | ... |
@@ -1,5 +1,3 @@ |
| 1 |
-// +build daemon |
|
| 2 |
- |
|
| 3 | 1 |
package vfs |
| 4 | 2 |
|
| 5 | 3 |
import ( |
| ... | ... |
@@ -14,6 +12,11 @@ import ( |
| 14 | 14 |
"github.com/opencontainers/runc/libcontainer/label" |
| 15 | 15 |
) |
| 16 | 16 |
|
| 17 |
+var ( |
|
| 18 |
+ // CopyWithTar defines the copy method to use. |
|
| 19 |
+ CopyWithTar = chrootarchive.CopyWithTar |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 17 | 22 |
func init() {
|
| 18 | 23 |
graphdriver.Register("vfs", Init)
|
| 19 | 24 |
} |
| ... | ... |
@@ -89,7 +92,7 @@ func (d *Driver) Create(id, parent, mountLabel string) error {
|
| 89 | 89 |
if err != nil {
|
| 90 | 90 |
return fmt.Errorf("%s: %s", parent, err)
|
| 91 | 91 |
} |
| 92 |
- if err := chrootarchive.CopyWithTar(parentDir, dir); err != nil {
|
|
| 92 |
+ if err := CopyWithTar(parentDir, dir); err != nil {
|
|
| 93 | 93 |
return err |
| 94 | 94 |
} |
| 95 | 95 |
return nil |
| ... | ... |
@@ -6,10 +6,10 @@ import ( |
| 6 | 6 |
"crypto/sha512" |
| 7 | 7 |
"encoding/json" |
| 8 | 8 |
"fmt" |
| 9 |
+ "io" |
|
| 9 | 10 |
"io/ioutil" |
| 10 | 11 |
"os" |
| 11 | 12 |
"path/filepath" |
| 12 |
- "runtime" |
|
| 13 | 13 |
"strconv" |
| 14 | 14 |
"strings" |
| 15 | 15 |
"sync" |
| ... | ... |
@@ -17,8 +17,6 @@ import ( |
| 17 | 17 |
|
| 18 | 18 |
"github.com/Sirupsen/logrus" |
| 19 | 19 |
"github.com/docker/docker/daemon/graphdriver" |
| 20 |
- "github.com/docker/docker/dockerversion" |
|
| 21 |
- "github.com/docker/docker/image" |
|
| 22 | 20 |
"github.com/docker/docker/pkg/archive" |
| 23 | 21 |
"github.com/docker/docker/pkg/chrootarchive" |
| 24 | 22 |
"github.com/docker/docker/pkg/idtools" |
| ... | ... |
@@ -40,26 +38,6 @@ const ( |
| 40 | 40 |
filterDriver |
| 41 | 41 |
) |
| 42 | 42 |
|
| 43 |
-// CustomImageDescriptor is an image descriptor for use by RestoreCustomImages |
|
| 44 |
-type customImageDescriptor struct {
|
|
| 45 |
- img *image.Image |
|
| 46 |
-} |
|
| 47 |
- |
|
| 48 |
-// ID returns the image ID specified in the image structure. |
|
| 49 |
-func (img customImageDescriptor) ID() string {
|
|
| 50 |
- return img.img.ID |
|
| 51 |
-} |
|
| 52 |
- |
|
| 53 |
-// Parent returns the parent ID - in this case, none |
|
| 54 |
-func (img customImageDescriptor) Parent() string {
|
|
| 55 |
- return "" |
|
| 56 |
-} |
|
| 57 |
- |
|
| 58 |
-// MarshalConfig renders the image structure into JSON. |
|
| 59 |
-func (img customImageDescriptor) MarshalConfig() ([]byte, error) {
|
|
| 60 |
- return json.Marshal(img.img) |
|
| 61 |
-} |
|
| 62 |
- |
|
| 63 | 43 |
// Driver represents a windows graph driver. |
| 64 | 44 |
type Driver struct {
|
| 65 | 45 |
// info stores the shim driver information |
| ... | ... |
@@ -195,7 +173,7 @@ func (d *Driver) Remove(id string) error {
|
| 195 | 195 |
if err != nil {
|
| 196 | 196 |
return err |
| 197 | 197 |
} |
| 198 |
- |
|
| 198 |
+ os.RemoveAll(filepath.Join(d.info.HomeDir, "sysfile-backups", rID)) // ok to fail |
|
| 199 | 199 |
return hcsshim.DestroyLayer(d.info, rID) |
| 200 | 200 |
} |
| 201 | 201 |
|
| ... | ... |
@@ -402,22 +380,27 @@ func (d *Driver) DiffSize(id, parent string) (size int64, err error) {
|
| 402 | 402 |
return archive.ChangesSize(layerFs, changes), nil |
| 403 | 403 |
} |
| 404 | 404 |
|
| 405 |
-// RestoreCustomImages adds any auto-detected OS specific images to the tag and graph store. |
|
| 406 |
-func (d *Driver) RestoreCustomImages(tagger graphdriver.Tagger, recorder graphdriver.Recorder) (imageIDs []string, err error) {
|
|
| 405 |
+// CustomImageInfo is the object returned by the driver describing the base |
|
| 406 |
+// image. |
|
| 407 |
+type CustomImageInfo struct {
|
|
| 408 |
+ ID string |
|
| 409 |
+ Name string |
|
| 410 |
+ Version string |
|
| 411 |
+ Path string |
|
| 412 |
+ Size int64 |
|
| 413 |
+ CreatedTime time.Time |
|
| 414 |
+} |
|
| 415 |
+ |
|
| 416 |
+// GetCustomImageInfos returns the image infos for window specific |
|
| 417 |
+// base images which should always be present. |
|
| 418 |
+func (d *Driver) GetCustomImageInfos() ([]CustomImageInfo, error) {
|
|
| 407 | 419 |
strData, err := hcsshim.GetSharedBaseImages() |
| 408 | 420 |
if err != nil {
|
| 409 | 421 |
return nil, fmt.Errorf("Failed to restore base images: %s", err)
|
| 410 | 422 |
} |
| 411 | 423 |
|
| 412 |
- type customImageInfo struct {
|
|
| 413 |
- Name string |
|
| 414 |
- Version string |
|
| 415 |
- Path string |
|
| 416 |
- Size int64 |
|
| 417 |
- CreatedTime time.Time |
|
| 418 |
- } |
|
| 419 | 424 |
type customImageInfoList struct {
|
| 420 |
- Images []customImageInfo |
|
| 425 |
+ Images []CustomImageInfo |
|
| 421 | 426 |
} |
| 422 | 427 |
|
| 423 | 428 |
var infoData customImageInfoList |
| ... | ... |
@@ -428,43 +411,28 @@ func (d *Driver) RestoreCustomImages(tagger graphdriver.Tagger, recorder graphdr |
| 428 | 428 |
return nil, err |
| 429 | 429 |
} |
| 430 | 430 |
|
| 431 |
+ var images []CustomImageInfo |
|
| 432 |
+ |
|
| 431 | 433 |
for _, imageData := range infoData.Images {
|
| 432 |
- _, folderName := filepath.Split(imageData.Path) |
|
| 434 |
+ folderName := filepath.Base(imageData.Path) |
|
| 433 | 435 |
|
| 434 | 436 |
// Use crypto hash of the foldername to generate a docker style id. |
| 435 | 437 |
h := sha512.Sum384([]byte(folderName)) |
| 436 | 438 |
id := fmt.Sprintf("%x", h[:32])
|
| 437 | 439 |
|
| 438 |
- if !recorder.Exists(id) {
|
|
| 439 |
- // Register the image. |
|
| 440 |
- img := &image.Image{
|
|
| 441 |
- ID: id, |
|
| 442 |
- Created: imageData.CreatedTime, |
|
| 443 |
- DockerVersion: dockerversion.Version, |
|
| 444 |
- Architecture: runtime.GOARCH, |
|
| 445 |
- OS: runtime.GOOS, |
|
| 446 |
- Size: imageData.Size, |
|
| 447 |
- } |
|
| 448 |
- |
|
| 449 |
- if err := recorder.Register(customImageDescriptor{img}, nil); err != nil {
|
|
| 450 |
- return nil, err |
|
| 451 |
- } |
|
| 452 |
- |
|
| 453 |
- // Create tags for the new image. |
|
| 454 |
- if err := tagger.Tag(strings.ToLower(imageData.Name), imageData.Version, img.ID, true); err != nil {
|
|
| 455 |
- return nil, err |
|
| 456 |
- } |
|
| 457 |
- |
|
| 458 |
- // Create the alternate ID file. |
|
| 459 |
- if err := d.setID(img.ID, folderName); err != nil {
|
|
| 460 |
- return nil, err |
|
| 461 |
- } |
|
| 462 |
- |
|
| 463 |
- imageIDs = append(imageIDs, img.ID) |
|
| 440 |
+ if err := d.Create(id, "", ""); err != nil {
|
|
| 441 |
+ return nil, err |
|
| 464 | 442 |
} |
| 443 |
+ // Create the alternate ID file. |
|
| 444 |
+ if err := d.setID(id, folderName); err != nil {
|
|
| 445 |
+ return nil, err |
|
| 446 |
+ } |
|
| 447 |
+ |
|
| 448 |
+ imageData.ID = id |
|
| 449 |
+ images = append(images, imageData) |
|
| 465 | 450 |
} |
| 466 | 451 |
|
| 467 |
- return imageIDs, nil |
|
| 452 |
+ return images, nil |
|
| 468 | 453 |
} |
| 469 | 454 |
|
| 470 | 455 |
// GetMetadata returns custom driver information. |
| ... | ... |
@@ -533,6 +501,10 @@ func (d *Driver) importLayer(id string, layerData archive.Reader, parentLayerPat |
| 533 | 533 |
if size, err = chrootarchive.ApplyLayer(tempFolder, layerData); err != nil {
|
| 534 | 534 |
return |
| 535 | 535 |
} |
| 536 |
+ err = copySysFiles(tempFolder, filepath.Join(d.info.HomeDir, "sysfile-backups", id)) |
|
| 537 |
+ if err != nil {
|
|
| 538 |
+ return |
|
| 539 |
+ } |
|
| 536 | 540 |
logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds())
|
| 537 | 541 |
|
| 538 | 542 |
if err = hcsshim.ImportLayer(d.info, id, tempFolder, parentLayerPaths); err != nil {
|
| ... | ... |
@@ -596,3 +568,103 @@ func (d *Driver) setLayerChain(id string, chain []string) error {
|
| 596 | 596 |
|
| 597 | 597 |
return nil |
| 598 | 598 |
} |
| 599 |
+ |
|
| 600 |
+// DiffPath returns a directory that contains files needed to construct layer diff. |
|
| 601 |
+func (d *Driver) DiffPath(id string) (path string, release func() error, err error) {
|
|
| 602 |
+ id, err = d.resolveID(id) |
|
| 603 |
+ if err != nil {
|
|
| 604 |
+ return |
|
| 605 |
+ } |
|
| 606 |
+ |
|
| 607 |
+ // Getting the layer paths must be done outside of the lock. |
|
| 608 |
+ layerChain, err := d.getLayerChain(id) |
|
| 609 |
+ if err != nil {
|
|
| 610 |
+ return |
|
| 611 |
+ } |
|
| 612 |
+ |
|
| 613 |
+ layerFolder := d.dir(id) |
|
| 614 |
+ tempFolder := layerFolder + "-" + strconv.FormatUint(uint64(random.Rand.Uint32()), 10) |
|
| 615 |
+ if err = os.MkdirAll(tempFolder, 0755); err != nil {
|
|
| 616 |
+ logrus.Errorf("Could not create %s %s", tempFolder, err)
|
|
| 617 |
+ return |
|
| 618 |
+ } |
|
| 619 |
+ |
|
| 620 |
+ defer func() {
|
|
| 621 |
+ if err != nil {
|
|
| 622 |
+ _, folderName := filepath.Split(tempFolder) |
|
| 623 |
+ if err2 := hcsshim.DestroyLayer(d.info, folderName); err2 != nil {
|
|
| 624 |
+ logrus.Warnf("Couldn't clean-up tempFolder: %s %s", tempFolder, err2)
|
|
| 625 |
+ } |
|
| 626 |
+ } |
|
| 627 |
+ }() |
|
| 628 |
+ |
|
| 629 |
+ if err = hcsshim.ExportLayer(d.info, id, tempFolder, layerChain); err != nil {
|
|
| 630 |
+ return |
|
| 631 |
+ } |
|
| 632 |
+ |
|
| 633 |
+ err = copySysFiles(filepath.Join(d.info.HomeDir, "sysfile-backups", id), tempFolder) |
|
| 634 |
+ if err != nil {
|
|
| 635 |
+ return |
|
| 636 |
+ } |
|
| 637 |
+ |
|
| 638 |
+ return tempFolder, func() error {
|
|
| 639 |
+ // TODO: activate layers and release here? |
|
| 640 |
+ _, folderName := filepath.Split(tempFolder) |
|
| 641 |
+ return hcsshim.DestroyLayer(d.info, folderName) |
|
| 642 |
+ }, nil |
|
| 643 |
+} |
|
| 644 |
+ |
|
| 645 |
+var sysFileWhiteList = []string{
|
|
| 646 |
+ "Hives\\*", |
|
| 647 |
+ "Files\\BOOTNXT", |
|
| 648 |
+ "tombstones.txt", |
|
| 649 |
+} |
|
| 650 |
+ |
|
| 651 |
+// note this only handles files |
|
| 652 |
+func copySysFiles(src string, dest string) error {
|
|
| 653 |
+ if err := os.MkdirAll(dest, 0700); err != nil {
|
|
| 654 |
+ return err |
|
| 655 |
+ } |
|
| 656 |
+ return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
|
| 657 |
+ rel, err := filepath.Rel(src, path) |
|
| 658 |
+ if err != nil {
|
|
| 659 |
+ return err |
|
| 660 |
+ } |
|
| 661 |
+ for _, sysfile := range sysFileWhiteList {
|
|
| 662 |
+ if matches, err := filepath.Match(sysfile, rel); err != nil || !matches {
|
|
| 663 |
+ continue |
|
| 664 |
+ } |
|
| 665 |
+ |
|
| 666 |
+ fi, err := os.Lstat(path) |
|
| 667 |
+ if err != nil {
|
|
| 668 |
+ return err |
|
| 669 |
+ } |
|
| 670 |
+ |
|
| 671 |
+ if !fi.Mode().IsRegular() {
|
|
| 672 |
+ continue |
|
| 673 |
+ } |
|
| 674 |
+ |
|
| 675 |
+ targetPath := filepath.Join(dest, rel) |
|
| 676 |
+ if err = os.MkdirAll(filepath.Dir(targetPath), 0700); err != nil {
|
|
| 677 |
+ return err |
|
| 678 |
+ } |
|
| 679 |
+ |
|
| 680 |
+ in, err := os.Open(path) |
|
| 681 |
+ if err != nil {
|
|
| 682 |
+ return err |
|
| 683 |
+ } |
|
| 684 |
+ out, err := os.Create(targetPath) |
|
| 685 |
+ if err != nil {
|
|
| 686 |
+ in.Close() |
|
| 687 |
+ return err |
|
| 688 |
+ } |
|
| 689 |
+ _, err = io.Copy(out, in) |
|
| 690 |
+ in.Close() |
|
| 691 |
+ out.Close() |
|
| 692 |
+ if err != nil {
|
|
| 693 |
+ return err |
|
| 694 |
+ } |
|
| 695 |
+ } |
|
| 696 |
+ return nil |
|
| 697 |
+ }) |
|
| 698 |
+} |
| ... | ... |
@@ -4,13 +4,12 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"strings" |
| 6 | 6 |
|
| 7 |
+ "github.com/docker/distribution/reference" |
|
| 7 | 8 |
"github.com/docker/docker/api/types" |
| 8 | 9 |
derr "github.com/docker/docker/errors" |
| 9 |
- "github.com/docker/docker/graph/tags" |
|
| 10 | 10 |
"github.com/docker/docker/image" |
| 11 |
- "github.com/docker/docker/pkg/parsers" |
|
| 12 | 11 |
"github.com/docker/docker/pkg/stringid" |
| 13 |
- "github.com/docker/docker/utils" |
|
| 12 |
+ tagpkg "github.com/docker/docker/tag" |
|
| 14 | 13 |
) |
| 15 | 14 |
|
| 16 | 15 |
// ImageDelete deletes the image referenced by the given imageRef from this |
| ... | ... |
@@ -53,39 +52,46 @@ import ( |
| 53 | 53 |
func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error) {
|
| 54 | 54 |
records := []types.ImageDelete{}
|
| 55 | 55 |
|
| 56 |
- img, err := daemon.repositories.LookupImage(imageRef) |
|
| 56 |
+ imgID, err := daemon.GetImageID(imageRef) |
|
| 57 | 57 |
if err != nil {
|
| 58 |
- return nil, daemon.graphNotExistToErrcode(imageRef, err) |
|
| 58 |
+ return nil, daemon.imageNotExistToErrcode(err) |
|
| 59 | 59 |
} |
| 60 | 60 |
|
| 61 |
+ repoRefs := daemon.tagStore.References(imgID) |
|
| 62 |
+ |
|
| 61 | 63 |
var removedRepositoryRef bool |
| 62 |
- if !isImageIDPrefix(img.ID, imageRef) {
|
|
| 64 |
+ if !isImageIDPrefix(imgID.String(), imageRef) {
|
|
| 63 | 65 |
// A repository reference was given and should be removed |
| 64 | 66 |
// first. We can only remove this reference if either force is |
| 65 | 67 |
// true, there are multiple repository references to this |
| 66 | 68 |
// image, or there are no containers using the given reference. |
| 67 |
- if !(force || daemon.imageHasMultipleRepositoryReferences(img.ID)) {
|
|
| 68 |
- if container := daemon.getContainerUsingImage(img.ID); container != nil {
|
|
| 69 |
+ if !(force || len(repoRefs) > 1) {
|
|
| 70 |
+ if container := daemon.getContainerUsingImage(imgID); container != nil {
|
|
| 69 | 71 |
// If we removed the repository reference then |
| 70 | 72 |
// this image would remain "dangling" and since |
| 71 | 73 |
// we really want to avoid that the client must |
| 72 | 74 |
// explicitly force its removal. |
| 73 |
- return nil, derr.ErrorCodeImgDelUsed.WithArgs(imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(img.ID)) |
|
| 75 |
+ return nil, derr.ErrorCodeImgDelUsed.WithArgs(imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(imgID.String())) |
|
| 74 | 76 |
} |
| 75 | 77 |
} |
| 76 | 78 |
|
| 77 |
- parsedRef, err := daemon.removeImageRef(imageRef) |
|
| 79 |
+ parsedRef, err := reference.ParseNamed(imageRef) |
|
| 78 | 80 |
if err != nil {
|
| 79 | 81 |
return nil, err |
| 80 | 82 |
} |
| 81 | 83 |
|
| 82 |
- untaggedRecord := types.ImageDelete{Untagged: parsedRef}
|
|
| 84 |
+ parsedRef, err = daemon.removeImageRef(parsedRef) |
|
| 85 |
+ if err != nil {
|
|
| 86 |
+ return nil, err |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
|
| 83 | 90 |
|
| 84 |
- daemon.EventsService.Log("untag", img.ID, "")
|
|
| 91 |
+ daemon.EventsService.Log("untag", imgID.String(), "")
|
|
| 85 | 92 |
records = append(records, untaggedRecord) |
| 86 | 93 |
|
| 87 | 94 |
// If has remaining references then untag finishes the remove |
| 88 |
- if daemon.repositories.HasReferences(img) {
|
|
| 95 |
+ if len(repoRefs) > 1 {
|
|
| 89 | 96 |
return records, nil |
| 90 | 97 |
} |
| 91 | 98 |
|
| ... | ... |
@@ -95,38 +101,39 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I |
| 95 | 95 |
// repository reference to the image then we will want to |
| 96 | 96 |
// remove that reference. |
| 97 | 97 |
// FIXME: Is this the behavior we want? |
| 98 |
- repoRefs := daemon.repositories.ByID()[img.ID] |
|
| 99 | 98 |
if len(repoRefs) == 1 {
|
| 100 | 99 |
parsedRef, err := daemon.removeImageRef(repoRefs[0]) |
| 101 | 100 |
if err != nil {
|
| 102 | 101 |
return nil, err |
| 103 | 102 |
} |
| 104 | 103 |
|
| 105 |
- untaggedRecord := types.ImageDelete{Untagged: parsedRef}
|
|
| 104 |
+ untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
|
| 106 | 105 |
|
| 107 |
- daemon.EventsService.Log("untag", img.ID, "")
|
|
| 106 |
+ daemon.EventsService.Log("untag", imgID.String(), "")
|
|
| 108 | 107 |
records = append(records, untaggedRecord) |
| 109 | 108 |
} |
| 110 | 109 |
} |
| 111 | 110 |
|
| 112 |
- return records, daemon.imageDeleteHelper(img, &records, force, prune, removedRepositoryRef) |
|
| 111 |
+ return records, daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef) |
|
| 113 | 112 |
} |
| 114 | 113 |
|
| 115 | 114 |
// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the |
| 116 | 115 |
// given imageID. |
| 117 | 116 |
func isImageIDPrefix(imageID, possiblePrefix string) bool {
|
| 118 |
- return strings.HasPrefix(imageID, possiblePrefix) |
|
| 119 |
-} |
|
| 117 |
+ if strings.HasPrefix(imageID, possiblePrefix) {
|
|
| 118 |
+ return true |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ if i := strings.IndexRune(imageID, ':'); i >= 0 {
|
|
| 122 |
+ return strings.HasPrefix(imageID[i+1:], possiblePrefix) |
|
| 123 |
+ } |
|
| 120 | 124 |
|
| 121 |
-// imageHasMultipleRepositoryReferences returns whether there are multiple |
|
| 122 |
-// repository references to the given imageID. |
|
| 123 |
-func (daemon *Daemon) imageHasMultipleRepositoryReferences(imageID string) bool {
|
|
| 124 |
- return len(daemon.repositories.ByID()[imageID]) > 1 |
|
| 125 |
+ return false |
|
| 125 | 126 |
} |
| 126 | 127 |
|
| 127 | 128 |
// getContainerUsingImage returns a container that was created using the given |
| 128 | 129 |
// imageID. Returns nil if there is no such container. |
| 129 |
-func (daemon *Daemon) getContainerUsingImage(imageID string) *Container {
|
|
| 130 |
+func (daemon *Daemon) getContainerUsingImage(imageID image.ID) *Container {
|
|
| 130 | 131 |
for _, container := range daemon.List() {
|
| 131 | 132 |
if container.ImageID == imageID {
|
| 132 | 133 |
return container |
| ... | ... |
@@ -141,18 +148,24 @@ func (daemon *Daemon) getContainerUsingImage(imageID string) *Container {
|
| 141 | 141 |
// repositoryRef must not be an image ID but a repository name followed by an |
| 142 | 142 |
// optional tag or digest reference. If tag or digest is omitted, the default |
| 143 | 143 |
// tag is used. Returns the resolved image reference and an error. |
| 144 |
-func (daemon *Daemon) removeImageRef(repositoryRef string) (string, error) {
|
|
| 145 |
- repository, ref := parsers.ParseRepositoryTag(repositoryRef) |
|
| 146 |
- if ref == "" {
|
|
| 147 |
- ref = tags.DefaultTag |
|
| 144 |
+func (daemon *Daemon) removeImageRef(ref reference.Named) (reference.Named, error) {
|
|
| 145 |
+ switch ref.(type) {
|
|
| 146 |
+ case reference.Tagged: |
|
| 147 |
+ case reference.Digested: |
|
| 148 |
+ default: |
|
| 149 |
+ var err error |
|
| 150 |
+ ref, err = reference.WithTag(ref, tagpkg.DefaultTag) |
|
| 151 |
+ if err != nil {
|
|
| 152 |
+ return nil, err |
|
| 153 |
+ } |
|
| 148 | 154 |
} |
| 149 | 155 |
|
| 150 | 156 |
// Ignore the boolean value returned, as far as we're concerned, this |
| 151 | 157 |
// is an idempotent operation and it's okay if the reference didn't |
| 152 | 158 |
// exist in the first place. |
| 153 |
- _, err := daemon.repositories.Delete(repository, ref) |
|
| 159 |
+ _, err := daemon.tagStore.Delete(ref) |
|
| 154 | 160 |
|
| 155 |
- return utils.ImageReference(repository, ref), err |
|
| 161 |
+ return ref, err |
|
| 156 | 162 |
} |
| 157 | 163 |
|
| 158 | 164 |
// removeAllReferencesToImageID attempts to remove every reference to the given |
| ... | ... |
@@ -160,8 +173,8 @@ func (daemon *Daemon) removeImageRef(repositoryRef string) (string, error) {
|
| 160 | 160 |
// on the first encountered error. Removed references are logged to this |
| 161 | 161 |
// daemon's event service. An "Untagged" types.ImageDelete is added to the |
| 162 | 162 |
// given list of records. |
| 163 |
-func (daemon *Daemon) removeAllReferencesToImageID(imgID string, records *[]types.ImageDelete) error {
|
|
| 164 |
- imageRefs := daemon.repositories.ByID()[imgID] |
|
| 163 |
+func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, records *[]types.ImageDelete) error {
|
|
| 164 |
+ imageRefs := daemon.tagStore.References(imgID) |
|
| 165 | 165 |
|
| 166 | 166 |
for _, imageRef := range imageRefs {
|
| 167 | 167 |
parsedRef, err := daemon.removeImageRef(imageRef) |
| ... | ... |
@@ -169,9 +182,9 @@ func (daemon *Daemon) removeAllReferencesToImageID(imgID string, records *[]type |
| 169 | 169 |
return err |
| 170 | 170 |
} |
| 171 | 171 |
|
| 172 |
- untaggedRecord := types.ImageDelete{Untagged: parsedRef}
|
|
| 172 |
+ untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
|
| 173 | 173 |
|
| 174 |
- daemon.EventsService.Log("untag", imgID, "")
|
|
| 174 |
+ daemon.EventsService.Log("untag", imgID.String(), "")
|
|
| 175 | 175 |
*records = append(*records, untaggedRecord) |
| 176 | 176 |
} |
| 177 | 177 |
|
| ... | ... |
@@ -182,7 +195,7 @@ func (daemon *Daemon) removeAllReferencesToImageID(imgID string, records *[]type |
| 182 | 182 |
// Implements the error interface. |
| 183 | 183 |
type imageDeleteConflict struct {
|
| 184 | 184 |
hard bool |
| 185 |
- imgID string |
|
| 185 |
+ imgID image.ID |
|
| 186 | 186 |
message string |
| 187 | 187 |
} |
| 188 | 188 |
|
| ... | ... |
@@ -194,7 +207,7 @@ func (idc *imageDeleteConflict) Error() string {
|
| 194 | 194 |
forceMsg = "must be forced" |
| 195 | 195 |
} |
| 196 | 196 |
|
| 197 |
- return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID), forceMsg, idc.message)
|
|
| 197 |
+ return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID.String()), forceMsg, idc.message)
|
|
| 198 | 198 |
} |
| 199 | 199 |
|
| 200 | 200 |
// imageDeleteHelper attempts to delete the given image from this daemon. If |
| ... | ... |
@@ -208,11 +221,11 @@ func (idc *imageDeleteConflict) Error() string {
|
| 208 | 208 |
// conflict is encountered, it will be returned immediately without deleting |
| 209 | 209 |
// the image. If quiet is true, any encountered conflicts will be ignored and |
| 210 | 210 |
// the function will return nil immediately without deleting the image. |
| 211 |
-func (daemon *Daemon) imageDeleteHelper(img *image.Image, records *[]types.ImageDelete, force, prune, quiet bool) error {
|
|
| 211 |
+func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDelete, force, prune, quiet bool) error {
|
|
| 212 | 212 |
// First, determine if this image has any conflicts. Ignore soft conflicts |
| 213 | 213 |
// if force is true. |
| 214 |
- if conflict := daemon.checkImageDeleteConflict(img, force); conflict != nil {
|
|
| 215 |
- if quiet && !daemon.imageIsDangling(img) {
|
|
| 214 |
+ if conflict := daemon.checkImageDeleteConflict(imgID, force); conflict != nil {
|
|
| 215 |
+ if quiet && !daemon.imageIsDangling(imgID) {
|
|
| 216 | 216 |
// Ignore conflicts UNLESS the image is "dangling" in |
| 217 | 217 |
// which case we want the user to know. |
| 218 | 218 |
return nil |
| ... | ... |
@@ -223,33 +236,38 @@ func (daemon *Daemon) imageDeleteHelper(img *image.Image, records *[]types.Image |
| 223 | 223 |
return conflict |
| 224 | 224 |
} |
| 225 | 225 |
|
| 226 |
+ parent, err := daemon.imageStore.GetParent(imgID) |
|
| 227 |
+ if err != nil {
|
|
| 228 |
+ // There may be no parent |
|
| 229 |
+ parent = "" |
|
| 230 |
+ } |
|
| 231 |
+ |
|
| 226 | 232 |
// Delete all repository tag/digest references to this image. |
| 227 |
- if err := daemon.removeAllReferencesToImageID(img.ID, records); err != nil {
|
|
| 233 |
+ if err := daemon.removeAllReferencesToImageID(imgID, records); err != nil {
|
|
| 228 | 234 |
return err |
| 229 | 235 |
} |
| 230 | 236 |
|
| 231 |
- if err := daemon.Graph().Delete(img.ID); err != nil {
|
|
| 237 |
+ removedLayers, err := daemon.imageStore.Delete(imgID) |
|
| 238 |
+ if err != nil {
|
|
| 232 | 239 |
return err |
| 233 | 240 |
} |
| 234 | 241 |
|
| 235 |
- daemon.EventsService.Log("delete", img.ID, "")
|
|
| 236 |
- *records = append(*records, types.ImageDelete{Deleted: img.ID})
|
|
| 242 |
+ daemon.EventsService.Log("delete", imgID.String(), "")
|
|
| 243 |
+ *records = append(*records, types.ImageDelete{Deleted: imgID.String()})
|
|
| 244 |
+ for _, removedLayer := range removedLayers {
|
|
| 245 |
+ *records = append(*records, types.ImageDelete{Deleted: removedLayer.ChainID.String()})
|
|
| 246 |
+ } |
|
| 237 | 247 |
|
| 238 |
- if !prune || img.Parent == "" {
|
|
| 248 |
+ if !prune || parent == "" {
|
|
| 239 | 249 |
return nil |
| 240 | 250 |
} |
| 241 | 251 |
|
| 242 | 252 |
// We need to prune the parent image. This means delete it if there are |
| 243 | 253 |
// no tags/digests referencing it and there are no containers using it ( |
| 244 | 254 |
// either running or stopped). |
| 245 |
- parentImg, err := daemon.Graph().Get(img.Parent) |
|
| 246 |
- if err != nil {
|
|
| 247 |
- return derr.ErrorCodeImgNoParent.WithArgs(err) |
|
| 248 |
- } |
|
| 249 |
- |
|
| 250 | 255 |
// Do not force prunings, but do so quietly (stopping on any encountered |
| 251 | 256 |
// conflicts). |
| 252 |
- return daemon.imageDeleteHelper(parentImg, records, false, true, true) |
|
| 257 |
+ return daemon.imageDeleteHelper(parent, records, false, true, true) |
|
| 253 | 258 |
} |
| 254 | 259 |
|
| 255 | 260 |
// checkImageDeleteConflict determines whether there are any conflicts |
| ... | ... |
@@ -258,9 +276,9 @@ func (daemon *Daemon) imageDeleteHelper(img *image.Image, records *[]types.Image |
| 258 | 258 |
// using the image. A soft conflict is any tags/digest referencing the given |
| 259 | 259 |
// image or any stopped container using the image. If ignoreSoftConflicts is |
| 260 | 260 |
// true, this function will not check for soft conflict conditions. |
| 261 |
-func (daemon *Daemon) checkImageDeleteConflict(img *image.Image, ignoreSoftConflicts bool) *imageDeleteConflict {
|
|
| 261 |
+func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, ignoreSoftConflicts bool) *imageDeleteConflict {
|
|
| 262 | 262 |
// Check for hard conflicts first. |
| 263 |
- if conflict := daemon.checkImageDeleteHardConflict(img); conflict != nil {
|
|
| 263 |
+ if conflict := daemon.checkImageDeleteHardConflict(imgID); conflict != nil {
|
|
| 264 | 264 |
return conflict |
| 265 | 265 |
} |
| 266 | 266 |
|
| ... | ... |
@@ -270,24 +288,15 @@ func (daemon *Daemon) checkImageDeleteConflict(img *image.Image, ignoreSoftConfl |
| 270 | 270 |
return nil |
| 271 | 271 |
} |
| 272 | 272 |
|
| 273 |
- return daemon.checkImageDeleteSoftConflict(img) |
|
| 273 |
+ return daemon.checkImageDeleteSoftConflict(imgID) |
|
| 274 | 274 |
} |
| 275 | 275 |
|
| 276 |
-func (daemon *Daemon) checkImageDeleteHardConflict(img *image.Image) *imageDeleteConflict {
|
|
| 277 |
- // Check if the image ID is being used by a pull or build. |
|
| 278 |
- if daemon.Graph().IsHeld(img.ID) {
|
|
| 279 |
- return &imageDeleteConflict{
|
|
| 280 |
- hard: true, |
|
| 281 |
- imgID: img.ID, |
|
| 282 |
- message: "image is held by an ongoing pull or build", |
|
| 283 |
- } |
|
| 284 |
- } |
|
| 285 |
- |
|
| 276 |
+func (daemon *Daemon) checkImageDeleteHardConflict(imgID image.ID) *imageDeleteConflict {
|
|
| 286 | 277 |
// Check if the image has any descendent images. |
| 287 |
- if daemon.Graph().HasChildren(img.ID) {
|
|
| 278 |
+ if len(daemon.imageStore.Children(imgID)) > 0 {
|
|
| 288 | 279 |
return &imageDeleteConflict{
|
| 289 | 280 |
hard: true, |
| 290 |
- imgID: img.ID, |
|
| 281 |
+ imgID: imgID, |
|
| 291 | 282 |
message: "image has dependent child images", |
| 292 | 283 |
} |
| 293 | 284 |
} |
| ... | ... |
@@ -299,9 +308,9 @@ func (daemon *Daemon) checkImageDeleteHardConflict(img *image.Image) *imageDelet |
| 299 | 299 |
continue |
| 300 | 300 |
} |
| 301 | 301 |
|
| 302 |
- if container.ImageID == img.ID {
|
|
| 302 |
+ if container.ImageID == imgID {
|
|
| 303 | 303 |
return &imageDeleteConflict{
|
| 304 |
- imgID: img.ID, |
|
| 304 |
+ imgID: imgID, |
|
| 305 | 305 |
hard: true, |
| 306 | 306 |
message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)),
|
| 307 | 307 |
} |
| ... | ... |
@@ -311,11 +320,11 @@ func (daemon *Daemon) checkImageDeleteHardConflict(img *image.Image) *imageDelet |
| 311 | 311 |
return nil |
| 312 | 312 |
} |
| 313 | 313 |
|
| 314 |
-func (daemon *Daemon) checkImageDeleteSoftConflict(img *image.Image) *imageDeleteConflict {
|
|
| 314 |
+func (daemon *Daemon) checkImageDeleteSoftConflict(imgID image.ID) *imageDeleteConflict {
|
|
| 315 | 315 |
// Check if any repository tags/digest reference this image. |
| 316 |
- if daemon.repositories.HasReferences(img) {
|
|
| 316 |
+ if len(daemon.tagStore.References(imgID)) > 0 {
|
|
| 317 | 317 |
return &imageDeleteConflict{
|
| 318 |
- imgID: img.ID, |
|
| 318 |
+ imgID: imgID, |
|
| 319 | 319 |
message: "image is referenced in one or more repositories", |
| 320 | 320 |
} |
| 321 | 321 |
} |
| ... | ... |
@@ -327,9 +336,9 @@ func (daemon *Daemon) checkImageDeleteSoftConflict(img *image.Image) *imageDelet |
| 327 | 327 |
continue |
| 328 | 328 |
} |
| 329 | 329 |
|
| 330 |
- if container.ImageID == img.ID {
|
|
| 330 |
+ if container.ImageID == imgID {
|
|
| 331 | 331 |
return &imageDeleteConflict{
|
| 332 |
- imgID: img.ID, |
|
| 332 |
+ imgID: imgID, |
|
| 333 | 333 |
message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)),
|
| 334 | 334 |
} |
| 335 | 335 |
} |
| ... | ... |
@@ -341,6 +350,6 @@ func (daemon *Daemon) checkImageDeleteSoftConflict(img *image.Image) *imageDelet |
| 341 | 341 |
// imageIsDangling returns whether the given image is "dangling" which means |
| 342 | 342 |
// that there are no repository references to the given image and it has no |
| 343 | 343 |
// child images. |
| 344 |
-func (daemon *Daemon) imageIsDangling(img *image.Image) bool {
|
|
| 345 |
- return !(daemon.repositories.HasReferences(img) || daemon.Graph().HasChildren(img.ID)) |
|
| 344 |
+func (daemon *Daemon) imageIsDangling(imgID image.ID) bool {
|
|
| 345 |
+ return !(len(daemon.tagStore.References(imgID)) > 0 || len(daemon.imageStore.Children(imgID)) > 0) |
|
| 346 | 346 |
} |
| 347 | 347 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,163 @@ |
| 0 |
+package daemon |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "path" |
|
| 5 |
+ "sort" |
|
| 6 |
+ "strings" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/distribution/reference" |
|
| 9 |
+ "github.com/docker/docker/api/types" |
|
| 10 |
+ "github.com/docker/docker/image" |
|
| 11 |
+ "github.com/docker/docker/layer" |
|
| 12 |
+ "github.com/docker/docker/pkg/parsers/filters" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+var acceptedImageFilterTags = map[string]struct{}{
|
|
| 16 |
+ "dangling": {},
|
|
| 17 |
+ "label": {},
|
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// byCreated is a temporary type used to sort a list of images by creation |
|
| 21 |
+// time. |
|
| 22 |
+type byCreated []*types.Image |
|
| 23 |
+ |
|
| 24 |
+func (r byCreated) Len() int { return len(r) }
|
|
| 25 |
+func (r byCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
|
| 26 |
+func (r byCreated) Less(i, j int) bool { return r[i].Created < r[j].Created }
|
|
| 27 |
+ |
|
| 28 |
+// Map returns a map of all images in the ImageStore |
|
| 29 |
+func (daemon *Daemon) Map() map[image.ID]*image.Image {
|
|
| 30 |
+ return daemon.imageStore.Map() |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+// Images returns a filtered list of images. filterArgs is a JSON-encoded set |
|
| 34 |
+// of filter arguments which will be interpreted by pkg/parsers/filters. |
|
| 35 |
+// filter is a shell glob string applied to repository names. The argument |
|
| 36 |
+// named all controls whether all images in the graph are filtered, or just |
|
| 37 |
+// the heads. |
|
| 38 |
+func (daemon *Daemon) Images(filterArgs, filter string, all bool) ([]*types.Image, error) {
|
|
| 39 |
+ var ( |
|
| 40 |
+ allImages map[image.ID]*image.Image |
|
| 41 |
+ err error |
|
| 42 |
+ danglingOnly = false |
|
| 43 |
+ ) |
|
| 44 |
+ |
|
| 45 |
+ imageFilters, err := filters.FromParam(filterArgs) |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ return nil, err |
|
| 48 |
+ } |
|
| 49 |
+ for name := range imageFilters {
|
|
| 50 |
+ if _, ok := acceptedImageFilterTags[name]; !ok {
|
|
| 51 |
+ return nil, fmt.Errorf("Invalid filter '%s'", name)
|
|
| 52 |
+ } |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ if i, ok := imageFilters["dangling"]; ok {
|
|
| 56 |
+ for _, value := range i {
|
|
| 57 |
+ if v := strings.ToLower(value); v == "true" {
|
|
| 58 |
+ danglingOnly = true |
|
| 59 |
+ } else if v != "false" {
|
|
| 60 |
+ return nil, fmt.Errorf("Invalid filter 'dangling=%s'", v)
|
|
| 61 |
+ } |
|
| 62 |
+ } |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ if danglingOnly {
|
|
| 66 |
+ allImages = daemon.imageStore.Heads() |
|
| 67 |
+ } else {
|
|
| 68 |
+ allImages = daemon.imageStore.Map() |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ images := []*types.Image{}
|
|
| 72 |
+ |
|
| 73 |
+ var filterTagged bool |
|
| 74 |
+ if filter != "" {
|
|
| 75 |
+ filterRef, err := reference.Parse(filter) |
|
| 76 |
+ if err == nil { // parse error means wildcard repo
|
|
| 77 |
+ if _, ok := filterRef.(reference.Tagged); ok {
|
|
| 78 |
+ filterTagged = true |
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ for id, img := range allImages {
|
|
| 84 |
+ if _, ok := imageFilters["label"]; ok {
|
|
| 85 |
+ if img.Config == nil {
|
|
| 86 |
+ // Very old image that do not have image.Config (or even labels) |
|
| 87 |
+ continue |
|
| 88 |
+ } |
|
| 89 |
+ // We are now sure image.Config is not nil |
|
| 90 |
+ if !imageFilters.MatchKVList("label", img.Config.Labels) {
|
|
| 91 |
+ continue |
|
| 92 |
+ } |
|
| 93 |
+ } |
|
| 94 |
+ |
|
| 95 |
+ layerID := img.RootFS.ChainID() |
|
| 96 |
+ var size int64 |
|
| 97 |
+ if layerID != "" {
|
|
| 98 |
+ l, err := daemon.layerStore.Get(layerID) |
|
| 99 |
+ if err != nil {
|
|
| 100 |
+ return nil, err |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ size, err = l.Size() |
|
| 104 |
+ layer.ReleaseAndLog(daemon.layerStore, l) |
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ return nil, err |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ newImage := newImage(img, size) |
|
| 111 |
+ |
|
| 112 |
+ for _, ref := range daemon.tagStore.References(id) {
|
|
| 113 |
+ if filter != "" { // filter by tag/repo name
|
|
| 114 |
+ if filterTagged { // filter by tag, require full ref match
|
|
| 115 |
+ if ref.String() != filter {
|
|
| 116 |
+ continue |
|
| 117 |
+ } |
|
| 118 |
+ } else if matched, err := path.Match(filter, ref.Name()); !matched || err != nil { // name only match, FIXME: docs say exact
|
|
| 119 |
+ continue |
|
| 120 |
+ } |
|
| 121 |
+ } |
|
| 122 |
+ if _, ok := ref.(reference.Digested); ok {
|
|
| 123 |
+ newImage.RepoDigests = append(newImage.RepoDigests, ref.String()) |
|
| 124 |
+ } |
|
| 125 |
+ if _, ok := ref.(reference.Tagged); ok {
|
|
| 126 |
+ newImage.RepoTags = append(newImage.RepoTags, ref.String()) |
|
| 127 |
+ } |
|
| 128 |
+ } |
|
| 129 |
+ if newImage.RepoDigests == nil && newImage.RepoTags == nil {
|
|
| 130 |
+ if all || len(daemon.imageStore.Children(id)) == 0 {
|
|
| 131 |
+ if filter != "" { // skip images with no references if filtering by tag
|
|
| 132 |
+ continue |
|
| 133 |
+ } |
|
| 134 |
+ newImage.RepoDigests = []string{"<none>@<none>"}
|
|
| 135 |
+ newImage.RepoTags = []string{"<none>:<none>"}
|
|
| 136 |
+ } else {
|
|
| 137 |
+ continue |
|
| 138 |
+ } |
|
| 139 |
+ } else if danglingOnly {
|
|
| 140 |
+ continue |
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 143 |
+ images = append(images, newImage) |
|
| 144 |
+ } |
|
| 145 |
+ |
|
| 146 |
+ sort.Sort(sort.Reverse(byCreated(images))) |
|
| 147 |
+ |
|
| 148 |
+ return images, nil |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+func newImage(image *image.Image, size int64) *types.Image {
|
|
| 152 |
+ newImage := new(types.Image) |
|
| 153 |
+ newImage.ParentID = image.Parent.String() |
|
| 154 |
+ newImage.ID = image.ID().String() |
|
| 155 |
+ newImage.Created = image.Created.Unix() |
|
| 156 |
+ newImage.Size = size |
|
| 157 |
+ newImage.VirtualSize = size |
|
| 158 |
+ if image.Config != nil {
|
|
| 159 |
+ newImage.Labels = image.Config.Labels |
|
| 160 |
+ } |
|
| 161 |
+ return newImage |
|
| 162 |
+} |
| 0 | 163 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,111 @@ |
| 0 |
+package daemon |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net/http" |
|
| 6 |
+ "net/url" |
|
| 7 |
+ "runtime" |
|
| 8 |
+ "time" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/distribution/reference" |
|
| 11 |
+ "github.com/docker/docker/dockerversion" |
|
| 12 |
+ "github.com/docker/docker/image" |
|
| 13 |
+ "github.com/docker/docker/layer" |
|
| 14 |
+ "github.com/docker/docker/pkg/httputils" |
|
| 15 |
+ "github.com/docker/docker/pkg/progressreader" |
|
| 16 |
+ "github.com/docker/docker/pkg/streamformatter" |
|
| 17 |
+ "github.com/docker/docker/runconfig" |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+// ImportImage imports an image, getting the archived layer data either from |
|
| 21 |
+// inConfig (if src is "-"), or from a URI specified in src. Progress output is |
|
| 22 |
+// written to outStream. Repository and tag names can optionally be given in |
|
| 23 |
+// the repo and tag arguments, respectively. |
|
| 24 |
+func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string, inConfig io.ReadCloser, outStream io.Writer, config *runconfig.Config) error {
|
|
| 25 |
+ var ( |
|
| 26 |
+ sf = streamformatter.NewJSONStreamFormatter() |
|
| 27 |
+ archive io.ReadCloser |
|
| 28 |
+ resp *http.Response |
|
| 29 |
+ ) |
|
| 30 |
+ |
|
| 31 |
+ if src == "-" {
|
|
| 32 |
+ archive = inConfig |
|
| 33 |
+ } else {
|
|
| 34 |
+ inConfig.Close() |
|
| 35 |
+ u, err := url.Parse(src) |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ return err |
|
| 38 |
+ } |
|
| 39 |
+ if u.Scheme == "" {
|
|
| 40 |
+ u.Scheme = "http" |
|
| 41 |
+ u.Host = src |
|
| 42 |
+ u.Path = "" |
|
| 43 |
+ } |
|
| 44 |
+ outStream.Write(sf.FormatStatus("", "Downloading from %s", u))
|
|
| 45 |
+ resp, err = httputils.Download(u.String()) |
|
| 46 |
+ if err != nil {
|
|
| 47 |
+ return err |
|
| 48 |
+ } |
|
| 49 |
+ progressReader := progressreader.New(progressreader.Config{
|
|
| 50 |
+ In: resp.Body, |
|
| 51 |
+ Out: outStream, |
|
| 52 |
+ Formatter: sf, |
|
| 53 |
+ Size: resp.ContentLength, |
|
| 54 |
+ NewLines: true, |
|
| 55 |
+ ID: "", |
|
| 56 |
+ Action: "Importing", |
|
| 57 |
+ }) |
|
| 58 |
+ archive = progressReader |
|
| 59 |
+ } |
|
| 60 |
+ |
|
| 61 |
+ defer archive.Close() |
|
| 62 |
+ if len(msg) == 0 {
|
|
| 63 |
+ msg = "Imported from " + src |
|
| 64 |
+ } |
|
| 65 |
+ // TODO: support windows baselayer? |
|
| 66 |
+ l, err := daemon.layerStore.Register(archive, "") |
|
| 67 |
+ if err != nil {
|
|
| 68 |
+ return err |
|
| 69 |
+ } |
|
| 70 |
+ defer layer.ReleaseAndLog(daemon.layerStore, l) |
|
| 71 |
+ |
|
| 72 |
+ created := time.Now().UTC() |
|
| 73 |
+ imgConfig, err := json.Marshal(&image.Image{
|
|
| 74 |
+ V1Image: image.V1Image{
|
|
| 75 |
+ DockerVersion: dockerversion.Version, |
|
| 76 |
+ Config: config, |
|
| 77 |
+ Architecture: runtime.GOARCH, |
|
| 78 |
+ OS: runtime.GOOS, |
|
| 79 |
+ Created: created, |
|
| 80 |
+ Comment: msg, |
|
| 81 |
+ }, |
|
| 82 |
+ RootFS: &image.RootFS{
|
|
| 83 |
+ Type: "layers", |
|
| 84 |
+ DiffIDs: []layer.DiffID{l.DiffID()},
|
|
| 85 |
+ }, |
|
| 86 |
+ History: []image.History{{
|
|
| 87 |
+ Created: created, |
|
| 88 |
+ Comment: msg, |
|
| 89 |
+ }}, |
|
| 90 |
+ }) |
|
| 91 |
+ if err != nil {
|
|
| 92 |
+ return err |
|
| 93 |
+ } |
|
| 94 |
+ |
|
| 95 |
+ id, err := daemon.imageStore.Create(imgConfig) |
|
| 96 |
+ if err != nil {
|
|
| 97 |
+ return err |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ // FIXME: connect with commit code and call tagstore directly |
|
| 101 |
+ if newRef != nil {
|
|
| 102 |
+ if err := daemon.TagImage(newRef, id.String(), true); err != nil {
|
|
| 103 |
+ return err |
|
| 104 |
+ } |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ outStream.Write(sf.FormatStatus("", id.String()))
|
|
| 108 |
+ daemon.EventsService.Log("import", id.String(), "")
|
|
| 109 |
+ return nil |
|
| 110 |
+} |
| ... | ... |
@@ -62,7 +62,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
| 62 | 62 |
v := &types.Info{
|
| 63 | 63 |
ID: daemon.ID, |
| 64 | 64 |
Containers: len(daemon.List()), |
| 65 |
- Images: len(daemon.Graph().Map()), |
|
| 65 |
+ Images: len(daemon.imageStore.Map()), |
|
| 66 | 66 |
Driver: daemon.GraphDriver().String(), |
| 67 | 67 |
DriverStatus: daemon.GraphDriver().Status(), |
| 68 | 68 |
Plugins: daemon.showPluginsInfo(), |
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
"github.com/docker/docker/api/types/versions/v1p20" |
| 9 | 9 |
"github.com/docker/docker/daemon/exec" |
| 10 | 10 |
"github.com/docker/docker/daemon/network" |
| 11 |
+ "github.com/docker/docker/layer" |
|
| 11 | 12 |
) |
| 12 | 13 |
|
| 13 | 14 |
// ContainerInspect returns low-level information about a |
| ... | ... |
@@ -124,7 +125,7 @@ func (daemon *Daemon) getInspectData(container *Container, size bool) (*types.Co |
| 124 | 124 |
Path: container.Path, |
| 125 | 125 |
Args: container.Args, |
| 126 | 126 |
State: containerState, |
| 127 |
- Image: container.ImageID, |
|
| 127 |
+ Image: container.ImageID.String(), |
|
| 128 | 128 |
LogPath: container.LogPath, |
| 129 | 129 |
Name: container.Name, |
| 130 | 130 |
RestartCount: container.RestartCount, |
| ... | ... |
@@ -149,7 +150,18 @@ func (daemon *Daemon) getInspectData(container *Container, size bool) (*types.Co |
| 149 | 149 |
contJSONBase = setPlatformSpecificContainerFields(container, contJSONBase) |
| 150 | 150 |
|
| 151 | 151 |
contJSONBase.GraphDriver.Name = container.Driver |
| 152 |
- graphDriverData, err := daemon.driver.GetMetadata(container.ID) |
|
| 152 |
+ |
|
| 153 |
+ image, err := daemon.imageStore.Get(container.ImageID) |
|
| 154 |
+ if err != nil {
|
|
| 155 |
+ return nil, err |
|
| 156 |
+ } |
|
| 157 |
+ l, err := daemon.layerStore.Get(image.RootFS.ChainID()) |
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ return nil, err |
|
| 160 |
+ } |
|
| 161 |
+ defer layer.ReleaseAndLog(daemon.layerStore, l) |
|
| 162 |
+ |
|
| 163 |
+ graphDriverData, err := l.Metadata() |
|
| 153 | 164 |
if err != nil {
|
| 154 | 165 |
return nil, err |
| 155 | 166 |
} |
| ... | ... |
@@ -9,7 +9,6 @@ import ( |
| 9 | 9 |
"github.com/Sirupsen/logrus" |
| 10 | 10 |
"github.com/docker/docker/api/types" |
| 11 | 11 |
derr "github.com/docker/docker/errors" |
| 12 |
- "github.com/docker/docker/graph" |
|
| 13 | 12 |
"github.com/docker/docker/image" |
| 14 | 13 |
"github.com/docker/docker/pkg/graphdb" |
| 15 | 14 |
"github.com/docker/docker/pkg/nat" |
| ... | ... |
@@ -66,7 +65,7 @@ type listContext struct {
|
| 66 | 66 |
// names is a list of container names to filter with |
| 67 | 67 |
names map[string][]string |
| 68 | 68 |
// images is a list of images to filter with |
| 69 |
- images map[string]bool |
|
| 69 |
+ images map[image.ID]bool |
|
| 70 | 70 |
// filters is a collection of arguments to filter with, specified by the user |
| 71 | 71 |
filters filters.Args |
| 72 | 72 |
// exitAllowed is a list of exit codes allowed to filter with |
| ... | ... |
@@ -176,25 +175,24 @@ func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error) |
| 176 | 176 |
} |
| 177 | 177 |
} |
| 178 | 178 |
|
| 179 |
- imagesFilter := map[string]bool{}
|
|
| 179 |
+ imagesFilter := map[image.ID]bool{}
|
|
| 180 | 180 |
var ancestorFilter bool |
| 181 | 181 |
if ancestors, ok := psFilters["ancestor"]; ok {
|
| 182 | 182 |
ancestorFilter = true |
| 183 |
- byParents := daemon.Graph().ByParent() |
|
| 184 | 183 |
// The idea is to walk the graph down the most "efficient" way. |
| 185 | 184 |
for _, ancestor := range ancestors {
|
| 186 | 185 |
// First, get the imageId of the ancestor filter (yay) |
| 187 |
- image, err := daemon.repositories.LookupImage(ancestor) |
|
| 186 |
+ id, err := daemon.GetImageID(ancestor) |
|
| 188 | 187 |
if err != nil {
|
| 189 | 188 |
logrus.Warnf("Error while looking up for image %v", ancestor)
|
| 190 | 189 |
continue |
| 191 | 190 |
} |
| 192 |
- if imagesFilter[ancestor] {
|
|
| 191 |
+ if imagesFilter[id] {
|
|
| 193 | 192 |
// Already seen this ancestor, skip it |
| 194 | 193 |
continue |
| 195 | 194 |
} |
| 196 | 195 |
// Then walk down the graph and put the imageIds in imagesFilter |
| 197 |
- populateImageFilterByParents(imagesFilter, image.ID, byParents) |
|
| 196 |
+ populateImageFilterByParents(imagesFilter, id, daemon.imageStore.Children) |
|
| 198 | 197 |
} |
| 199 | 198 |
} |
| 200 | 199 |
|
| ... | ... |
@@ -310,41 +308,29 @@ func includeContainerInList(container *Container, ctx *listContext) iterationAct |
| 310 | 310 |
return includeContainer |
| 311 | 311 |
} |
| 312 | 312 |
|
| 313 |
-func getImage(s *graph.TagStore, img, imgID string) (string, error) {
|
|
| 314 |
- // both Image and ImageID is actually ids, nothing to guess |
|
| 315 |
- if strings.HasPrefix(imgID, img) {
|
|
| 316 |
- return img, nil |
|
| 317 |
- } |
|
| 318 |
- id, err := s.GetID(img) |
|
| 319 |
- if err != nil {
|
|
| 320 |
- if err == graph.ErrNameIsNotExist {
|
|
| 321 |
- return imgID, nil |
|
| 322 |
- } |
|
| 323 |
- return "", err |
|
| 324 |
- } |
|
| 325 |
- if id != imgID {
|
|
| 326 |
- return imgID, nil |
|
| 327 |
- } |
|
| 328 |
- return img, nil |
|
| 329 |
-} |
|
| 330 |
- |
|
| 331 | 313 |
// transformContainer generates the container type expected by the docker ps command. |
| 332 | 314 |
func (daemon *Daemon) transformContainer(container *Container, ctx *listContext) (*types.Container, error) {
|
| 333 | 315 |
newC := &types.Container{
|
| 334 | 316 |
ID: container.ID, |
| 335 | 317 |
Names: ctx.names[container.ID], |
| 336 |
- ImageID: container.ImageID, |
|
| 318 |
+ ImageID: container.ImageID.String(), |
|
| 337 | 319 |
} |
| 338 | 320 |
if newC.Names == nil {
|
| 339 | 321 |
// Dead containers will often have no name, so make sure the response isn't null |
| 340 | 322 |
newC.Names = []string{}
|
| 341 | 323 |
} |
| 342 | 324 |
|
| 343 |
- showImg, err := getImage(daemon.repositories, container.Config.Image, container.ImageID) |
|
| 344 |
- if err != nil {
|
|
| 345 |
- return nil, err |
|
| 325 |
+ image := container.Config.Image // if possible keep the original ref |
|
| 326 |
+ if image != container.ImageID.String() {
|
|
| 327 |
+ id, err := daemon.GetImageID(image) |
|
| 328 |
+ if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
|
|
| 329 |
+ return nil, err |
|
| 330 |
+ } |
|
| 331 |
+ if err != nil || id != container.ImageID {
|
|
| 332 |
+ image = container.ImageID.String() |
|
| 333 |
+ } |
|
| 346 | 334 |
} |
| 347 |
- newC.Image = showImg |
|
| 335 |
+ newC.Image = image |
|
| 348 | 336 |
|
| 349 | 337 |
if len(container.Args) > 0 {
|
| 350 | 338 |
args := []string{}
|
| ... | ... |
@@ -433,12 +419,10 @@ func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
|
| 433 | 433 |
return volumesOut, nil |
| 434 | 434 |
} |
| 435 | 435 |
|
| 436 |
-func populateImageFilterByParents(ancestorMap map[string]bool, imageID string, byParents map[string][]*image.Image) {
|
|
| 436 |
+func populateImageFilterByParents(ancestorMap map[image.ID]bool, imageID image.ID, getChildren func(image.ID) []image.ID) {
|
|
| 437 | 437 |
if !ancestorMap[imageID] {
|
| 438 |
- if images, ok := byParents[imageID]; ok {
|
|
| 439 |
- for _, image := range images {
|
|
| 440 |
- populateImageFilterByParents(ancestorMap, image.ID, byParents) |
|
| 441 |
- } |
|
| 438 |
+ for _, id := range getChildren(imageID) {
|
|
| 439 |
+ populateImageFilterByParents(ancestorMap, id, getChildren) |
|
| 442 | 440 |
} |
| 443 | 441 |
ancestorMap[imageID] = true |
| 444 | 442 |
} |
| ... | ... |
@@ -832,15 +832,6 @@ var ( |
| 832 | 832 |
HTTPStatusCode: http.StatusInternalServerError, |
| 833 | 833 |
}) |
| 834 | 834 |
|
| 835 |
- // ErrorCodeRmInit is generated when we try to delete a container |
|
| 836 |
- // but failed deleting its init filesystem. |
|
| 837 |
- ErrorCodeRmInit = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
| 838 |
- Value: "RMINIT", |
|
| 839 |
- Message: "Driver %s failed to remove init filesystem %s: %s", |
|
| 840 |
- Description: "While trying to delete a container, the driver failed to remove the init filesystem", |
|
| 841 |
- HTTPStatusCode: http.StatusInternalServerError, |
|
| 842 |
- }) |
|
| 843 |
- |
|
| 844 | 835 |
// ErrorCodeRmFS is generated when we try to delete a container |
| 845 | 836 |
// but failed deleting its filesystem. |
| 846 | 837 |
ErrorCodeRmFS = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
| ... | ... |
@@ -553,7 +553,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) {
|
| 553 | 553 |
cName := "testapicommit" |
| 554 | 554 |
dockerCmd(c, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test") |
| 555 | 555 |
|
| 556 |
- name := "TestContainerApiCommit" |
|
| 556 |
+ name := "testcontainerapicommit" |
|
| 557 | 557 |
status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+cName, nil)
|
| 558 | 558 |
c.Assert(err, check.IsNil) |
| 559 | 559 |
c.Assert(status, check.Equals, http.StatusCreated) |
| ... | ... |
@@ -586,7 +586,7 @@ func (s *DockerSuite) TestContainerApiCommitWithLabelInConfig(c *check.C) {
|
| 586 | 586 |
"Labels": map[string]string{"key1": "value1", "key2": "value2"},
|
| 587 | 587 |
} |
| 588 | 588 |
|
| 589 |
- name := "TestContainerApiCommitWithConfig" |
|
| 589 |
+ name := "testcontainerapicommitwithconfig" |
|
| 590 | 590 |
status, b, err := sockRequest("POST", "/commit?repo="+name+"&container="+cName, config)
|
| 591 | 591 |
c.Assert(err, check.IsNil) |
| 592 | 592 |
c.Assert(status, check.Equals, http.StatusCreated) |
| ... | ... |
@@ -4543,7 +4543,7 @@ func (s *DockerSuite) TestBuildInvalidTag(c *check.C) {
|
| 4543 | 4543 |
_, out, err := buildImageWithOut(name, "FROM scratch\nMAINTAINER quux\n", true) |
| 4544 | 4544 |
// if the error doesnt check for illegal tag name, or the image is built |
| 4545 | 4545 |
// then this should fail |
| 4546 |
- if !strings.Contains(out, "Illegal tag name") || strings.Contains(out, "Sending build context to Docker daemon") {
|
|
| 4546 |
+ if !strings.Contains(out, "invalid reference format") || strings.Contains(out, "Sending build context to Docker daemon") {
|
|
| 4547 | 4547 |
c.Fatalf("failed to stop before building. Error: %s, Output: %s", err, out)
|
| 4548 | 4548 |
} |
| 4549 | 4549 |
} |
| ... | ... |
@@ -6377,7 +6377,7 @@ func (s *DockerSuite) TestBuildTagEvent(c *check.C) {
|
| 6377 | 6377 |
select {
|
| 6378 | 6378 |
case ev := <-ch: |
| 6379 | 6379 |
c.Assert(ev.Status, check.Equals, "tag") |
| 6380 |
- c.Assert(ev.ID, check.Equals, "test:") |
|
| 6380 |
+ c.Assert(ev.ID, check.Equals, "test:latest") |
|
| 6381 | 6381 |
case <-time.After(time.Second): |
| 6382 | 6382 |
c.Fatal("The 'tag' event not heard from the server")
|
| 6383 | 6383 |
} |
| ... | ... |
@@ -3,6 +3,8 @@ package main |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 | 5 |
"fmt" |
| 6 |
+ "os" |
|
| 7 |
+ "path/filepath" |
|
| 6 | 8 |
"regexp" |
| 7 | 9 |
"strings" |
| 8 | 10 |
|
| ... | ... |
@@ -11,7 +13,6 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 | 12 |
"github.com/docker/docker/pkg/integration/checker" |
| 13 | 13 |
"github.com/docker/docker/pkg/stringutils" |
| 14 |
- "github.com/docker/docker/utils" |
|
| 15 | 14 |
"github.com/go-check/check" |
| 16 | 15 |
) |
| 17 | 16 |
|
| ... | ... |
@@ -32,7 +33,7 @@ func setupImageWithTag(c *check.C, tag string) (digest.Digest, error) {
|
| 32 | 32 |
dockerCmd(c, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox") |
| 33 | 33 |
|
| 34 | 34 |
// tag the image to upload it to the private registry |
| 35 |
- repoAndTag := utils.ImageReference(repoName, tag) |
|
| 35 |
+ repoAndTag := repoName + ":" + tag |
|
| 36 | 36 |
out, _, err := dockerCmdWithError("commit", containerName, repoAndTag)
|
| 37 | 37 |
c.Assert(err, checker.IsNil, check.Commentf("image tagging failed: %s", out))
|
| 38 | 38 |
|
| ... | ... |
@@ -438,6 +439,11 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) {
|
| 438 | 438 |
// Now try pulling that image by digest. We should get an error about |
| 439 | 439 |
// digest verification for the target layer digest. |
| 440 | 440 |
|
| 441 |
+ // Remove distribution cache to force a re-pull of the blobs |
|
| 442 |
+ if err := os.RemoveAll(filepath.Join(dockerBasePath, "image", s.d.storageDriver, "distribution")); err != nil {
|
|
| 443 |
+ c.Fatalf("error clearing distribution cache: %v", err)
|
|
| 444 |
+ } |
|
| 445 |
+ |
|
| 441 | 446 |
// Pull from the registry using the <name>@<digest> reference. |
| 442 | 447 |
imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
|
| 443 | 448 |
out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
|
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
|
| 15 | 15 |
"github.com/docker/docker/pkg/integration/checker" |
| 16 | 16 |
"github.com/docker/docker/pkg/nat" |
| 17 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 17 | 18 |
"github.com/go-check/check" |
| 18 | 19 |
) |
| 19 | 20 |
|
| ... | ... |
@@ -243,6 +244,41 @@ func (s *DockerSuite) TestCreateModeIpcContainer(c *check.C) {
|
| 243 | 243 |
dockerCmd(c, "create", fmt.Sprintf("--ipc=container:%s", id), "busybox")
|
| 244 | 244 |
} |
| 245 | 245 |
|
| 246 |
+func (s *DockerSuite) TestCreateByImageID(c *check.C) {
|
|
| 247 |
+ imageName := "testcreatebyimageid" |
|
| 248 |
+ imageID, err := buildImage(imageName, |
|
| 249 |
+ `FROM busybox |
|
| 250 |
+ MAINTAINER dockerio`, |
|
| 251 |
+ true) |
|
| 252 |
+ if err != nil {
|
|
| 253 |
+ c.Fatal(err) |
|
| 254 |
+ } |
|
| 255 |
+ truncatedImageID := stringid.TruncateID(imageID) |
|
| 256 |
+ |
|
| 257 |
+ dockerCmd(c, "create", imageID) |
|
| 258 |
+ dockerCmd(c, "create", truncatedImageID) |
|
| 259 |
+ dockerCmd(c, "create", fmt.Sprintf("%s:%s", imageName, truncatedImageID))
|
|
| 260 |
+ |
|
| 261 |
+ // Ensure this fails |
|
| 262 |
+ out, exit, _ := dockerCmdWithError("create", fmt.Sprintf("%s:%s", imageName, imageID))
|
|
| 263 |
+ if exit == 0 {
|
|
| 264 |
+ c.Fatalf("expected non-zero exit code; received %d", exit)
|
|
| 265 |
+ } |
|
| 266 |
+ |
|
| 267 |
+ if expected := "invalid reference format"; !strings.Contains(out, expected) {
|
|
| 268 |
+ c.Fatalf(`Expected %q in output; got: %s`, expected, out) |
|
| 269 |
+ } |
|
| 270 |
+ |
|
| 271 |
+ out, exit, _ = dockerCmdWithError("create", fmt.Sprintf("%s:%s", "wrongimage", truncatedImageID))
|
|
| 272 |
+ if exit == 0 {
|
|
| 273 |
+ c.Fatalf("expected non-zero exit code; received %d", exit)
|
|
| 274 |
+ } |
|
| 275 |
+ |
|
| 276 |
+ if expected := "Unable to find image"; !strings.Contains(out, expected) {
|
|
| 277 |
+ c.Fatalf(`Expected %q in output; got: %s`, expected, out) |
|
| 278 |
+ } |
|
| 279 |
+} |
|
| 280 |
+ |
|
| 246 | 281 |
func (s *DockerTrustSuite) TestTrustedCreate(c *check.C) {
|
| 247 | 282 |
repoName := s.setupTrustedImage(c, "trusted-create") |
| 248 | 283 |
|
| ... | ... |
@@ -325,6 +325,8 @@ func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) {
|
| 325 | 325 |
err = s.d.Stop() |
| 326 | 326 |
c.Assert(err, check.IsNil) |
| 327 | 327 |
|
| 328 |
+ // Don't check s.ec.exists, because the daemon no longer calls the |
|
| 329 |
+ // Exists function. |
|
| 328 | 330 |
c.Assert(s.ec.activations, check.Equals, 2) |
| 329 | 331 |
c.Assert(s.ec.init, check.Equals, 2) |
| 330 | 332 |
c.Assert(s.ec.creations >= 1, check.Equals, true) |
| ... | ... |
@@ -333,7 +335,6 @@ func (s *DockerExternalGraphdriverSuite) TestExternalGraphDriver(c *check.C) {
|
| 333 | 333 |
c.Assert(s.ec.puts >= 1, check.Equals, true) |
| 334 | 334 |
c.Assert(s.ec.stats, check.Equals, 3) |
| 335 | 335 |
c.Assert(s.ec.cleanups, check.Equals, 2) |
| 336 |
- c.Assert(s.ec.exists >= 1, check.Equals, true) |
|
| 337 | 336 |
c.Assert(s.ec.applydiff >= 1, check.Equals, true) |
| 338 | 337 |
c.Assert(s.ec.changes, check.Equals, 1) |
| 339 | 338 |
c.Assert(s.ec.diffsize, check.Equals, 0) |
| ... | ... |
@@ -98,9 +98,9 @@ func (s *DockerSuite) TestImagesFilterLabel(c *check.C) {
|
| 98 | 98 |
|
| 99 | 99 |
out, _ := dockerCmd(c, "images", "--no-trunc", "-q", "-f", "label=match") |
| 100 | 100 |
out = strings.TrimSpace(out) |
| 101 |
- c.Assert(out, check.Matches, fmt.Sprintf("[\\s\\w]*%s[\\s\\w]*", image1ID))
|
|
| 102 |
- c.Assert(out, check.Matches, fmt.Sprintf("[\\s\\w]*%s[\\s\\w]*", image2ID))
|
|
| 103 |
- c.Assert(out, check.Not(check.Matches), fmt.Sprintf("[\\s\\w]*%s[\\s\\w]*", image3ID))
|
|
| 101 |
+ c.Assert(out, check.Matches, fmt.Sprintf("[\\s\\w:]*%s[\\s\\w:]*", image1ID))
|
|
| 102 |
+ c.Assert(out, check.Matches, fmt.Sprintf("[\\s\\w:]*%s[\\s\\w:]*", image2ID))
|
|
| 103 |
+ c.Assert(out, check.Not(check.Matches), fmt.Sprintf("[\\s\\w:]*%s[\\s\\w:]*", image3ID))
|
|
| 104 | 104 |
|
| 105 | 105 |
out, _ = dockerCmd(c, "images", "--no-trunc", "-q", "-f", "label=match=me too") |
| 106 | 106 |
out = strings.TrimSpace(out) |
| ... | ... |
@@ -204,7 +204,7 @@ func (s *DockerSuite) TestImagesEnsureOnlyHeadsImagesShown(c *check.C) {
|
| 204 | 204 |
// images shouldn't show non-heads images |
| 205 | 205 |
c.Assert(out, checker.Not(checker.Contains), intermediate) |
| 206 | 206 |
// images should contain final built images |
| 207 |
- c.Assert(out, checker.Contains, head[:12]) |
|
| 207 |
+ c.Assert(out, checker.Contains, stringid.TruncateID(head)) |
|
| 208 | 208 |
} |
| 209 | 209 |
|
| 210 | 210 |
func (s *DockerSuite) TestImagesEnsureImagesFromScratchShown(c *check.C) {
|
| ... | ... |
@@ -219,5 +219,5 @@ func (s *DockerSuite) TestImagesEnsureImagesFromScratchShown(c *check.C) {
|
| 219 | 219 |
|
| 220 | 220 |
out, _ := dockerCmd(c, "images") |
| 221 | 221 |
// images should contain images built from scratch |
| 222 |
- c.Assert(out, checker.Contains, id[:12]) |
|
| 222 |
+ c.Assert(out, checker.Contains, stringid.TruncateID(id)) |
|
| 223 | 223 |
} |
| ... | ... |
@@ -23,7 +23,12 @@ func checkValidGraphDriver(c *check.C, name string) {
|
| 23 | 23 |
func (s *DockerSuite) TestInspectImage(c *check.C) {
|
| 24 | 24 |
testRequires(c, DaemonIsLinux) |
| 25 | 25 |
imageTest := "emptyfs" |
| 26 |
- imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158" |
|
| 26 |
+ // It is important that this ID remain stable. If a code change causes |
|
| 27 |
+ // it to be different, this is equivalent to a cache bust when pulling |
|
| 28 |
+ // a legacy-format manifest. If the check at the end of this function |
|
| 29 |
+ // fails, fix the difference in the image serialization instead of |
|
| 30 |
+ // updating this hash. |
|
| 31 |
+ imageTestID := "sha256:11f64303f0f7ffdc71f001788132bca5346831939a956e3e975c93267d89a16d" |
|
| 27 | 32 |
id, err := inspectField(imageTest, "Id") |
| 28 | 33 |
c.Assert(err, checker.IsNil) |
| 29 | 34 |
|
| ... | ... |
@@ -159,3 +159,71 @@ func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
|
| 159 | 159 |
c.Assert(strings.TrimSpace(out), check.Equals, "/bin/sh -c echo "+repo, check.Commentf("CMD did not contain /bin/sh -c echo %s; %s", repo, out))
|
| 160 | 160 |
} |
| 161 | 161 |
} |
| 162 |
+ |
|
| 163 |
+// TestPullIDStability verifies that pushing an image and pulling it back |
|
| 164 |
+// preserves the image ID. |
|
| 165 |
+func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
|
|
| 166 |
+ derivedImage := privateRegistryURL + "/dockercli/id-stability" |
|
| 167 |
+ baseImage := "busybox" |
|
| 168 |
+ |
|
| 169 |
+ _, err := buildImage(derivedImage, fmt.Sprintf(` |
|
| 170 |
+ FROM %s |
|
| 171 |
+ ENV derived true |
|
| 172 |
+ ENV asdf true |
|
| 173 |
+ RUN dd if=/dev/zero of=/file bs=1024 count=1024 |
|
| 174 |
+ CMD echo %s |
|
| 175 |
+ `, baseImage, derivedImage), true) |
|
| 176 |
+ if err != nil {
|
|
| 177 |
+ c.Fatal(err) |
|
| 178 |
+ } |
|
| 179 |
+ |
|
| 180 |
+ originalID, err := getIDByName(derivedImage) |
|
| 181 |
+ if err != nil {
|
|
| 182 |
+ c.Fatalf("error inspecting: %v", err)
|
|
| 183 |
+ } |
|
| 184 |
+ dockerCmd(c, "push", derivedImage) |
|
| 185 |
+ |
|
| 186 |
+ // Pull |
|
| 187 |
+ out, _ := dockerCmd(c, "pull", derivedImage) |
|
| 188 |
+ if strings.Contains(out, "Pull complete") {
|
|
| 189 |
+ c.Fatalf("repull redownloaded a layer: %s", out)
|
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ derivedIDAfterPull, err := getIDByName(derivedImage) |
|
| 193 |
+ if err != nil {
|
|
| 194 |
+ c.Fatalf("error inspecting: %v", err)
|
|
| 195 |
+ } |
|
| 196 |
+ |
|
| 197 |
+ if derivedIDAfterPull != originalID {
|
|
| 198 |
+ c.Fatal("image's ID unexpectedly changed after a repush/repull")
|
|
| 199 |
+ } |
|
| 200 |
+ |
|
| 201 |
+ // Make sure the image runs correctly |
|
| 202 |
+ out, _ = dockerCmd(c, "run", "--rm", derivedImage) |
|
| 203 |
+ if strings.TrimSpace(out) != derivedImage {
|
|
| 204 |
+ c.Fatalf("expected %s; got %s", derivedImage, out)
|
|
| 205 |
+ } |
|
| 206 |
+ |
|
| 207 |
+ // Confirm that repushing and repulling does not change the computed ID |
|
| 208 |
+ dockerCmd(c, "push", derivedImage) |
|
| 209 |
+ dockerCmd(c, "rmi", derivedImage) |
|
| 210 |
+ dockerCmd(c, "pull", derivedImage) |
|
| 211 |
+ |
|
| 212 |
+ derivedIDAfterPull, err = getIDByName(derivedImage) |
|
| 213 |
+ if err != nil {
|
|
| 214 |
+ c.Fatalf("error inspecting: %v", err)
|
|
| 215 |
+ } |
|
| 216 |
+ |
|
| 217 |
+ if derivedIDAfterPull != originalID {
|
|
| 218 |
+ c.Fatal("image's ID unexpectedly changed after a repush/repull")
|
|
| 219 |
+ } |
|
| 220 |
+ if err != nil {
|
|
| 221 |
+ c.Fatalf("error inspecting: %v", err)
|
|
| 222 |
+ } |
|
| 223 |
+ |
|
| 224 |
+ // Make sure the image still runs |
|
| 225 |
+ out, _ = dockerCmd(c, "run", "--rm", derivedImage) |
|
| 226 |
+ if strings.TrimSpace(out) != derivedImage {
|
|
| 227 |
+ c.Fatalf("expected %s; got %s", derivedImage, out)
|
|
| 228 |
+ } |
|
| 229 |
+} |
| ... | ... |
@@ -1,11 +1,7 @@ |
| 1 | 1 |
package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "encoding/json" |
|
| 5 | 4 |
"fmt" |
| 6 |
- "io/ioutil" |
|
| 7 |
- "os" |
|
| 8 |
- "path/filepath" |
|
| 9 | 5 |
"regexp" |
| 10 | 6 |
"strings" |
| 11 | 7 |
"time" |
| ... | ... |
@@ -46,19 +42,19 @@ func (s *DockerHubPullSuite) TestPullFromCentralRegistry(c *check.C) {
|
| 46 | 46 |
func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) {
|
| 47 | 47 |
testRequires(c, DaemonIsLinux) |
| 48 | 48 |
for _, e := range []struct {
|
| 49 |
- Image string |
|
| 49 |
+ Repo string |
|
| 50 | 50 |
Alias string |
| 51 | 51 |
}{
|
| 52 |
- {"library/asdfasdf:foobar", "asdfasdf:foobar"},
|
|
| 53 |
- {"library/asdfasdf:foobar", "library/asdfasdf:foobar"},
|
|
| 54 |
- {"library/asdfasdf:latest", "asdfasdf"},
|
|
| 55 |
- {"library/asdfasdf:latest", "asdfasdf:latest"},
|
|
| 56 |
- {"library/asdfasdf:latest", "library/asdfasdf"},
|
|
| 57 |
- {"library/asdfasdf:latest", "library/asdfasdf:latest"},
|
|
| 52 |
+ {"library/asdfasdf", "asdfasdf:foobar"},
|
|
| 53 |
+ {"library/asdfasdf", "library/asdfasdf:foobar"},
|
|
| 54 |
+ {"library/asdfasdf", "asdfasdf"},
|
|
| 55 |
+ {"library/asdfasdf", "asdfasdf:latest"},
|
|
| 56 |
+ {"library/asdfasdf", "library/asdfasdf"},
|
|
| 57 |
+ {"library/asdfasdf", "library/asdfasdf:latest"},
|
|
| 58 | 58 |
} {
|
| 59 | 59 |
out, err := s.CmdWithError("pull", e.Alias)
|
| 60 | 60 |
c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out))
|
| 61 |
- c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Image), check.Commentf("expected image not found error messages"))
|
|
| 61 |
+ c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Repo), check.Commentf("expected image not found error messages"))
|
|
| 62 | 62 |
} |
| 63 | 63 |
} |
| 64 | 64 |
|
| ... | ... |
@@ -163,254 +159,3 @@ func (s *DockerHubPullSuite) TestPullClientDisconnect(c *check.C) {
|
| 163 | 163 |
time.Sleep(500 * time.Millisecond) |
| 164 | 164 |
} |
| 165 | 165 |
} |
| 166 |
- |
|
| 167 |
-type idAndParent struct {
|
|
| 168 |
- ID string |
|
| 169 |
- Parent string |
|
| 170 |
-} |
|
| 171 |
- |
|
| 172 |
-func inspectImage(c *check.C, imageRef string) idAndParent {
|
|
| 173 |
- out, _ := dockerCmd(c, "inspect", imageRef) |
|
| 174 |
- var inspectOutput []idAndParent |
|
| 175 |
- err := json.Unmarshal([]byte(out), &inspectOutput) |
|
| 176 |
- if err != nil {
|
|
| 177 |
- c.Fatal(err) |
|
| 178 |
- } |
|
| 179 |
- |
|
| 180 |
- return inspectOutput[0] |
|
| 181 |
-} |
|
| 182 |
- |
|
| 183 |
-func imageID(c *check.C, imageRef string) string {
|
|
| 184 |
- return inspectImage(c, imageRef).ID |
|
| 185 |
-} |
|
| 186 |
- |
|
| 187 |
-func imageParent(c *check.C, imageRef string) string {
|
|
| 188 |
- return inspectImage(c, imageRef).Parent |
|
| 189 |
-} |
|
| 190 |
- |
|
| 191 |
-// TestPullMigration verifies that pulling an image based on layers |
|
| 192 |
-// that already exists locally will reuse those existing layers. |
|
| 193 |
-func (s *DockerRegistrySuite) TestPullMigration(c *check.C) {
|
|
| 194 |
- repoName := privateRegistryURL + "/dockercli/migration" |
|
| 195 |
- |
|
| 196 |
- baseImage := repoName + ":base" |
|
| 197 |
- _, err := buildImage(baseImage, fmt.Sprintf(` |
|
| 198 |
- FROM scratch |
|
| 199 |
- ENV IMAGE base |
|
| 200 |
- CMD echo %s |
|
| 201 |
- `, baseImage), true) |
|
| 202 |
- if err != nil {
|
|
| 203 |
- c.Fatal(err) |
|
| 204 |
- } |
|
| 205 |
- |
|
| 206 |
- baseIDBeforePush := imageID(c, baseImage) |
|
| 207 |
- baseParentBeforePush := imageParent(c, baseImage) |
|
| 208 |
- |
|
| 209 |
- derivedImage := repoName + ":derived" |
|
| 210 |
- _, err = buildImage(derivedImage, fmt.Sprintf(` |
|
| 211 |
- FROM %s |
|
| 212 |
- CMD echo %s |
|
| 213 |
- `, baseImage, derivedImage), true) |
|
| 214 |
- if err != nil {
|
|
| 215 |
- c.Fatal(err) |
|
| 216 |
- } |
|
| 217 |
- |
|
| 218 |
- derivedIDBeforePush := imageID(c, derivedImage) |
|
| 219 |
- |
|
| 220 |
- dockerCmd(c, "push", derivedImage) |
|
| 221 |
- |
|
| 222 |
- // Remove derived image from the local store |
|
| 223 |
- dockerCmd(c, "rmi", derivedImage) |
|
| 224 |
- |
|
| 225 |
- // Repull |
|
| 226 |
- dockerCmd(c, "pull", derivedImage) |
|
| 227 |
- |
|
| 228 |
- // Check that the parent of this pulled image is the original base |
|
| 229 |
- // image |
|
| 230 |
- derivedIDAfterPull1 := imageID(c, derivedImage) |
|
| 231 |
- derivedParentAfterPull1 := imageParent(c, derivedImage) |
|
| 232 |
- |
|
| 233 |
- if derivedIDAfterPull1 == derivedIDBeforePush {
|
|
| 234 |
- c.Fatal("image's ID should have changed on after deleting and pulling")
|
|
| 235 |
- } |
|
| 236 |
- |
|
| 237 |
- if derivedParentAfterPull1 != baseIDBeforePush {
|
|
| 238 |
- c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull1, baseIDBeforePush)
|
|
| 239 |
- } |
|
| 240 |
- |
|
| 241 |
- // Confirm that repushing and repulling does not change the computed ID |
|
| 242 |
- dockerCmd(c, "push", derivedImage) |
|
| 243 |
- dockerCmd(c, "rmi", derivedImage) |
|
| 244 |
- dockerCmd(c, "pull", derivedImage) |
|
| 245 |
- |
|
| 246 |
- derivedIDAfterPull2 := imageID(c, derivedImage) |
|
| 247 |
- derivedParentAfterPull2 := imageParent(c, derivedImage) |
|
| 248 |
- |
|
| 249 |
- if derivedIDAfterPull2 != derivedIDAfterPull1 {
|
|
| 250 |
- c.Fatal("image's ID unexpectedly changed after a repush/repull")
|
|
| 251 |
- } |
|
| 252 |
- |
|
| 253 |
- if derivedParentAfterPull2 != baseIDBeforePush {
|
|
| 254 |
- c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull2, baseIDBeforePush)
|
|
| 255 |
- } |
|
| 256 |
- |
|
| 257 |
- // Remove everything, repull, and make sure everything uses computed IDs |
|
| 258 |
- dockerCmd(c, "rmi", baseImage, derivedImage) |
|
| 259 |
- dockerCmd(c, "pull", derivedImage) |
|
| 260 |
- |
|
| 261 |
- derivedIDAfterPull3 := imageID(c, derivedImage) |
|
| 262 |
- derivedParentAfterPull3 := imageParent(c, derivedImage) |
|
| 263 |
- derivedGrandparentAfterPull3 := imageParent(c, derivedParentAfterPull3) |
|
| 264 |
- |
|
| 265 |
- if derivedIDAfterPull3 != derivedIDAfterPull1 {
|
|
| 266 |
- c.Fatal("image's ID unexpectedly changed after a second repull")
|
|
| 267 |
- } |
|
| 268 |
- |
|
| 269 |
- if derivedParentAfterPull3 == baseIDBeforePush {
|
|
| 270 |
- c.Fatalf("pulled image's parent ID (%s) should not match base image's original ID (%s)", derivedParentAfterPull3, derivedIDBeforePush)
|
|
| 271 |
- } |
|
| 272 |
- |
|
| 273 |
- if derivedGrandparentAfterPull3 == baseParentBeforePush {
|
|
| 274 |
- c.Fatal("base image's parent ID should have been rewritten on pull")
|
|
| 275 |
- } |
|
| 276 |
-} |
|
| 277 |
- |
|
| 278 |
-// TestPullMigrationRun verifies that pulling an image based on layers |
|
| 279 |
-// that already exists locally will result in an image that runs properly. |
|
| 280 |
-func (s *DockerRegistrySuite) TestPullMigrationRun(c *check.C) {
|
|
| 281 |
- type idAndParent struct {
|
|
| 282 |
- ID string |
|
| 283 |
- Parent string |
|
| 284 |
- } |
|
| 285 |
- |
|
| 286 |
- derivedImage := privateRegistryURL + "/dockercli/migration-run" |
|
| 287 |
- baseImage := "busybox" |
|
| 288 |
- |
|
| 289 |
- _, err := buildImage(derivedImage, fmt.Sprintf(` |
|
| 290 |
- FROM %s |
|
| 291 |
- RUN dd if=/dev/zero of=/file bs=1024 count=1024 |
|
| 292 |
- CMD echo %s |
|
| 293 |
- `, baseImage, derivedImage), true) |
|
| 294 |
- if err != nil {
|
|
| 295 |
- c.Fatal(err) |
|
| 296 |
- } |
|
| 297 |
- |
|
| 298 |
- baseIDBeforePush := imageID(c, baseImage) |
|
| 299 |
- derivedIDBeforePush := imageID(c, derivedImage) |
|
| 300 |
- |
|
| 301 |
- dockerCmd(c, "push", derivedImage) |
|
| 302 |
- |
|
| 303 |
- // Remove derived image from the local store |
|
| 304 |
- dockerCmd(c, "rmi", derivedImage) |
|
| 305 |
- |
|
| 306 |
- // Repull |
|
| 307 |
- dockerCmd(c, "pull", derivedImage) |
|
| 308 |
- |
|
| 309 |
- // Check that this pulled image is based on the original base image |
|
| 310 |
- derivedIDAfterPull1 := imageID(c, derivedImage) |
|
| 311 |
- derivedParentAfterPull1 := imageParent(c, imageParent(c, derivedImage)) |
|
| 312 |
- |
|
| 313 |
- if derivedIDAfterPull1 == derivedIDBeforePush {
|
|
| 314 |
- c.Fatal("image's ID should have changed on after deleting and pulling")
|
|
| 315 |
- } |
|
| 316 |
- |
|
| 317 |
- if derivedParentAfterPull1 != baseIDBeforePush {
|
|
| 318 |
- c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull1, baseIDBeforePush)
|
|
| 319 |
- } |
|
| 320 |
- |
|
| 321 |
- // Make sure the image runs correctly |
|
| 322 |
- out, _ := dockerCmd(c, "run", "--rm", derivedImage) |
|
| 323 |
- if strings.TrimSpace(out) != derivedImage {
|
|
| 324 |
- c.Fatalf("expected %s; got %s", derivedImage, out)
|
|
| 325 |
- } |
|
| 326 |
- |
|
| 327 |
- // Confirm that repushing and repulling does not change the computed ID |
|
| 328 |
- dockerCmd(c, "push", derivedImage) |
|
| 329 |
- dockerCmd(c, "rmi", derivedImage) |
|
| 330 |
- dockerCmd(c, "pull", derivedImage) |
|
| 331 |
- |
|
| 332 |
- derivedIDAfterPull2 := imageID(c, derivedImage) |
|
| 333 |
- derivedParentAfterPull2 := imageParent(c, imageParent(c, derivedImage)) |
|
| 334 |
- |
|
| 335 |
- if derivedIDAfterPull2 != derivedIDAfterPull1 {
|
|
| 336 |
- c.Fatal("image's ID unexpectedly changed after a repush/repull")
|
|
| 337 |
- } |
|
| 338 |
- |
|
| 339 |
- if derivedParentAfterPull2 != baseIDBeforePush {
|
|
| 340 |
- c.Fatalf("pulled image's parent ID (%s) does not match base image's ID (%s)", derivedParentAfterPull2, baseIDBeforePush)
|
|
| 341 |
- } |
|
| 342 |
- |
|
| 343 |
- // Make sure the image still runs |
|
| 344 |
- out, _ = dockerCmd(c, "run", "--rm", derivedImage) |
|
| 345 |
- if strings.TrimSpace(out) != derivedImage {
|
|
| 346 |
- c.Fatalf("expected %s; got %s", derivedImage, out)
|
|
| 347 |
- } |
|
| 348 |
-} |
|
| 349 |
- |
|
| 350 |
-// TestPullConflict provides coverage of the situation where a computed |
|
| 351 |
-// strongID conflicts with some unverifiable data in the graph. |
|
| 352 |
-func (s *DockerRegistrySuite) TestPullConflict(c *check.C) {
|
|
| 353 |
- repoName := privateRegistryURL + "/dockercli/conflict" |
|
| 354 |
- |
|
| 355 |
- _, err := buildImage(repoName, ` |
|
| 356 |
- FROM scratch |
|
| 357 |
- ENV IMAGE conflict |
|
| 358 |
- CMD echo conflict |
|
| 359 |
- `, true) |
|
| 360 |
- if err != nil {
|
|
| 361 |
- c.Fatal(err) |
|
| 362 |
- } |
|
| 363 |
- |
|
| 364 |
- dockerCmd(c, "push", repoName) |
|
| 365 |
- |
|
| 366 |
- // Pull to make it content-addressable |
|
| 367 |
- dockerCmd(c, "rmi", repoName) |
|
| 368 |
- dockerCmd(c, "pull", repoName) |
|
| 369 |
- |
|
| 370 |
- IDBeforeLoad := imageID(c, repoName) |
|
| 371 |
- |
|
| 372 |
- // Load/save to turn this into an unverified image with the same ID |
|
| 373 |
- tmpDir, err := ioutil.TempDir("", "conflict-save-output")
|
|
| 374 |
- if err != nil {
|
|
| 375 |
- c.Errorf("failed to create temporary directory: %s", err)
|
|
| 376 |
- } |
|
| 377 |
- defer os.RemoveAll(tmpDir) |
|
| 378 |
- |
|
| 379 |
- tarFile := filepath.Join(tmpDir, "repo.tar") |
|
| 380 |
- |
|
| 381 |
- dockerCmd(c, "save", "-o", tarFile, repoName) |
|
| 382 |
- dockerCmd(c, "rmi", repoName) |
|
| 383 |
- dockerCmd(c, "load", "-i", tarFile) |
|
| 384 |
- |
|
| 385 |
- // Check that the the ID is the same after save/load. |
|
| 386 |
- IDAfterLoad := imageID(c, repoName) |
|
| 387 |
- |
|
| 388 |
- if IDAfterLoad != IDBeforeLoad {
|
|
| 389 |
- c.Fatal("image's ID should be the same after save/load")
|
|
| 390 |
- } |
|
| 391 |
- |
|
| 392 |
- // Repull |
|
| 393 |
- dockerCmd(c, "pull", repoName) |
|
| 394 |
- |
|
| 395 |
- // Check that the ID is now different because of the conflict. |
|
| 396 |
- IDAfterPull1 := imageID(c, repoName) |
|
| 397 |
- |
|
| 398 |
- // Expect the new ID to be SHA256(oldID) |
|
| 399 |
- expectedIDDigest, err := digest.FromBytes([]byte(IDBeforeLoad)) |
|
| 400 |
- if err != nil {
|
|
| 401 |
- c.Fatalf("digest error: %v", err)
|
|
| 402 |
- } |
|
| 403 |
- expectedID := expectedIDDigest.Hex() |
|
| 404 |
- if IDAfterPull1 != expectedID {
|
|
| 405 |
- c.Fatalf("image's ID should have changed on pull to %s (got %s)", expectedID, IDAfterPull1)
|
|
| 406 |
- } |
|
| 407 |
- |
|
| 408 |
- // A second pull should use the new ID again. |
|
| 409 |
- dockerCmd(c, "pull", repoName) |
|
| 410 |
- |
|
| 411 |
- IDAfterPull2 := imageID(c, repoName) |
|
| 412 |
- |
|
| 413 |
- if IDAfterPull2 != IDAfterPull1 {
|
|
| 414 |
- c.Fatal("image's ID unexpectedly changed after a repull")
|
|
| 415 |
- } |
|
| 416 |
-} |
| ... | ... |
@@ -2,16 +2,13 @@ package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"archive/tar" |
| 5 |
- "encoding/json" |
|
| 6 | 5 |
"fmt" |
| 7 | 6 |
"io/ioutil" |
| 8 | 7 |
"os" |
| 9 | 8 |
"os/exec" |
| 10 |
- "path/filepath" |
|
| 11 | 9 |
"strings" |
| 12 | 10 |
"time" |
| 13 | 11 |
|
| 14 |
- "github.com/docker/docker/image" |
|
| 15 | 12 |
"github.com/docker/docker/pkg/integration/checker" |
| 16 | 13 |
"github.com/go-check/check" |
| 17 | 14 |
) |
| ... | ... |
@@ -86,46 +83,6 @@ func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
|
| 86 | 86 |
} |
| 87 | 87 |
} |
| 88 | 88 |
|
| 89 |
-// TestPushBadParentChain tries to push an image with a corrupted parent chain |
|
| 90 |
-// in the v1compatibility files, and makes sure the push process fixes it. |
|
| 91 |
-func (s *DockerRegistrySuite) TestPushBadParentChain(c *check.C) {
|
|
| 92 |
- repoName := fmt.Sprintf("%v/dockercli/badparent", privateRegistryURL)
|
|
| 93 |
- |
|
| 94 |
- id, err := buildImage(repoName, ` |
|
| 95 |
- FROM busybox |
|
| 96 |
- CMD echo "adding another layer" |
|
| 97 |
- `, true) |
|
| 98 |
- if err != nil {
|
|
| 99 |
- c.Fatal(err) |
|
| 100 |
- } |
|
| 101 |
- |
|
| 102 |
- // Push to create v1compatibility file |
|
| 103 |
- dockerCmd(c, "push", repoName) |
|
| 104 |
- |
|
| 105 |
- // Corrupt the parent in the v1compatibility file from the top layer |
|
| 106 |
- filename := filepath.Join(dockerBasePath, "graph", id, "v1Compatibility") |
|
| 107 |
- |
|
| 108 |
- jsonBytes, err := ioutil.ReadFile(filename) |
|
| 109 |
- c.Assert(err, check.IsNil, check.Commentf("Could not read v1Compatibility file: %s", err))
|
|
| 110 |
- |
|
| 111 |
- var img image.Image |
|
| 112 |
- err = json.Unmarshal(jsonBytes, &img) |
|
| 113 |
- c.Assert(err, check.IsNil, check.Commentf("Could not unmarshal json: %s", err))
|
|
| 114 |
- |
|
| 115 |
- img.Parent = "1234123412341234123412341234123412341234123412341234123412341234" |
|
| 116 |
- |
|
| 117 |
- jsonBytes, err = json.Marshal(&img) |
|
| 118 |
- c.Assert(err, check.IsNil, check.Commentf("Could not marshal json: %s", err))
|
|
| 119 |
- |
|
| 120 |
- err = ioutil.WriteFile(filename, jsonBytes, 0600) |
|
| 121 |
- c.Assert(err, check.IsNil, check.Commentf("Could not write v1Compatibility file: %s", err))
|
|
| 122 |
- |
|
| 123 |
- dockerCmd(c, "push", repoName) |
|
| 124 |
- |
|
| 125 |
- // pull should succeed |
|
| 126 |
- dockerCmd(c, "pull", repoName) |
|
| 127 |
-} |
|
| 128 |
- |
|
| 129 | 89 |
func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
|
| 130 | 90 |
repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL)
|
| 131 | 91 |
emptyTarball, err := ioutil.TempFile("", "empty_tarball")
|
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"strings" |
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/docker/pkg/integration/checker" |
| 9 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 9 | 10 |
"github.com/go-check/check" |
| 10 | 11 |
) |
| 11 | 12 |
|
| ... | ... |
@@ -85,7 +86,7 @@ func (s *DockerSuite) TestRmiImgIDMultipleTag(c *check.C) {
|
| 85 | 85 |
|
| 86 | 86 |
// first checkout without force it fails |
| 87 | 87 |
out, _, err = dockerCmdWithError("rmi", imgID)
|
| 88 |
- expected := fmt.Sprintf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", imgID[:12], containerID[:12])
|
|
| 88 |
+ expected := fmt.Sprintf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", stringid.TruncateID(imgID), stringid.TruncateID(containerID))
|
|
| 89 | 89 |
// rmi tagged in multiple repos should have failed without force |
| 90 | 90 |
c.Assert(err, checker.NotNil) |
| 91 | 91 |
c.Assert(out, checker.Contains, expected) |
| ... | ... |
@@ -3749,3 +3749,15 @@ func (s *DockerSuite) TestDockerFails(c *check.C) {
|
| 3749 | 3749 |
c.Fatalf("Docker run with flag not defined should exit with 125, but we got out: %s, exit: %d, err: %s", out, exit, err)
|
| 3750 | 3750 |
} |
| 3751 | 3751 |
} |
| 3752 |
+ |
|
| 3753 |
+// TestRunInvalidReference invokes docker run with a bad reference. |
|
| 3754 |
+func (s *DockerSuite) TestRunInvalidReference(c *check.C) {
|
|
| 3755 |
+ out, exit, _ := dockerCmdWithError("run", "busybox@foo")
|
|
| 3756 |
+ if exit == 0 {
|
|
| 3757 |
+ c.Fatalf("expected non-zero exist code; received %d", exit)
|
|
| 3758 |
+ } |
|
| 3759 |
+ |
|
| 3760 |
+ if !strings.Contains(out, "invalid reference format") {
|
|
| 3761 |
+ c.Fatalf(`Expected "invalid reference format" in output; got: %s`, out) |
|
| 3762 |
+ } |
|
| 3763 |
+} |
| ... | ... |
@@ -8,10 +8,12 @@ import ( |
| 8 | 8 |
"os/exec" |
| 9 | 9 |
"path/filepath" |
| 10 | 10 |
"reflect" |
| 11 |
+ "regexp" |
|
| 11 | 12 |
"sort" |
| 12 | 13 |
"strings" |
| 13 | 14 |
"time" |
| 14 | 15 |
|
| 16 |
+ "github.com/docker/distribution/digest" |
|
| 15 | 17 |
"github.com/docker/docker/pkg/integration/checker" |
| 16 | 18 |
"github.com/go-check/check" |
| 17 | 19 |
) |
| ... | ... |
@@ -100,7 +102,7 @@ func (s *DockerSuite) TestSaveCheckTimes(c *check.C) {
|
| 100 | 100 |
out, _, err = runCommandPipelineWithOutput( |
| 101 | 101 |
exec.Command(dockerBinary, "save", repoName), |
| 102 | 102 |
exec.Command("tar", "tv"),
|
| 103 |
- exec.Command("grep", "-E", fmt.Sprintf("%s %s", data[0].Created.Format(tarTvTimeFormat), data[0].ID)))
|
|
| 103 |
+ exec.Command("grep", "-E", fmt.Sprintf("%s %s", data[0].Created.Format(tarTvTimeFormat), digest.Digest(data[0].ID).Hex())))
|
|
| 104 | 104 |
c.Assert(err, checker.IsNil, check.Commentf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err))
|
| 105 | 105 |
} |
| 106 | 106 |
|
| ... | ... |
@@ -110,7 +112,7 @@ func (s *DockerSuite) TestSaveImageId(c *check.C) {
|
| 110 | 110 |
dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName))
|
| 111 | 111 |
|
| 112 | 112 |
out, _ := dockerCmd(c, "images", "-q", "--no-trunc", repoName) |
| 113 |
- cleanedLongImageID := strings.TrimSpace(out) |
|
| 113 |
+ cleanedLongImageID := strings.TrimPrefix(strings.TrimSpace(out), "sha256:") |
|
| 114 | 114 |
|
| 115 | 115 |
out, _ = dockerCmd(c, "images", "-q", repoName) |
| 116 | 116 |
cleanedShortImageID := strings.TrimSpace(out) |
| ... | ... |
@@ -207,20 +209,30 @@ func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) {
|
| 207 | 207 |
|
| 208 | 208 |
// create the archive |
| 209 | 209 |
out, _, err := runCommandPipelineWithOutput( |
| 210 |
- exec.Command(dockerBinary, "save", repoName), |
|
| 211 |
- exec.Command("tar", "t"),
|
|
| 212 |
- exec.Command("grep", "VERSION"),
|
|
| 213 |
- exec.Command("cut", "-d", "/", "-f1"))
|
|
| 210 |
+ exec.Command(dockerBinary, "save", repoName, "busybox:latest"), |
|
| 211 |
+ exec.Command("tar", "t"))
|
|
| 214 | 212 |
c.Assert(err, checker.IsNil, check.Commentf("failed to save multiple images: %s, %v", out, err))
|
| 215 |
- actual := strings.Split(strings.TrimSpace(out), "\n") |
|
| 213 |
+ |
|
| 214 |
+ lines := strings.Split(strings.TrimSpace(out), "\n") |
|
| 215 |
+ var actual []string |
|
| 216 |
+ for _, l := range lines {
|
|
| 217 |
+ if regexp.MustCompile("^[a-f0-9]{64}\\.json$").Match([]byte(l)) {
|
|
| 218 |
+ actual = append(actual, strings.TrimSuffix(l, ".json")) |
|
| 219 |
+ } |
|
| 220 |
+ } |
|
| 216 | 221 |
|
| 217 | 222 |
// make the list of expected layers |
| 218 |
- out, _ = dockerCmd(c, "history", "-q", "--no-trunc", "busybox:latest") |
|
| 219 |
- expected := append(strings.Split(strings.TrimSpace(out), "\n"), idFoo, idBar) |
|
| 223 |
+ out, _ = dockerCmd(c, "inspect", "-f", "{{.Id}}", "busybox:latest")
|
|
| 224 |
+ expected := []string{strings.TrimSpace(out), idFoo, idBar}
|
|
| 225 |
+ |
|
| 226 |
+ // prefixes are not in tar |
|
| 227 |
+ for i := range expected {
|
|
| 228 |
+ expected[i] = digest.Digest(expected[i]).Hex() |
|
| 229 |
+ } |
|
| 220 | 230 |
|
| 221 | 231 |
sort.Strings(actual) |
| 222 | 232 |
sort.Strings(expected) |
| 223 |
- c.Assert(actual, checker.DeepEquals, expected, check.Commentf("archive does not contains the right layers: got %v, expected %v", actual, expected))
|
|
| 233 |
+ c.Assert(actual, checker.DeepEquals, expected, check.Commentf("archive does not contains the right layers: got %v, expected %v, output: %q", actual, expected, out))
|
|
| 224 | 234 |
} |
| 225 | 235 |
|
| 226 | 236 |
// Issue #6722 #5892 ensure directories are included in changes |
| ... | ... |
@@ -18,9 +18,7 @@ func (s *DockerSuite) TestSaveAndLoadRepoStdout(c *check.C) {
|
| 18 | 18 |
dockerCmd(c, "run", "--name", name, "busybox", "true") |
| 19 | 19 |
|
| 20 | 20 |
repoName := "foobar-save-load-test" |
| 21 |
- out, _ := dockerCmd(c, "commit", name, repoName) |
|
| 22 |
- |
|
| 23 |
- before, _ := dockerCmd(c, "inspect", repoName) |
|
| 21 |
+ before, _ := dockerCmd(c, "commit", name, repoName) |
|
| 24 | 22 |
|
| 25 | 23 |
tmpFile, err := ioutil.TempFile("", "foobar-save-load-test.tar")
|
| 26 | 24 |
c.Assert(err, check.IsNil) |
| ... | ... |
@@ -40,10 +38,10 @@ func (s *DockerSuite) TestSaveAndLoadRepoStdout(c *check.C) {
|
| 40 | 40 |
loadCmd := exec.Command(dockerBinary, "load") |
| 41 | 41 |
loadCmd.Stdin = tmpFile |
| 42 | 42 |
|
| 43 |
- out, _, err = runCommandWithOutput(loadCmd) |
|
| 43 |
+ out, _, err := runCommandWithOutput(loadCmd) |
|
| 44 | 44 |
c.Assert(err, check.IsNil, check.Commentf(out)) |
| 45 | 45 |
|
| 46 |
- after, _ := dockerCmd(c, "inspect", repoName) |
|
| 46 |
+ after, _ := dockerCmd(c, "inspect", "-f", "{{.Id}}", repoName)
|
|
| 47 | 47 |
|
| 48 | 48 |
c.Assert(before, check.Equals, after) //inspect is not the same after a save / load |
| 49 | 49 |
|
| ... | ... |
@@ -1,9 +1,11 @@ |
| 1 | 1 |
package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 4 | 5 |
"strings" |
| 5 | 6 |
|
| 6 | 7 |
"github.com/docker/docker/pkg/integration/checker" |
| 8 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 7 | 9 |
"github.com/docker/docker/pkg/stringutils" |
| 8 | 10 |
"github.com/go-check/check" |
| 9 | 11 |
) |
| ... | ... |
@@ -111,7 +113,7 @@ func (s *DockerSuite) TestTagWithPrefixHyphen(c *check.C) {
|
| 111 | 111 |
// test index name begin with '-' |
| 112 | 112 |
out, _, err = dockerCmdWithError("tag", "busybox:latest", "-index:5000/busybox:test")
|
| 113 | 113 |
c.Assert(err, checker.NotNil, check.Commentf(out)) |
| 114 |
- c.Assert(out, checker.Contains, "Invalid index name (-index:5000). Cannot begin or end with a hyphen", check.Commentf("tag a name begin with '-' should failed"))
|
|
| 114 |
+ c.Assert(out, checker.Contains, "invalid reference format", check.Commentf("tag a name begin with '-' should failed"))
|
|
| 115 | 115 |
} |
| 116 | 116 |
|
| 117 | 117 |
// ensure tagging using official names works |
| ... | ... |
@@ -171,3 +173,57 @@ func (s *DockerSuite) TestTagMatchesDigest(c *check.C) {
|
| 171 | 171 |
c.Fatal("inspecting by digest should have failed")
|
| 172 | 172 |
} |
| 173 | 173 |
} |
| 174 |
+ |
|
| 175 |
+func (s *DockerSuite) TestTagInvalidRepoName(c *check.C) {
|
|
| 176 |
+ testRequires(c, DaemonIsLinux) |
|
| 177 |
+ if err := pullImageIfNotExist("busybox:latest"); err != nil {
|
|
| 178 |
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
|
|
| 179 |
+ } |
|
| 180 |
+ |
|
| 181 |
+ // test setting tag fails |
|
| 182 |
+ _, _, err := dockerCmdWithError("tag", "-f", "busybox:latest", "sha256:sometag")
|
|
| 183 |
+ if err == nil {
|
|
| 184 |
+ c.Fatal("tagging with image named \"sha256\" should have failed")
|
|
| 185 |
+ } |
|
| 186 |
+} |
|
| 187 |
+ |
|
| 188 |
+// ensure tags cannot create ambiguity with image ids |
|
| 189 |
+func (s *DockerSuite) TestTagTruncationAmbiguity(c *check.C) {
|
|
| 190 |
+ testRequires(c, DaemonIsLinux) |
|
| 191 |
+ if err := pullImageIfNotExist("busybox:latest"); err != nil {
|
|
| 192 |
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
|
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 195 |
+ imageID, err := buildImage("notbusybox:latest",
|
|
| 196 |
+ `FROM busybox |
|
| 197 |
+ MAINTAINER dockerio`, |
|
| 198 |
+ true) |
|
| 199 |
+ if err != nil {
|
|
| 200 |
+ c.Fatal(err) |
|
| 201 |
+ } |
|
| 202 |
+ truncatedImageID := stringid.TruncateID(imageID) |
|
| 203 |
+ truncatedTag := fmt.Sprintf("notbusybox:%s", truncatedImageID)
|
|
| 204 |
+ |
|
| 205 |
+ id, err := inspectField(truncatedTag, "Id") |
|
| 206 |
+ if err != nil {
|
|
| 207 |
+ c.Fatalf("Error inspecting by image id: %s", err)
|
|
| 208 |
+ } |
|
| 209 |
+ |
|
| 210 |
+ // Ensure inspect by image id returns image for image id |
|
| 211 |
+ c.Assert(id, checker.Equals, imageID) |
|
| 212 |
+ c.Logf("Built image: %s", imageID)
|
|
| 213 |
+ |
|
| 214 |
+ // test setting tag fails |
|
| 215 |
+ _, _, err = dockerCmdWithError("tag", "-f", "busybox:latest", truncatedTag)
|
|
| 216 |
+ if err != nil {
|
|
| 217 |
+ c.Fatalf("Error tagging with an image id: %s", err)
|
|
| 218 |
+ } |
|
| 219 |
+ |
|
| 220 |
+ id, err = inspectField(truncatedTag, "Id") |
|
| 221 |
+ if err != nil {
|
|
| 222 |
+ c.Fatalf("Error inspecting by image id: %s", err)
|
|
| 223 |
+ } |
|
| 224 |
+ |
|
| 225 |
+ // Ensure id is imageID and not busybox:latest |
|
| 226 |
+ c.Assert(id, checker.Not(checker.Equals), imageID) |
|
| 227 |
+} |
| ... | ... |
@@ -110,26 +110,6 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
|
| 110 | 110 |
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
|
| 111 | 111 |
} |
| 112 | 112 |
|
| 113 |
-// ParseRepositoryTag gets a repos name and returns the right reposName + tag|digest |
|
| 114 |
-// The tag can be confusing because of a port in a repository name. |
|
| 115 |
-// Ex: localhost.localdomain:5000/samalba/hipache:latest |
|
| 116 |
-// Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb |
|
| 117 |
-func ParseRepositoryTag(repos string) (string, string) {
|
|
| 118 |
- n := strings.Index(repos, "@") |
|
| 119 |
- if n >= 0 {
|
|
| 120 |
- parts := strings.Split(repos, "@") |
|
| 121 |
- return parts[0], parts[1] |
|
| 122 |
- } |
|
| 123 |
- n = strings.LastIndex(repos, ":") |
|
| 124 |
- if n < 0 {
|
|
| 125 |
- return repos, "" |
|
| 126 |
- } |
|
| 127 |
- if tag := repos[n+1:]; !strings.Contains(tag, "/") {
|
|
| 128 |
- return repos[:n], tag |
|
| 129 |
- } |
|
| 130 |
- return repos, "" |
|
| 131 |
-} |
|
| 132 |
- |
|
| 133 | 113 |
// PartParser parses and validates the specified string (data) using the specified template |
| 134 | 114 |
// e.g. ip:public:private -> 192.168.0.1:80:8000 |
| 135 | 115 |
func PartParser(template, data string) (map[string]string, error) {
|
| ... | ... |
@@ -120,36 +120,6 @@ func TestParseInvalidUnixAddrInvalid(t *testing.T) {
|
| 120 | 120 |
} |
| 121 | 121 |
} |
| 122 | 122 |
|
| 123 |
-func TestParseRepositoryTag(t *testing.T) {
|
|
| 124 |
- if repo, tag := ParseRepositoryTag("root"); repo != "root" || tag != "" {
|
|
| 125 |
- t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "", repo, tag)
|
|
| 126 |
- } |
|
| 127 |
- if repo, tag := ParseRepositoryTag("root:tag"); repo != "root" || tag != "tag" {
|
|
| 128 |
- t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "tag", repo, tag)
|
|
| 129 |
- } |
|
| 130 |
- if repo, digest := ParseRepositoryTag("root@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "root" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
|
|
| 131 |
- t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "root", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
|
|
| 132 |
- } |
|
| 133 |
- if repo, tag := ParseRepositoryTag("user/repo"); repo != "user/repo" || tag != "" {
|
|
| 134 |
- t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "", repo, tag)
|
|
| 135 |
- } |
|
| 136 |
- if repo, tag := ParseRepositoryTag("user/repo:tag"); repo != "user/repo" || tag != "tag" {
|
|
| 137 |
- t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "tag", repo, tag)
|
|
| 138 |
- } |
|
| 139 |
- if repo, digest := ParseRepositoryTag("user/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "user/repo" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
|
|
| 140 |
- t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "user/repo", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
|
|
| 141 |
- } |
|
| 142 |
- if repo, tag := ParseRepositoryTag("url:5000/repo"); repo != "url:5000/repo" || tag != "" {
|
|
| 143 |
- t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "", repo, tag)
|
|
| 144 |
- } |
|
| 145 |
- if repo, tag := ParseRepositoryTag("url:5000/repo:tag"); repo != "url:5000/repo" || tag != "tag" {
|
|
| 146 |
- t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag)
|
|
| 147 |
- } |
|
| 148 |
- if repo, digest := ParseRepositoryTag("url:5000/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "url:5000/repo" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
|
|
| 149 |
- t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "url:5000/repo", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
|
|
| 150 |
- } |
|
| 151 |
-} |
|
| 152 |
- |
|
| 153 | 123 |
func TestParseKeyValueOpt(t *testing.T) {
|
| 154 | 124 |
invalids := map[string]string{
|
| 155 | 125 |
"": "Unable to parse key/value option: ", |
| ... | ... |
@@ -7,6 +7,7 @@ import ( |
| 7 | 7 |
"io" |
| 8 | 8 |
"regexp" |
| 9 | 9 |
"strconv" |
| 10 |
+ "strings" |
|
| 10 | 11 |
|
| 11 | 12 |
"github.com/docker/docker/pkg/random" |
| 12 | 13 |
) |
| ... | ... |
@@ -25,6 +26,9 @@ func IsShortID(id string) bool {
|
| 25 | 25 |
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller |
| 26 | 26 |
// will need to use a langer prefix, or the full-length Id. |
| 27 | 27 |
func TruncateID(id string) string {
|
| 28 |
+ if i := strings.IndexRune(id, ':'); i >= 0 {
|
|
| 29 |
+ id = id[i+1:] |
|
| 30 |
+ } |
|
| 28 | 31 |
trimTo := shortLen |
| 29 | 32 |
if len(id) < shortLen {
|
| 30 | 33 |
trimTo = len(id) |
| ... | ... |
@@ -9,7 +9,7 @@ import ( |
| 9 | 9 |
"strings" |
| 10 | 10 |
|
| 11 | 11 |
"github.com/docker/distribution/reference" |
| 12 |
- "github.com/docker/docker/image" |
|
| 12 |
+ "github.com/docker/docker/image/v1" |
|
| 13 | 13 |
"github.com/docker/docker/opts" |
| 14 | 14 |
flag "github.com/docker/docker/pkg/mflag" |
| 15 | 15 |
) |
| ... | ... |
@@ -216,18 +216,15 @@ func ValidateIndexName(val string) (string, error) {
|
| 216 | 216 |
return val, nil |
| 217 | 217 |
} |
| 218 | 218 |
|
| 219 |
-func validateRemoteName(remoteName string) error {
|
|
| 220 |
- |
|
| 221 |
- if !strings.Contains(remoteName, "/") {
|
|
| 222 |
- |
|
| 219 |
+func validateRemoteName(remoteName reference.Named) error {
|
|
| 220 |
+ remoteNameStr := remoteName.Name() |
|
| 221 |
+ if !strings.Contains(remoteNameStr, "/") {
|
|
| 223 | 222 |
// the repository name must not be a valid image ID |
| 224 |
- if err := image.ValidateID(remoteName); err == nil {
|
|
| 223 |
+ if err := v1.ValidateID(remoteNameStr); err == nil {
|
|
| 225 | 224 |
return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", remoteName)
|
| 226 | 225 |
} |
| 227 | 226 |
} |
| 228 |
- |
|
| 229 |
- _, err := reference.WithName(remoteName) |
|
| 230 |
- return err |
|
| 227 |
+ return nil |
|
| 231 | 228 |
} |
| 232 | 229 |
|
| 233 | 230 |
func validateNoSchema(reposName string) error {
|
| ... | ... |
@@ -239,27 +236,24 @@ func validateNoSchema(reposName string) error {
|
| 239 | 239 |
} |
| 240 | 240 |
|
| 241 | 241 |
// ValidateRepositoryName validates a repository name |
| 242 |
-func ValidateRepositoryName(reposName string) error {
|
|
| 243 |
- _, _, err := loadRepositoryName(reposName, true) |
|
| 242 |
+func ValidateRepositoryName(reposName reference.Named) error {
|
|
| 243 |
+ _, _, err := loadRepositoryName(reposName) |
|
| 244 | 244 |
return err |
| 245 | 245 |
} |
| 246 | 246 |
|
| 247 | 247 |
// loadRepositoryName returns the repo name splitted into index name |
| 248 | 248 |
// and remote repo name. It returns an error if the name is not valid. |
| 249 |
-func loadRepositoryName(reposName string, checkRemoteName bool) (string, string, error) {
|
|
| 250 |
- if err := validateNoSchema(reposName); err != nil {
|
|
| 251 |
- return "", "", err |
|
| 249 |
+func loadRepositoryName(reposName reference.Named) (string, reference.Named, error) {
|
|
| 250 |
+ if err := validateNoSchema(reposName.Name()); err != nil {
|
|
| 251 |
+ return "", nil, err |
|
| 252 | 252 |
} |
| 253 |
- indexName, remoteName := splitReposName(reposName) |
|
| 253 |
+ indexName, remoteName, err := splitReposName(reposName) |
|
| 254 | 254 |
|
| 255 |
- var err error |
|
| 256 | 255 |
if indexName, err = ValidateIndexName(indexName); err != nil {
|
| 257 |
- return "", "", err |
|
| 256 |
+ return "", nil, err |
|
| 258 | 257 |
} |
| 259 |
- if checkRemoteName {
|
|
| 260 |
- if err = validateRemoteName(remoteName); err != nil {
|
|
| 261 |
- return "", "", err |
|
| 262 |
- } |
|
| 258 |
+ if err = validateRemoteName(remoteName); err != nil {
|
|
| 259 |
+ return "", nil, err |
|
| 263 | 260 |
} |
| 264 | 261 |
return indexName, remoteName, nil |
| 265 | 262 |
} |
| ... | ... |
@@ -297,31 +291,36 @@ func (index *IndexInfo) GetAuthConfigKey() string {
|
| 297 | 297 |
} |
| 298 | 298 |
|
| 299 | 299 |
// splitReposName breaks a reposName into an index name and remote name |
| 300 |
-func splitReposName(reposName string) (string, string) {
|
|
| 301 |
- nameParts := strings.SplitN(reposName, "/", 2) |
|
| 302 |
- var indexName, remoteName string |
|
| 303 |
- if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && |
|
| 304 |
- !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
|
|
| 300 |
+func splitReposName(reposName reference.Named) (indexName string, remoteName reference.Named, err error) {
|
|
| 301 |
+ var remoteNameStr string |
|
| 302 |
+ indexName, remoteNameStr = reference.SplitHostname(reposName) |
|
| 303 |
+ if indexName == "" || (!strings.Contains(indexName, ".") && |
|
| 304 |
+ !strings.Contains(indexName, ":") && indexName != "localhost") {
|
|
| 305 | 305 |
// This is a Docker Index repos (ex: samalba/hipache or ubuntu) |
| 306 | 306 |
// 'docker.io' |
| 307 | 307 |
indexName = IndexName |
| 308 | 308 |
remoteName = reposName |
| 309 | 309 |
} else {
|
| 310 |
- indexName = nameParts[0] |
|
| 311 |
- remoteName = nameParts[1] |
|
| 310 |
+ remoteName, err = reference.WithName(remoteNameStr) |
|
| 312 | 311 |
} |
| 313 |
- return indexName, remoteName |
|
| 312 |
+ return |
|
| 314 | 313 |
} |
| 315 | 314 |
|
| 316 | 315 |
// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo |
| 317 |
-func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool) (*RepositoryInfo, error) {
|
|
| 318 |
- indexName, remoteName, err := loadRepositoryName(reposName, !bySearch) |
|
| 319 |
- if err != nil {
|
|
| 316 |
+func (config *ServiceConfig) NewRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
|
|
| 317 |
+ if err := validateNoSchema(reposName.Name()); err != nil {
|
|
| 320 | 318 |
return nil, err |
| 321 | 319 |
} |
| 322 | 320 |
|
| 323 |
- repoInfo := &RepositoryInfo{
|
|
| 324 |
- RemoteName: remoteName, |
|
| 321 |
+ repoInfo := &RepositoryInfo{}
|
|
| 322 |
+ var ( |
|
| 323 |
+ indexName string |
|
| 324 |
+ err error |
|
| 325 |
+ ) |
|
| 326 |
+ |
|
| 327 |
+ indexName, repoInfo.RemoteName, err = loadRepositoryName(reposName) |
|
| 328 |
+ if err != nil {
|
|
| 329 |
+ return nil, err |
|
| 325 | 330 |
} |
| 326 | 331 |
|
| 327 | 332 |
repoInfo.Index, err = config.NewIndexInfo(indexName) |
| ... | ... |
@@ -330,46 +329,47 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool) |
| 330 | 330 |
} |
| 331 | 331 |
|
| 332 | 332 |
if repoInfo.Index.Official {
|
| 333 |
- normalizedName := normalizeLibraryRepoName(repoInfo.RemoteName) |
|
| 333 |
+ repoInfo.LocalName, err = normalizeLibraryRepoName(repoInfo.RemoteName) |
|
| 334 |
+ if err != nil {
|
|
| 335 |
+ return nil, err |
|
| 336 |
+ } |
|
| 337 |
+ repoInfo.RemoteName = repoInfo.LocalName |
|
| 334 | 338 |
|
| 335 |
- repoInfo.LocalName = normalizedName |
|
| 336 |
- repoInfo.RemoteName = normalizedName |
|
| 337 | 339 |
// If the normalized name does not contain a '/' (e.g. "foo") |
| 338 | 340 |
// then it is an official repo. |
| 339 |
- if strings.IndexRune(normalizedName, '/') == -1 {
|
|
| 341 |
+ if strings.IndexRune(repoInfo.RemoteName.Name(), '/') == -1 {
|
|
| 340 | 342 |
repoInfo.Official = true |
| 341 | 343 |
// Fix up remote name for official repos. |
| 342 |
- repoInfo.RemoteName = "library/" + normalizedName |
|
| 344 |
+ repoInfo.RemoteName, err = reference.WithName("library/" + repoInfo.RemoteName.Name())
|
|
| 345 |
+ if err != nil {
|
|
| 346 |
+ return nil, err |
|
| 347 |
+ } |
|
| 343 | 348 |
} |
| 344 | 349 |
|
| 345 |
- repoInfo.CanonicalName = "docker.io/" + repoInfo.RemoteName |
|
| 350 |
+ repoInfo.CanonicalName, err = reference.WithName("docker.io/" + repoInfo.RemoteName.Name())
|
|
| 351 |
+ if err != nil {
|
|
| 352 |
+ return nil, err |
|
| 353 |
+ } |
|
| 346 | 354 |
} else {
|
| 347 |
- repoInfo.LocalName = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName) |
|
| 355 |
+ repoInfo.LocalName, err = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName) |
|
| 356 |
+ if err != nil {
|
|
| 357 |
+ return nil, err |
|
| 358 |
+ } |
|
| 348 | 359 |
repoInfo.CanonicalName = repoInfo.LocalName |
| 349 |
- |
|
| 350 | 360 |
} |
| 351 | 361 |
|
| 352 | 362 |
return repoInfo, nil |
| 353 | 363 |
} |
| 354 | 364 |
|
| 355 |
-// GetSearchTerm special-cases using local name for official index, and |
|
| 356 |
-// remote name for private indexes. |
|
| 357 |
-func (repoInfo *RepositoryInfo) GetSearchTerm() string {
|
|
| 358 |
- if repoInfo.Index.Official {
|
|
| 359 |
- return repoInfo.LocalName |
|
| 360 |
- } |
|
| 361 |
- return repoInfo.RemoteName |
|
| 362 |
-} |
|
| 363 |
- |
|
| 364 | 365 |
// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but |
| 365 | 366 |
// lacks registry configuration. |
| 366 |
-func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
|
|
| 367 |
- return emptyServiceConfig.NewRepositoryInfo(reposName, false) |
|
| 367 |
+func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
|
|
| 368 |
+ return emptyServiceConfig.NewRepositoryInfo(reposName) |
|
| 368 | 369 |
} |
| 369 | 370 |
|
| 370 |
-// ParseIndexInfo will use repository name to get back an indexInfo. |
|
| 371 |
-func ParseIndexInfo(reposName string) (*IndexInfo, error) {
|
|
| 372 |
- indexName, _ := splitReposName(reposName) |
|
| 371 |
+// ParseSearchIndexInfo will use repository name to get back an indexInfo. |
|
| 372 |
+func ParseSearchIndexInfo(reposName string) (*IndexInfo, error) {
|
|
| 373 |
+ indexName, _ := splitReposSearchTerm(reposName) |
|
| 373 | 374 |
|
| 374 | 375 |
indexInfo, err := emptyServiceConfig.NewIndexInfo(indexName) |
| 375 | 376 |
if err != nil {
|
| ... | ... |
@@ -378,12 +378,12 @@ func ParseIndexInfo(reposName string) (*IndexInfo, error) {
|
| 378 | 378 |
return indexInfo, nil |
| 379 | 379 |
} |
| 380 | 380 |
|
| 381 |
-// NormalizeLocalName transforms a repository name into a normalize LocalName |
|
| 381 |
+// NormalizeLocalName transforms a repository name into a normalized LocalName |
|
| 382 | 382 |
// Passes through the name without transformation on error (image id, etc) |
| 383 | 383 |
// It does not use the repository info because we don't want to load |
| 384 | 384 |
// the repository index and do request over the network. |
| 385 |
-func NormalizeLocalName(name string) string {
|
|
| 386 |
- indexName, remoteName, err := loadRepositoryName(name, true) |
|
| 385 |
+func NormalizeLocalName(name reference.Named) reference.Named {
|
|
| 386 |
+ indexName, remoteName, err := loadRepositoryName(name) |
|
| 387 | 387 |
if err != nil {
|
| 388 | 388 |
return name |
| 389 | 389 |
} |
| ... | ... |
@@ -395,23 +395,52 @@ func NormalizeLocalName(name string) string {
|
| 395 | 395 |
} |
| 396 | 396 |
|
| 397 | 397 |
if officialIndex {
|
| 398 |
- return normalizeLibraryRepoName(remoteName) |
|
| 398 |
+ localName, err := normalizeLibraryRepoName(remoteName) |
|
| 399 |
+ if err != nil {
|
|
| 400 |
+ return name |
|
| 401 |
+ } |
|
| 402 |
+ return localName |
|
| 399 | 403 |
} |
| 400 |
- return localNameFromRemote(indexName, remoteName) |
|
| 404 |
+ localName, err := localNameFromRemote(indexName, remoteName) |
|
| 405 |
+ if err != nil {
|
|
| 406 |
+ return name |
|
| 407 |
+ } |
|
| 408 |
+ return localName |
|
| 401 | 409 |
} |
| 402 | 410 |
|
| 403 | 411 |
// normalizeLibraryRepoName removes the library prefix from |
| 404 | 412 |
// the repository name for official repos. |
| 405 |
-func normalizeLibraryRepoName(name string) string {
|
|
| 406 |
- if strings.HasPrefix(name, "library/") {
|
|
| 413 |
+func normalizeLibraryRepoName(name reference.Named) (reference.Named, error) {
|
|
| 414 |
+ if strings.HasPrefix(name.Name(), "library/") {
|
|
| 407 | 415 |
// If pull "library/foo", it's stored locally under "foo" |
| 408 |
- name = strings.SplitN(name, "/", 2)[1] |
|
| 416 |
+ return reference.WithName(strings.SplitN(name.Name(), "/", 2)[1]) |
|
| 409 | 417 |
} |
| 410 |
- return name |
|
| 418 |
+ return name, nil |
|
| 411 | 419 |
} |
| 412 | 420 |
|
| 413 | 421 |
// localNameFromRemote combines the index name and the repo remote name |
| 414 | 422 |
// to generate a repo local name. |
| 415 |
-func localNameFromRemote(indexName, remoteName string) string {
|
|
| 416 |
- return indexName + "/" + remoteName |
|
| 423 |
+func localNameFromRemote(indexName string, remoteName reference.Named) (reference.Named, error) {
|
|
| 424 |
+ return reference.WithName(indexName + "/" + remoteName.Name()) |
|
| 425 |
+} |
|
| 426 |
+ |
|
| 427 |
+// NormalizeLocalReference transforms a reference to use a normalized LocalName |
|
| 428 |
+// for the name poriton. Passes through the reference without transformation on |
|
| 429 |
+// error. |
|
| 430 |
+func NormalizeLocalReference(ref reference.Named) reference.Named {
|
|
| 431 |
+ localName := NormalizeLocalName(ref) |
|
| 432 |
+ if tagged, isTagged := ref.(reference.Tagged); isTagged {
|
|
| 433 |
+ newRef, err := reference.WithTag(localName, tagged.Tag()) |
|
| 434 |
+ if err != nil {
|
|
| 435 |
+ return ref |
|
| 436 |
+ } |
|
| 437 |
+ return newRef |
|
| 438 |
+ } else if digested, isDigested := ref.(reference.Digested); isDigested {
|
|
| 439 |
+ newRef, err := reference.WithDigest(localName, digested.Digest()) |
|
| 440 |
+ if err != nil {
|
|
| 441 |
+ return ref |
|
| 442 |
+ } |
|
| 443 |
+ return newRef |
|
| 444 |
+ } |
|
| 445 |
+ return localName |
|
| 417 | 446 |
} |
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"testing" |
| 16 | 16 |
"time" |
| 17 | 17 |
|
| 18 |
+ "github.com/docker/distribution/reference" |
|
| 18 | 19 |
"github.com/docker/docker/opts" |
| 19 | 20 |
"github.com/gorilla/mux" |
| 20 | 21 |
|
| ... | ... |
@@ -349,15 +350,19 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
|
| 349 | 349 |
if !requiresAuth(w, r) {
|
| 350 | 350 |
return |
| 351 | 351 |
} |
| 352 |
- repositoryName := mux.Vars(r)["repository"] |
|
| 352 |
+ repositoryName, err := reference.WithName(mux.Vars(r)["repository"]) |
|
| 353 |
+ if err != nil {
|
|
| 354 |
+ apiError(w, "Could not parse repository", 400) |
|
| 355 |
+ return |
|
| 356 |
+ } |
|
| 353 | 357 |
repositoryName = NormalizeLocalName(repositoryName) |
| 354 |
- tags, exists := testRepositories[repositoryName] |
|
| 358 |
+ tags, exists := testRepositories[repositoryName.String()] |
|
| 355 | 359 |
if !exists {
|
| 356 | 360 |
apiError(w, "Repository not found", 404) |
| 357 | 361 |
return |
| 358 | 362 |
} |
| 359 | 363 |
if r.Method == "DELETE" {
|
| 360 |
- delete(testRepositories, repositoryName) |
|
| 364 |
+ delete(testRepositories, repositoryName.String()) |
|
| 361 | 365 |
writeResponse(w, true, 200) |
| 362 | 366 |
return |
| 363 | 367 |
} |
| ... | ... |
@@ -369,10 +374,14 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) {
|
| 369 | 369 |
return |
| 370 | 370 |
} |
| 371 | 371 |
vars := mux.Vars(r) |
| 372 |
- repositoryName := vars["repository"] |
|
| 372 |
+ repositoryName, err := reference.WithName(vars["repository"]) |
|
| 373 |
+ if err != nil {
|
|
| 374 |
+ apiError(w, "Could not parse repository", 400) |
|
| 375 |
+ return |
|
| 376 |
+ } |
|
| 373 | 377 |
repositoryName = NormalizeLocalName(repositoryName) |
| 374 | 378 |
tagName := vars["tag"] |
| 375 |
- tags, exists := testRepositories[repositoryName] |
|
| 379 |
+ tags, exists := testRepositories[repositoryName.String()] |
|
| 376 | 380 |
if !exists {
|
| 377 | 381 |
apiError(w, "Repository not found", 404) |
| 378 | 382 |
return |
| ... | ... |
@@ -390,13 +399,17 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) {
|
| 390 | 390 |
return |
| 391 | 391 |
} |
| 392 | 392 |
vars := mux.Vars(r) |
| 393 |
- repositoryName := vars["repository"] |
|
| 393 |
+ repositoryName, err := reference.WithName(vars["repository"]) |
|
| 394 |
+ if err != nil {
|
|
| 395 |
+ apiError(w, "Could not parse repository", 400) |
|
| 396 |
+ return |
|
| 397 |
+ } |
|
| 394 | 398 |
repositoryName = NormalizeLocalName(repositoryName) |
| 395 | 399 |
tagName := vars["tag"] |
| 396 |
- tags, exists := testRepositories[repositoryName] |
|
| 400 |
+ tags, exists := testRepositories[repositoryName.String()] |
|
| 397 | 401 |
if !exists {
|
| 398 |
- tags := make(map[string]string) |
|
| 399 |
- testRepositories[repositoryName] = tags |
|
| 402 |
+ tags = make(map[string]string) |
|
| 403 |
+ testRepositories[repositoryName.String()] = tags |
|
| 400 | 404 |
} |
| 401 | 405 |
tagValue := "" |
| 402 | 406 |
readJSON(r, tagValue) |
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
"strings" |
| 9 | 9 |
"testing" |
| 10 | 10 |
|
| 11 |
+ "github.com/docker/distribution/reference" |
|
| 11 | 12 |
"github.com/docker/distribution/registry/client/transport" |
| 12 | 13 |
"github.com/docker/docker/cliconfig" |
| 13 | 14 |
) |
| ... | ... |
@@ -214,13 +215,21 @@ func TestGetRemoteImageLayer(t *testing.T) {
|
| 214 | 214 |
|
| 215 | 215 |
func TestGetRemoteTag(t *testing.T) {
|
| 216 | 216 |
r := spawnTestRegistrySession(t) |
| 217 |
- tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, REPO, "test")
|
|
| 217 |
+ repoRef, err := reference.ParseNamed(REPO) |
|
| 218 |
+ if err != nil {
|
|
| 219 |
+ t.Fatal(err) |
|
| 220 |
+ } |
|
| 221 |
+ tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, repoRef, "test")
|
|
| 218 | 222 |
if err != nil {
|
| 219 | 223 |
t.Fatal(err) |
| 220 | 224 |
} |
| 221 | 225 |
assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID) |
| 222 | 226 |
|
| 223 |
- _, err = r.GetRemoteTag([]string{makeURL("/v1/")}, "foo42/baz", "foo")
|
|
| 227 |
+ bazRef, err := reference.ParseNamed("foo42/baz")
|
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ t.Fatal(err) |
|
| 230 |
+ } |
|
| 231 |
+ _, err = r.GetRemoteTag([]string{makeURL("/v1/")}, bazRef, "foo")
|
|
| 224 | 232 |
if err != ErrRepoNotFound {
|
| 225 | 233 |
t.Fatal("Expected ErrRepoNotFound error when fetching tag for bogus repo")
|
| 226 | 234 |
} |
| ... | ... |
@@ -228,7 +237,11 @@ func TestGetRemoteTag(t *testing.T) {
|
| 228 | 228 |
|
| 229 | 229 |
func TestGetRemoteTags(t *testing.T) {
|
| 230 | 230 |
r := spawnTestRegistrySession(t) |
| 231 |
- tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO)
|
|
| 231 |
+ repoRef, err := reference.ParseNamed(REPO) |
|
| 232 |
+ if err != nil {
|
|
| 233 |
+ t.Fatal(err) |
|
| 234 |
+ } |
|
| 235 |
+ tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, repoRef)
|
|
| 232 | 236 |
if err != nil {
|
| 233 | 237 |
t.Fatal(err) |
| 234 | 238 |
} |
| ... | ... |
@@ -236,7 +249,11 @@ func TestGetRemoteTags(t *testing.T) {
|
| 236 | 236 |
assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID) |
| 237 | 237 |
assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID) |
| 238 | 238 |
|
| 239 |
- _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz")
|
|
| 239 |
+ bazRef, err := reference.ParseNamed("foo42/baz")
|
|
| 240 |
+ if err != nil {
|
|
| 241 |
+ t.Fatal(err) |
|
| 242 |
+ } |
|
| 243 |
+ _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, bazRef)
|
|
| 240 | 244 |
if err != ErrRepoNotFound {
|
| 241 | 245 |
t.Fatal("Expected ErrRepoNotFound error when fetching tags for bogus repo")
|
| 242 | 246 |
} |
| ... | ... |
@@ -249,7 +266,11 @@ func TestGetRepositoryData(t *testing.T) {
|
| 249 | 249 |
t.Fatal(err) |
| 250 | 250 |
} |
| 251 | 251 |
host := "http://" + parsedURL.Host + "/v1/" |
| 252 |
- data, err := r.GetRepositoryData("foo42/bar")
|
|
| 252 |
+ repoRef, err := reference.ParseNamed(REPO) |
|
| 253 |
+ if err != nil {
|
|
| 254 |
+ t.Fatal(err) |
|
| 255 |
+ } |
|
| 256 |
+ data, err := r.GetRepositoryData(repoRef) |
|
| 253 | 257 |
if err != nil {
|
| 254 | 258 |
t.Fatal(err) |
| 255 | 259 |
} |
| ... | ... |
@@ -315,29 +336,41 @@ func TestValidateRepositoryName(t *testing.T) {
|
| 315 | 315 |
} |
| 316 | 316 |
|
| 317 | 317 |
for _, name := range invalidRepoNames {
|
| 318 |
- err := ValidateRepositoryName(name) |
|
| 319 |
- assertNotEqual(t, err, nil, "Expected invalid repo name: "+name) |
|
| 318 |
+ named, err := reference.WithName(name) |
|
| 319 |
+ if err == nil {
|
|
| 320 |
+ err := ValidateRepositoryName(named) |
|
| 321 |
+ assertNotEqual(t, err, nil, "Expected invalid repo name: "+name) |
|
| 322 |
+ } |
|
| 320 | 323 |
} |
| 321 | 324 |
|
| 322 | 325 |
for _, name := range validRepoNames {
|
| 323 |
- err := ValidateRepositoryName(name) |
|
| 326 |
+ named, err := reference.WithName(name) |
|
| 327 |
+ if err != nil {
|
|
| 328 |
+ t.Fatalf("could not parse valid name: %s", name)
|
|
| 329 |
+ } |
|
| 330 |
+ err = ValidateRepositoryName(named) |
|
| 324 | 331 |
assertEqual(t, err, nil, "Expected valid repo name: "+name) |
| 325 | 332 |
} |
| 326 |
- |
|
| 327 |
- err := ValidateRepositoryName(invalidRepoNames[0]) |
|
| 328 |
- assertEqual(t, err, ErrInvalidRepositoryName, "Expected ErrInvalidRepositoryName: "+invalidRepoNames[0]) |
|
| 329 | 333 |
} |
| 330 | 334 |
|
| 331 | 335 |
func TestParseRepositoryInfo(t *testing.T) {
|
| 336 |
+ withName := func(name string) reference.Named {
|
|
| 337 |
+ named, err := reference.WithName(name) |
|
| 338 |
+ if err != nil {
|
|
| 339 |
+ t.Fatalf("could not parse reference %s", name)
|
|
| 340 |
+ } |
|
| 341 |
+ return named |
|
| 342 |
+ } |
|
| 343 |
+ |
|
| 332 | 344 |
expectedRepoInfos := map[string]RepositoryInfo{
|
| 333 | 345 |
"fooo/bar": {
|
| 334 | 346 |
Index: &IndexInfo{
|
| 335 | 347 |
Name: IndexName, |
| 336 | 348 |
Official: true, |
| 337 | 349 |
}, |
| 338 |
- RemoteName: "fooo/bar", |
|
| 339 |
- LocalName: "fooo/bar", |
|
| 340 |
- CanonicalName: "docker.io/fooo/bar", |
|
| 350 |
+ RemoteName: withName("fooo/bar"),
|
|
| 351 |
+ LocalName: withName("fooo/bar"),
|
|
| 352 |
+ CanonicalName: withName("docker.io/fooo/bar"),
|
|
| 341 | 353 |
Official: false, |
| 342 | 354 |
}, |
| 343 | 355 |
"library/ubuntu": {
|
| ... | ... |
@@ -345,9 +378,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 345 | 345 |
Name: IndexName, |
| 346 | 346 |
Official: true, |
| 347 | 347 |
}, |
| 348 |
- RemoteName: "library/ubuntu", |
|
| 349 |
- LocalName: "ubuntu", |
|
| 350 |
- CanonicalName: "docker.io/library/ubuntu", |
|
| 348 |
+ RemoteName: withName("library/ubuntu"),
|
|
| 349 |
+ LocalName: withName("ubuntu"),
|
|
| 350 |
+ CanonicalName: withName("docker.io/library/ubuntu"),
|
|
| 351 | 351 |
Official: true, |
| 352 | 352 |
}, |
| 353 | 353 |
"nonlibrary/ubuntu": {
|
| ... | ... |
@@ -355,9 +388,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 355 | 355 |
Name: IndexName, |
| 356 | 356 |
Official: true, |
| 357 | 357 |
}, |
| 358 |
- RemoteName: "nonlibrary/ubuntu", |
|
| 359 |
- LocalName: "nonlibrary/ubuntu", |
|
| 360 |
- CanonicalName: "docker.io/nonlibrary/ubuntu", |
|
| 358 |
+ RemoteName: withName("nonlibrary/ubuntu"),
|
|
| 359 |
+ LocalName: withName("nonlibrary/ubuntu"),
|
|
| 360 |
+ CanonicalName: withName("docker.io/nonlibrary/ubuntu"),
|
|
| 361 | 361 |
Official: false, |
| 362 | 362 |
}, |
| 363 | 363 |
"ubuntu": {
|
| ... | ... |
@@ -365,9 +398,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 365 | 365 |
Name: IndexName, |
| 366 | 366 |
Official: true, |
| 367 | 367 |
}, |
| 368 |
- RemoteName: "library/ubuntu", |
|
| 369 |
- LocalName: "ubuntu", |
|
| 370 |
- CanonicalName: "docker.io/library/ubuntu", |
|
| 368 |
+ RemoteName: withName("library/ubuntu"),
|
|
| 369 |
+ LocalName: withName("ubuntu"),
|
|
| 370 |
+ CanonicalName: withName("docker.io/library/ubuntu"),
|
|
| 371 | 371 |
Official: true, |
| 372 | 372 |
}, |
| 373 | 373 |
"other/library": {
|
| ... | ... |
@@ -375,9 +408,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 375 | 375 |
Name: IndexName, |
| 376 | 376 |
Official: true, |
| 377 | 377 |
}, |
| 378 |
- RemoteName: "other/library", |
|
| 379 |
- LocalName: "other/library", |
|
| 380 |
- CanonicalName: "docker.io/other/library", |
|
| 378 |
+ RemoteName: withName("other/library"),
|
|
| 379 |
+ LocalName: withName("other/library"),
|
|
| 380 |
+ CanonicalName: withName("docker.io/other/library"),
|
|
| 381 | 381 |
Official: false, |
| 382 | 382 |
}, |
| 383 | 383 |
"127.0.0.1:8000/private/moonbase": {
|
| ... | ... |
@@ -385,9 +418,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 385 | 385 |
Name: "127.0.0.1:8000", |
| 386 | 386 |
Official: false, |
| 387 | 387 |
}, |
| 388 |
- RemoteName: "private/moonbase", |
|
| 389 |
- LocalName: "127.0.0.1:8000/private/moonbase", |
|
| 390 |
- CanonicalName: "127.0.0.1:8000/private/moonbase", |
|
| 388 |
+ RemoteName: withName("private/moonbase"),
|
|
| 389 |
+ LocalName: withName("127.0.0.1:8000/private/moonbase"),
|
|
| 390 |
+ CanonicalName: withName("127.0.0.1:8000/private/moonbase"),
|
|
| 391 | 391 |
Official: false, |
| 392 | 392 |
}, |
| 393 | 393 |
"127.0.0.1:8000/privatebase": {
|
| ... | ... |
@@ -395,9 +428,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 395 | 395 |
Name: "127.0.0.1:8000", |
| 396 | 396 |
Official: false, |
| 397 | 397 |
}, |
| 398 |
- RemoteName: "privatebase", |
|
| 399 |
- LocalName: "127.0.0.1:8000/privatebase", |
|
| 400 |
- CanonicalName: "127.0.0.1:8000/privatebase", |
|
| 398 |
+ RemoteName: withName("privatebase"),
|
|
| 399 |
+ LocalName: withName("127.0.0.1:8000/privatebase"),
|
|
| 400 |
+ CanonicalName: withName("127.0.0.1:8000/privatebase"),
|
|
| 401 | 401 |
Official: false, |
| 402 | 402 |
}, |
| 403 | 403 |
"localhost:8000/private/moonbase": {
|
| ... | ... |
@@ -405,9 +438,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 405 | 405 |
Name: "localhost:8000", |
| 406 | 406 |
Official: false, |
| 407 | 407 |
}, |
| 408 |
- RemoteName: "private/moonbase", |
|
| 409 |
- LocalName: "localhost:8000/private/moonbase", |
|
| 410 |
- CanonicalName: "localhost:8000/private/moonbase", |
|
| 408 |
+ RemoteName: withName("private/moonbase"),
|
|
| 409 |
+ LocalName: withName("localhost:8000/private/moonbase"),
|
|
| 410 |
+ CanonicalName: withName("localhost:8000/private/moonbase"),
|
|
| 411 | 411 |
Official: false, |
| 412 | 412 |
}, |
| 413 | 413 |
"localhost:8000/privatebase": {
|
| ... | ... |
@@ -415,9 +448,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 415 | 415 |
Name: "localhost:8000", |
| 416 | 416 |
Official: false, |
| 417 | 417 |
}, |
| 418 |
- RemoteName: "privatebase", |
|
| 419 |
- LocalName: "localhost:8000/privatebase", |
|
| 420 |
- CanonicalName: "localhost:8000/privatebase", |
|
| 418 |
+ RemoteName: withName("privatebase"),
|
|
| 419 |
+ LocalName: withName("localhost:8000/privatebase"),
|
|
| 420 |
+ CanonicalName: withName("localhost:8000/privatebase"),
|
|
| 421 | 421 |
Official: false, |
| 422 | 422 |
}, |
| 423 | 423 |
"example.com/private/moonbase": {
|
| ... | ... |
@@ -425,9 +458,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 425 | 425 |
Name: "example.com", |
| 426 | 426 |
Official: false, |
| 427 | 427 |
}, |
| 428 |
- RemoteName: "private/moonbase", |
|
| 429 |
- LocalName: "example.com/private/moonbase", |
|
| 430 |
- CanonicalName: "example.com/private/moonbase", |
|
| 428 |
+ RemoteName: withName("private/moonbase"),
|
|
| 429 |
+ LocalName: withName("example.com/private/moonbase"),
|
|
| 430 |
+ CanonicalName: withName("example.com/private/moonbase"),
|
|
| 431 | 431 |
Official: false, |
| 432 | 432 |
}, |
| 433 | 433 |
"example.com/privatebase": {
|
| ... | ... |
@@ -435,9 +468,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 435 | 435 |
Name: "example.com", |
| 436 | 436 |
Official: false, |
| 437 | 437 |
}, |
| 438 |
- RemoteName: "privatebase", |
|
| 439 |
- LocalName: "example.com/privatebase", |
|
| 440 |
- CanonicalName: "example.com/privatebase", |
|
| 438 |
+ RemoteName: withName("privatebase"),
|
|
| 439 |
+ LocalName: withName("example.com/privatebase"),
|
|
| 440 |
+ CanonicalName: withName("example.com/privatebase"),
|
|
| 441 | 441 |
Official: false, |
| 442 | 442 |
}, |
| 443 | 443 |
"example.com:8000/private/moonbase": {
|
| ... | ... |
@@ -445,9 +478,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 445 | 445 |
Name: "example.com:8000", |
| 446 | 446 |
Official: false, |
| 447 | 447 |
}, |
| 448 |
- RemoteName: "private/moonbase", |
|
| 449 |
- LocalName: "example.com:8000/private/moonbase", |
|
| 450 |
- CanonicalName: "example.com:8000/private/moonbase", |
|
| 448 |
+ RemoteName: withName("private/moonbase"),
|
|
| 449 |
+ LocalName: withName("example.com:8000/private/moonbase"),
|
|
| 450 |
+ CanonicalName: withName("example.com:8000/private/moonbase"),
|
|
| 451 | 451 |
Official: false, |
| 452 | 452 |
}, |
| 453 | 453 |
"example.com:8000/privatebase": {
|
| ... | ... |
@@ -455,9 +488,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 455 | 455 |
Name: "example.com:8000", |
| 456 | 456 |
Official: false, |
| 457 | 457 |
}, |
| 458 |
- RemoteName: "privatebase", |
|
| 459 |
- LocalName: "example.com:8000/privatebase", |
|
| 460 |
- CanonicalName: "example.com:8000/privatebase", |
|
| 458 |
+ RemoteName: withName("privatebase"),
|
|
| 459 |
+ LocalName: withName("example.com:8000/privatebase"),
|
|
| 460 |
+ CanonicalName: withName("example.com:8000/privatebase"),
|
|
| 461 | 461 |
Official: false, |
| 462 | 462 |
}, |
| 463 | 463 |
"localhost/private/moonbase": {
|
| ... | ... |
@@ -465,9 +498,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 465 | 465 |
Name: "localhost", |
| 466 | 466 |
Official: false, |
| 467 | 467 |
}, |
| 468 |
- RemoteName: "private/moonbase", |
|
| 469 |
- LocalName: "localhost/private/moonbase", |
|
| 470 |
- CanonicalName: "localhost/private/moonbase", |
|
| 468 |
+ RemoteName: withName("private/moonbase"),
|
|
| 469 |
+ LocalName: withName("localhost/private/moonbase"),
|
|
| 470 |
+ CanonicalName: withName("localhost/private/moonbase"),
|
|
| 471 | 471 |
Official: false, |
| 472 | 472 |
}, |
| 473 | 473 |
"localhost/privatebase": {
|
| ... | ... |
@@ -475,9 +508,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 475 | 475 |
Name: "localhost", |
| 476 | 476 |
Official: false, |
| 477 | 477 |
}, |
| 478 |
- RemoteName: "privatebase", |
|
| 479 |
- LocalName: "localhost/privatebase", |
|
| 480 |
- CanonicalName: "localhost/privatebase", |
|
| 478 |
+ RemoteName: withName("privatebase"),
|
|
| 479 |
+ LocalName: withName("localhost/privatebase"),
|
|
| 480 |
+ CanonicalName: withName("localhost/privatebase"),
|
|
| 481 | 481 |
Official: false, |
| 482 | 482 |
}, |
| 483 | 483 |
IndexName + "/public/moonbase": {
|
| ... | ... |
@@ -485,9 +518,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 485 | 485 |
Name: IndexName, |
| 486 | 486 |
Official: true, |
| 487 | 487 |
}, |
| 488 |
- RemoteName: "public/moonbase", |
|
| 489 |
- LocalName: "public/moonbase", |
|
| 490 |
- CanonicalName: "docker.io/public/moonbase", |
|
| 488 |
+ RemoteName: withName("public/moonbase"),
|
|
| 489 |
+ LocalName: withName("public/moonbase"),
|
|
| 490 |
+ CanonicalName: withName("docker.io/public/moonbase"),
|
|
| 491 | 491 |
Official: false, |
| 492 | 492 |
}, |
| 493 | 493 |
"index." + IndexName + "/public/moonbase": {
|
| ... | ... |
@@ -495,9 +528,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 495 | 495 |
Name: IndexName, |
| 496 | 496 |
Official: true, |
| 497 | 497 |
}, |
| 498 |
- RemoteName: "public/moonbase", |
|
| 499 |
- LocalName: "public/moonbase", |
|
| 500 |
- CanonicalName: "docker.io/public/moonbase", |
|
| 498 |
+ RemoteName: withName("public/moonbase"),
|
|
| 499 |
+ LocalName: withName("public/moonbase"),
|
|
| 500 |
+ CanonicalName: withName("docker.io/public/moonbase"),
|
|
| 501 | 501 |
Official: false, |
| 502 | 502 |
}, |
| 503 | 503 |
"ubuntu-12.04-base": {
|
| ... | ... |
@@ -505,9 +538,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 505 | 505 |
Name: IndexName, |
| 506 | 506 |
Official: true, |
| 507 | 507 |
}, |
| 508 |
- RemoteName: "library/ubuntu-12.04-base", |
|
| 509 |
- LocalName: "ubuntu-12.04-base", |
|
| 510 |
- CanonicalName: "docker.io/library/ubuntu-12.04-base", |
|
| 508 |
+ RemoteName: withName("library/ubuntu-12.04-base"),
|
|
| 509 |
+ LocalName: withName("ubuntu-12.04-base"),
|
|
| 510 |
+ CanonicalName: withName("docker.io/library/ubuntu-12.04-base"),
|
|
| 511 | 511 |
Official: true, |
| 512 | 512 |
}, |
| 513 | 513 |
IndexName + "/ubuntu-12.04-base": {
|
| ... | ... |
@@ -515,9 +548,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 515 | 515 |
Name: IndexName, |
| 516 | 516 |
Official: true, |
| 517 | 517 |
}, |
| 518 |
- RemoteName: "library/ubuntu-12.04-base", |
|
| 519 |
- LocalName: "ubuntu-12.04-base", |
|
| 520 |
- CanonicalName: "docker.io/library/ubuntu-12.04-base", |
|
| 518 |
+ RemoteName: withName("library/ubuntu-12.04-base"),
|
|
| 519 |
+ LocalName: withName("ubuntu-12.04-base"),
|
|
| 520 |
+ CanonicalName: withName("docker.io/library/ubuntu-12.04-base"),
|
|
| 521 | 521 |
Official: true, |
| 522 | 522 |
}, |
| 523 | 523 |
"index." + IndexName + "/ubuntu-12.04-base": {
|
| ... | ... |
@@ -525,22 +558,27 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 525 | 525 |
Name: IndexName, |
| 526 | 526 |
Official: true, |
| 527 | 527 |
}, |
| 528 |
- RemoteName: "library/ubuntu-12.04-base", |
|
| 529 |
- LocalName: "ubuntu-12.04-base", |
|
| 530 |
- CanonicalName: "docker.io/library/ubuntu-12.04-base", |
|
| 528 |
+ RemoteName: withName("library/ubuntu-12.04-base"),
|
|
| 529 |
+ LocalName: withName("ubuntu-12.04-base"),
|
|
| 530 |
+ CanonicalName: withName("docker.io/library/ubuntu-12.04-base"),
|
|
| 531 | 531 |
Official: true, |
| 532 | 532 |
}, |
| 533 | 533 |
} |
| 534 | 534 |
|
| 535 | 535 |
for reposName, expectedRepoInfo := range expectedRepoInfos {
|
| 536 |
- repoInfo, err := ParseRepositoryInfo(reposName) |
|
| 536 |
+ named, err := reference.WithName(reposName) |
|
| 537 |
+ if err != nil {
|
|
| 538 |
+ t.Error(err) |
|
| 539 |
+ } |
|
| 540 |
+ |
|
| 541 |
+ repoInfo, err := ParseRepositoryInfo(named) |
|
| 537 | 542 |
if err != nil {
|
| 538 | 543 |
t.Error(err) |
| 539 | 544 |
} else {
|
| 540 | 545 |
checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName) |
| 541 |
- checkEqual(t, repoInfo.RemoteName, expectedRepoInfo.RemoteName, reposName) |
|
| 542 |
- checkEqual(t, repoInfo.LocalName, expectedRepoInfo.LocalName, reposName) |
|
| 543 |
- checkEqual(t, repoInfo.CanonicalName, expectedRepoInfo.CanonicalName, reposName) |
|
| 546 |
+ checkEqual(t, repoInfo.RemoteName.String(), expectedRepoInfo.RemoteName.String(), reposName) |
|
| 547 |
+ checkEqual(t, repoInfo.LocalName.String(), expectedRepoInfo.LocalName.String(), reposName) |
|
| 548 |
+ checkEqual(t, repoInfo.CanonicalName.String(), expectedRepoInfo.CanonicalName.String(), reposName) |
|
| 544 | 549 |
checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName) |
| 545 | 550 |
checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName) |
| 546 | 551 |
} |
| ... | ... |
@@ -687,8 +725,11 @@ func TestMirrorEndpointLookup(t *testing.T) {
|
| 687 | 687 |
return false |
| 688 | 688 |
} |
| 689 | 689 |
s := Service{Config: makeServiceConfig([]string{"my.mirror"}, nil)}
|
| 690 |
- imageName := IndexName + "/test/image" |
|
| 691 | 690 |
|
| 691 |
+ imageName, err := reference.WithName(IndexName + "/test/image") |
|
| 692 |
+ if err != nil {
|
|
| 693 |
+ t.Error(err) |
|
| 694 |
+ } |
|
| 692 | 695 |
pushAPIEndpoints, err := s.LookupPushEndpoints(imageName) |
| 693 | 696 |
if err != nil {
|
| 694 | 697 |
t.Fatal(err) |
| ... | ... |
@@ -708,7 +749,11 @@ func TestMirrorEndpointLookup(t *testing.T) {
|
| 708 | 708 |
|
| 709 | 709 |
func TestPushRegistryTag(t *testing.T) {
|
| 710 | 710 |
r := spawnTestRegistrySession(t) |
| 711 |
- err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"))
|
|
| 711 |
+ repoRef, err := reference.ParseNamed(REPO) |
|
| 712 |
+ if err != nil {
|
|
| 713 |
+ t.Fatal(err) |
|
| 714 |
+ } |
|
| 715 |
+ err = r.PushRegistryTag(repoRef, imageID, "stable", makeURL("/v1/"))
|
|
| 712 | 716 |
if err != nil {
|
| 713 | 717 |
t.Fatal(err) |
| 714 | 718 |
} |
| ... | ... |
@@ -726,14 +771,18 @@ func TestPushImageJSONIndex(t *testing.T) {
|
| 726 | 726 |
Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", |
| 727 | 727 |
}, |
| 728 | 728 |
} |
| 729 |
- repoData, err := r.PushImageJSONIndex("foo42/bar", imgData, false, nil)
|
|
| 729 |
+ repoRef, err := reference.ParseNamed(REPO) |
|
| 730 |
+ if err != nil {
|
|
| 731 |
+ t.Fatal(err) |
|
| 732 |
+ } |
|
| 733 |
+ repoData, err := r.PushImageJSONIndex(repoRef, imgData, false, nil) |
|
| 730 | 734 |
if err != nil {
|
| 731 | 735 |
t.Fatal(err) |
| 732 | 736 |
} |
| 733 | 737 |
if repoData == nil {
|
| 734 | 738 |
t.Fatal("Expected RepositoryData object")
|
| 735 | 739 |
} |
| 736 |
- repoData, err = r.PushImageJSONIndex("foo42/bar", imgData, true, []string{r.indexEndpoint.String()})
|
|
| 740 |
+ repoData, err = r.PushImageJSONIndex(repoRef, imgData, true, []string{r.indexEndpoint.String()})
|
|
| 737 | 741 |
if err != nil {
|
| 738 | 742 |
t.Fatal(err) |
| 739 | 743 |
} |
| ... | ... |
@@ -781,7 +830,11 @@ func TestValidRemoteName(t *testing.T) {
|
| 781 | 781 |
"dock__er/docker", |
| 782 | 782 |
} |
| 783 | 783 |
for _, repositoryName := range validRepositoryNames {
|
| 784 |
- if err := validateRemoteName(repositoryName); err != nil {
|
|
| 784 |
+ repositoryRef, err := reference.WithName(repositoryName) |
|
| 785 |
+ if err != nil {
|
|
| 786 |
+ t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
|
|
| 787 |
+ } |
|
| 788 |
+ if err := validateRemoteName(repositoryRef); err != nil {
|
|
| 785 | 789 |
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
|
| 786 | 790 |
} |
| 787 | 791 |
} |
| ... | ... |
@@ -818,7 +871,11 @@ func TestValidRemoteName(t *testing.T) {
|
| 818 | 818 |
"this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker", |
| 819 | 819 |
} |
| 820 | 820 |
for _, repositoryName := range invalidRepositoryNames {
|
| 821 |
- if err := validateRemoteName(repositoryName); err == nil {
|
|
| 821 |
+ repositoryRef, err := reference.ParseNamed(repositoryName) |
|
| 822 |
+ if err != nil {
|
|
| 823 |
+ continue |
|
| 824 |
+ } |
|
| 825 |
+ if err := validateRemoteName(repositoryRef); err == nil {
|
|
| 822 | 826 |
t.Errorf("Repository name should be invalid: %v", repositoryName)
|
| 823 | 827 |
} |
| 824 | 828 |
} |
| ... | ... |
@@ -4,7 +4,9 @@ import ( |
| 4 | 4 |
"crypto/tls" |
| 5 | 5 |
"net/http" |
| 6 | 6 |
"net/url" |
| 7 |
+ "strings" |
|
| 7 | 8 |
|
| 9 |
+ "github.com/docker/distribution/reference" |
|
| 8 | 10 |
"github.com/docker/distribution/registry/client/auth" |
| 9 | 11 |
"github.com/docker/docker/cliconfig" |
| 10 | 12 |
) |
| ... | ... |
@@ -51,17 +53,39 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
|
| 51 | 51 |
return Login(authConfig, endpoint) |
| 52 | 52 |
} |
| 53 | 53 |
|
| 54 |
+// splitReposSearchTerm breaks a search term into an index name and remote name |
|
| 55 |
+func splitReposSearchTerm(reposName string) (string, string) {
|
|
| 56 |
+ nameParts := strings.SplitN(reposName, "/", 2) |
|
| 57 |
+ var indexName, remoteName string |
|
| 58 |
+ if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && |
|
| 59 |
+ !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
|
|
| 60 |
+ // This is a Docker Index repos (ex: samalba/hipache or ubuntu) |
|
| 61 |
+ // 'docker.io' |
|
| 62 |
+ indexName = IndexName |
|
| 63 |
+ remoteName = reposName |
|
| 64 |
+ } else {
|
|
| 65 |
+ indexName = nameParts[0] |
|
| 66 |
+ remoteName = nameParts[1] |
|
| 67 |
+ } |
|
| 68 |
+ return indexName, remoteName |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 54 | 71 |
// Search queries the public registry for images matching the specified |
| 55 | 72 |
// search terms, and returns the results. |
| 56 | 73 |
func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) {
|
| 74 |
+ if err := validateNoSchema(term); err != nil {
|
|
| 75 |
+ return nil, err |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ indexName, remoteName := splitReposSearchTerm(term) |
|
| 57 | 79 |
|
| 58 |
- repoInfo, err := s.ResolveRepositoryBySearch(term) |
|
| 80 |
+ index, err := s.Config.NewIndexInfo(indexName) |
|
| 59 | 81 |
if err != nil {
|
| 60 | 82 |
return nil, err |
| 61 | 83 |
} |
| 62 | 84 |
|
| 63 | 85 |
// *TODO: Search multiple indexes. |
| 64 |
- endpoint, err := NewEndpoint(repoInfo.Index, http.Header(headers), APIVersionUnknown) |
|
| 86 |
+ endpoint, err := NewEndpoint(index, http.Header(headers), APIVersionUnknown) |
|
| 65 | 87 |
if err != nil {
|
| 66 | 88 |
return nil, err |
| 67 | 89 |
} |
| ... | ... |
@@ -70,19 +94,23 @@ func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers |
| 70 | 70 |
if err != nil {
|
| 71 | 71 |
return nil, err |
| 72 | 72 |
} |
| 73 |
- return r.SearchRepositories(repoInfo.GetSearchTerm()) |
|
| 74 |
-} |
|
| 75 | 73 |
|
| 76 |
-// ResolveRepository splits a repository name into its components |
|
| 77 |
-// and configuration of the associated registry. |
|
| 78 |
-func (s *Service) ResolveRepository(name string) (*RepositoryInfo, error) {
|
|
| 79 |
- return s.Config.NewRepositoryInfo(name, false) |
|
| 74 |
+ if index.Official {
|
|
| 75 |
+ localName := remoteName |
|
| 76 |
+ if strings.HasPrefix(localName, "library/") {
|
|
| 77 |
+ // If pull "library/foo", it's stored locally under "foo" |
|
| 78 |
+ localName = strings.SplitN(localName, "/", 2)[1] |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ return r.SearchRepositories(localName) |
|
| 82 |
+ } |
|
| 83 |
+ return r.SearchRepositories(remoteName) |
|
| 80 | 84 |
} |
| 81 | 85 |
|
| 82 |
-// ResolveRepositoryBySearch splits a repository name into its components |
|
| 86 |
+// ResolveRepository splits a repository name into its components |
|
| 83 | 87 |
// and configuration of the associated registry. |
| 84 |
-func (s *Service) ResolveRepositoryBySearch(name string) (*RepositoryInfo, error) {
|
|
| 85 |
- return s.Config.NewRepositoryInfo(name, true) |
|
| 88 |
+func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
|
| 89 |
+ return s.Config.NewRepositoryInfo(name) |
|
| 86 | 90 |
} |
| 87 | 91 |
|
| 88 | 92 |
// ResolveIndex takes indexName and returns index info |
| ... | ... |
@@ -123,14 +151,14 @@ func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) {
|
| 123 | 123 |
// LookupPullEndpoints creates an list of endpoints to try to pull from, in order of preference. |
| 124 | 124 |
// It gives preference to v2 endpoints over v1, mirrors over the actual |
| 125 | 125 |
// registry, and HTTPS over plain HTTP. |
| 126 |
-func (s *Service) LookupPullEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
|
|
| 126 |
+func (s *Service) LookupPullEndpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
|
|
| 127 | 127 |
return s.lookupEndpoints(repoName) |
| 128 | 128 |
} |
| 129 | 129 |
|
| 130 | 130 |
// LookupPushEndpoints creates an list of endpoints to try to push to, in order of preference. |
| 131 | 131 |
// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP. |
| 132 | 132 |
// Mirrors are not included. |
| 133 |
-func (s *Service) LookupPushEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
|
|
| 133 |
+func (s *Service) LookupPushEndpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
|
|
| 134 | 134 |
allEndpoints, err := s.lookupEndpoints(repoName) |
| 135 | 135 |
if err == nil {
|
| 136 | 136 |
for _, endpoint := range allEndpoints {
|
| ... | ... |
@@ -142,7 +170,7 @@ func (s *Service) LookupPushEndpoints(repoName string) (endpoints []APIEndpoint, |
| 142 | 142 |
return endpoints, err |
| 143 | 143 |
} |
| 144 | 144 |
|
| 145 |
-func (s *Service) lookupEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
|
|
| 145 |
+func (s *Service) lookupEndpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
|
|
| 146 | 146 |
endpoints, err = s.lookupV2Endpoints(repoName) |
| 147 | 147 |
if err != nil {
|
| 148 | 148 |
return nil, err |
| ... | ... |
@@ -4,13 +4,15 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"strings" |
| 6 | 6 |
|
| 7 |
+ "github.com/docker/distribution/reference" |
|
| 7 | 8 |
"github.com/docker/docker/pkg/tlsconfig" |
| 8 | 9 |
) |
| 9 | 10 |
|
| 10 |
-func (s *Service) lookupV1Endpoints(repoName string) (endpoints []APIEndpoint, err error) {
|
|
| 11 |
+func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
|
|
| 11 | 12 |
var cfg = tlsconfig.ServerDefault |
| 12 | 13 |
tlsConfig := &cfg |
| 13 |
- if strings.HasPrefix(repoName, DefaultNamespace+"/") {
|
|
| 14 |
+ nameString := repoName.Name() |
|
| 15 |
+ if strings.HasPrefix(nameString, DefaultNamespace+"/") {
|
|
| 14 | 16 |
endpoints = append(endpoints, APIEndpoint{
|
| 15 | 17 |
URL: DefaultV1Registry, |
| 16 | 18 |
Version: APIVersion1, |
| ... | ... |
@@ -21,11 +23,11 @@ func (s *Service) lookupV1Endpoints(repoName string) (endpoints []APIEndpoint, e |
| 21 | 21 |
return endpoints, nil |
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
- slashIndex := strings.IndexRune(repoName, '/') |
|
| 24 |
+ slashIndex := strings.IndexRune(nameString, '/') |
|
| 25 | 25 |
if slashIndex <= 0 {
|
| 26 |
- return nil, fmt.Errorf("invalid repo name: missing '/': %s", repoName)
|
|
| 26 |
+ return nil, fmt.Errorf("invalid repo name: missing '/': %s", nameString)
|
|
| 27 | 27 |
} |
| 28 |
- hostname := repoName[:slashIndex] |
|
| 28 |
+ hostname := nameString[:slashIndex] |
|
| 29 | 29 |
|
| 30 | 30 |
tlsConfig, err = s.TLSConfig(hostname) |
| 31 | 31 |
if err != nil {
|
| ... | ... |
@@ -4,14 +4,16 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"strings" |
| 6 | 6 |
|
| 7 |
+ "github.com/docker/distribution/reference" |
|
| 7 | 8 |
"github.com/docker/distribution/registry/client/auth" |
| 8 | 9 |
"github.com/docker/docker/pkg/tlsconfig" |
| 9 | 10 |
) |
| 10 | 11 |
|
| 11 |
-func (s *Service) lookupV2Endpoints(repoName string) (endpoints []APIEndpoint, err error) {
|
|
| 12 |
+func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) {
|
|
| 12 | 13 |
var cfg = tlsconfig.ServerDefault |
| 13 | 14 |
tlsConfig := &cfg |
| 14 |
- if strings.HasPrefix(repoName, DefaultNamespace+"/") {
|
|
| 15 |
+ nameString := repoName.Name() |
|
| 16 |
+ if strings.HasPrefix(nameString, DefaultNamespace+"/") {
|
|
| 15 | 17 |
// v2 mirrors |
| 16 | 18 |
for _, mirror := range s.Config.Mirrors {
|
| 17 | 19 |
mirrorTLSConfig, err := s.tlsConfigForMirror(mirror) |
| ... | ... |
@@ -39,11 +41,11 @@ func (s *Service) lookupV2Endpoints(repoName string) (endpoints []APIEndpoint, e |
| 39 | 39 |
return endpoints, nil |
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
- slashIndex := strings.IndexRune(repoName, '/') |
|
| 42 |
+ slashIndex := strings.IndexRune(nameString, '/') |
|
| 43 | 43 |
if slashIndex <= 0 {
|
| 44 |
- return nil, fmt.Errorf("invalid repo name: missing '/': %s", repoName)
|
|
| 44 |
+ return nil, fmt.Errorf("invalid repo name: missing '/': %s", nameString)
|
|
| 45 | 45 |
} |
| 46 |
- hostname := repoName[:slashIndex] |
|
| 46 |
+ hostname := nameString[:slashIndex] |
|
| 47 | 47 |
|
| 48 | 48 |
tlsConfig, err = s.TLSConfig(hostname) |
| 49 | 49 |
if err != nil {
|
| ... | ... |
@@ -20,6 +20,7 @@ import ( |
| 20 | 20 |
"time" |
| 21 | 21 |
|
| 22 | 22 |
"github.com/Sirupsen/logrus" |
| 23 |
+ "github.com/docker/distribution/reference" |
|
| 23 | 24 |
"github.com/docker/docker/cliconfig" |
| 24 | 25 |
"github.com/docker/docker/pkg/httputils" |
| 25 | 26 |
"github.com/docker/docker/pkg/ioutils" |
| ... | ... |
@@ -320,7 +321,9 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io |
| 320 | 320 |
// repository. It queries each of the registries supplied in the registries |
| 321 | 321 |
// argument, and returns data from the first one that answers the query |
| 322 | 322 |
// successfully. |
| 323 |
-func (r *Session) GetRemoteTag(registries []string, repository string, askedTag string) (string, error) {
|
|
| 323 |
+func (r *Session) GetRemoteTag(registries []string, repositoryRef reference.Named, askedTag string) (string, error) {
|
|
| 324 |
+ repository := repositoryRef.Name() |
|
| 325 |
+ |
|
| 324 | 326 |
if strings.Count(repository, "/") == 0 {
|
| 325 | 327 |
// This will be removed once the registry supports auto-resolution on |
| 326 | 328 |
// the "library" namespace |
| ... | ... |
@@ -356,7 +359,9 @@ func (r *Session) GetRemoteTag(registries []string, repository string, askedTag |
| 356 | 356 |
// of the registries supplied in the registries argument, and returns data from |
| 357 | 357 |
// the first one that answers the query successfully. It returns a map with |
| 358 | 358 |
// tag names as the keys and image IDs as the values. |
| 359 |
-func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) {
|
|
| 359 |
+func (r *Session) GetRemoteTags(registries []string, repositoryRef reference.Named) (map[string]string, error) {
|
|
| 360 |
+ repository := repositoryRef.Name() |
|
| 361 |
+ |
|
| 360 | 362 |
if strings.Count(repository, "/") == 0 {
|
| 361 | 363 |
// This will be removed once the registry supports auto-resolution on |
| 362 | 364 |
// the "library" namespace |
| ... | ... |
@@ -408,8 +413,8 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
|
| 408 | 408 |
} |
| 409 | 409 |
|
| 410 | 410 |
// GetRepositoryData returns lists of images and endpoints for the repository |
| 411 |
-func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
|
|
| 412 |
- repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote)
|
|
| 411 |
+func (r *Session) GetRepositoryData(remote reference.Named) (*RepositoryData, error) {
|
|
| 412 |
+ repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote.Name())
|
|
| 413 | 413 |
|
| 414 | 414 |
logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
|
| 415 | 415 |
|
| ... | ... |
@@ -443,7 +448,7 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
|
| 443 | 443 |
if err != nil {
|
| 444 | 444 |
logrus.Debugf("Error reading response body: %s", err)
|
| 445 | 445 |
} |
| 446 |
- return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
|
|
| 446 |
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote.Name(), errBody), res)
|
|
| 447 | 447 |
} |
| 448 | 448 |
|
| 449 | 449 |
var endpoints []string |
| ... | ... |
@@ -595,10 +600,10 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry |
| 595 | 595 |
|
| 596 | 596 |
// PushRegistryTag pushes a tag on the registry. |
| 597 | 597 |
// Remote has the format '<user>/<repo> |
| 598 |
-func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error {
|
|
| 598 |
+func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error {
|
|
| 599 | 599 |
// "jsonify" the string |
| 600 | 600 |
revision = "\"" + revision + "\"" |
| 601 |
- path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
|
|
| 601 |
+ path := fmt.Sprintf("repositories/%s/tags/%s", remote.Name(), tag)
|
|
| 602 | 602 |
|
| 603 | 603 |
req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision))
|
| 604 | 604 |
if err != nil {
|
| ... | ... |
@@ -612,13 +617,13 @@ func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error |
| 612 | 612 |
} |
| 613 | 613 |
res.Body.Close() |
| 614 | 614 |
if res.StatusCode != 200 && res.StatusCode != 201 {
|
| 615 |
- return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res)
|
|
| 615 |
+ return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote.Name()), res)
|
|
| 616 | 616 |
} |
| 617 | 617 |
return nil |
| 618 | 618 |
} |
| 619 | 619 |
|
| 620 | 620 |
// PushImageJSONIndex uploads an image list to the repository |
| 621 |
-func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
|
|
| 621 |
+func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
|
|
| 622 | 622 |
cleanImgList := []*ImgData{}
|
| 623 | 623 |
if validate {
|
| 624 | 624 |
for _, elem := range imgList {
|
| ... | ... |
@@ -638,7 +643,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate |
| 638 | 638 |
if validate {
|
| 639 | 639 |
suffix = "images" |
| 640 | 640 |
} |
| 641 |
- u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote, suffix)
|
|
| 641 |
+ u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote.Name(), suffix)
|
|
| 642 | 642 |
logrus.Debugf("[registry] PUT %s", u)
|
| 643 | 643 |
logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
|
| 644 | 644 |
headers := map[string][]string{
|
| ... | ... |
@@ -676,7 +681,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate |
| 676 | 676 |
if err != nil {
|
| 677 | 677 |
logrus.Debugf("Error reading response body: %s", err)
|
| 678 | 678 |
} |
| 679 |
- return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
|
|
| 679 |
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote.Name(), errBody), res)
|
|
| 680 | 680 |
} |
| 681 | 681 |
tokens = res.Header["X-Docker-Token"] |
| 682 | 682 |
logrus.Debugf("Auth token: %v", tokens)
|
| ... | ... |
@@ -694,7 +699,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate |
| 694 | 694 |
if err != nil {
|
| 695 | 695 |
logrus.Debugf("Error reading response body: %s", err)
|
| 696 | 696 |
} |
| 697 |
- return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res)
|
|
| 697 |
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote.Name(), errBody), res)
|
|
| 698 | 698 |
} |
| 699 | 699 |
} |
| 700 | 700 |
|
| ... | ... |
@@ -1,5 +1,9 @@ |
| 1 | 1 |
package registry |
| 2 | 2 |
|
| 3 |
+import ( |
|
| 4 |
+ "github.com/docker/distribution/reference" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 3 | 7 |
// SearchResult describes a search result returned from a registry |
| 4 | 8 |
type SearchResult struct {
|
| 5 | 9 |
// StarCount indicates the number of stars this repository has |
| ... | ... |
@@ -126,13 +130,13 @@ type RepositoryInfo struct {
|
| 126 | 126 |
Index *IndexInfo |
| 127 | 127 |
// RemoteName is the remote name of the repository, such as |
| 128 | 128 |
// "library/ubuntu-12.04-base" |
| 129 |
- RemoteName string |
|
| 129 |
+ RemoteName reference.Named |
|
| 130 | 130 |
// LocalName is the local name of the repository, such as |
| 131 | 131 |
// "ubuntu-12.04-base" |
| 132 |
- LocalName string |
|
| 132 |
+ LocalName reference.Named |
|
| 133 | 133 |
// CanonicalName is the canonical name of the repository, such as |
| 134 | 134 |
// "docker.io/library/ubuntu-12.04-base" |
| 135 |
- CanonicalName string |
|
| 135 |
+ CanonicalName reference.Named |
|
| 136 | 136 |
// Official indicates whether the repository is considered official. |
| 137 | 137 |
// If the registry is official, and the normalized name does not |
| 138 | 138 |
// contain a '/' (e.g. "foo"), then it is considered an official repo. |
| ... | ... |
@@ -269,23 +269,6 @@ func ReadDockerIgnore(reader io.ReadCloser) ([]string, error) {
|
| 269 | 269 |
return excludes, nil |
| 270 | 270 |
} |
| 271 | 271 |
|
| 272 |
-// ImageReference combines `repo` and `ref` and returns a string representing |
|
| 273 |
-// the combination. If `ref` is a digest (meaning it's of the form |
|
| 274 |
-// <algorithm>:<digest>, the returned string is <repo>@<ref>. Otherwise, |
|
| 275 |
-// ref is assumed to be a tag, and the returned string is <repo>:<tag>. |
|
| 276 |
-func ImageReference(repo, ref string) string {
|
|
| 277 |
- if DigestReference(ref) {
|
|
| 278 |
- return repo + "@" + ref |
|
| 279 |
- } |
|
| 280 |
- return repo + ":" + ref |
|
| 281 |
-} |
|
| 282 |
- |
|
| 283 |
-// DigestReference returns true if ref is a digest reference; i.e. if it |
|
| 284 |
-// is of the form <algorithm>:<digest>. |
|
| 285 |
-func DigestReference(ref string) bool {
|
|
| 286 |
- return strings.Contains(ref, ":") |
|
| 287 |
-} |
|
| 288 |
- |
|
| 289 | 272 |
// GetErrorMessage returns the human readable message associated with |
| 290 | 273 |
// the passed-in error. In some cases the default Error() func returns |
| 291 | 274 |
// something that is less than useful so based on its types this func |
| ... | ... |
@@ -26,36 +26,6 @@ func TestReplaceAndAppendEnvVars(t *testing.T) {
|
| 26 | 26 |
} |
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 |
-func TestImageReference(t *testing.T) {
|
|
| 30 |
- tests := []struct {
|
|
| 31 |
- repo string |
|
| 32 |
- ref string |
|
| 33 |
- expected string |
|
| 34 |
- }{
|
|
| 35 |
- {"repo", "tag", "repo:tag"},
|
|
| 36 |
- {"repo", "sha256:c100b11b25d0cacd52c14e0e7bf525e1a4c0e6aec8827ae007055545909d1a64", "repo@sha256:c100b11b25d0cacd52c14e0e7bf525e1a4c0e6aec8827ae007055545909d1a64"},
|
|
| 37 |
- } |
|
| 38 |
- |
|
| 39 |
- for i, test := range tests {
|
|
| 40 |
- actual := ImageReference(test.repo, test.ref) |
|
| 41 |
- if test.expected != actual {
|
|
| 42 |
- t.Errorf("%d: expected %q, got %q", i, test.expected, actual)
|
|
| 43 |
- } |
|
| 44 |
- } |
|
| 45 |
-} |
|
| 46 |
- |
|
| 47 |
-func TestDigestReference(t *testing.T) {
|
|
| 48 |
- input := "sha256:c100b11b25d0cacd52c14e0e7bf525e1a4c0e6aec8827ae007055545909d1a64" |
|
| 49 |
- if !DigestReference(input) {
|
|
| 50 |
- t.Errorf("Expected DigestReference=true for input %q", input)
|
|
| 51 |
- } |
|
| 52 |
- |
|
| 53 |
- input = "latest" |
|
| 54 |
- if DigestReference(input) {
|
|
| 55 |
- t.Errorf("Unexpected DigestReference=true for input %q", input)
|
|
| 56 |
- } |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 | 29 |
func TestReadDockerIgnore(t *testing.T) {
|
| 60 | 30 |
tmpDir, err := ioutil.TempDir("", "dockerignore-test")
|
| 61 | 31 |
if err != nil {
|