summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/load.go30
-rw-r--r--cmd/podman/pull.go13
-rw-r--r--cmd/podman/push.go7
-rw-r--r--cmd/podman/save.go92
-rw-r--r--cmd/podman/umount.go22
-rw-r--r--completions/bash/podman2
-rw-r--r--docs/podman-export.1.md2
-rw-r--r--docs/podman-umount.1.md17
-rw-r--r--docs/podman-varlink.1.md10
-rw-r--r--docs/podman-version.1.md2
-rw-r--r--libpod/container_api.go4
-rw-r--r--libpod/container_internal.go6
-rw-r--r--libpod/image/docker_registry_options.go13
-rw-r--r--libpod/image/image.go42
-rw-r--r--libpod/image/pull.go294
-rw-r--r--libpod/image/pull_test.go320
-rw-r--r--libpod/image/testdata/docker-name-only.tar.xzbin0 -> 1024 bytes
-rw-r--r--libpod/image/testdata/docker-registry-name.tar.xzbin0 -> 1028 bytes
-rw-r--r--libpod/image/testdata/docker-two-images.tar.xzbin0 -> 1416 bytes
-rw-r--r--libpod/image/testdata/docker-two-names.tar.xzbin0 -> 1040 bytes
-rw-r--r--libpod/image/testdata/docker-unnamed.tar.xzbin0 -> 968 bytes
-rw-r--r--libpod/image/testdata/oci-name-only.tar.gzbin0 -> 975 bytes
-rw-r--r--libpod/image/testdata/oci-non-docker-name.tar.gzbin0 -> 991 bytes
-rw-r--r--libpod/image/testdata/oci-registry-name.tar.gzbin0 -> 979 bytes
-rw-r--r--libpod/image/testdata/oci-unnamed.tar.gzbin0 -> 928 bytes
-rw-r--r--libpod/image/utils.go8
-rw-r--r--libpod/runtime_img.go26
-rw-r--r--libpod/storage.go14
-rw-r--r--pkg/varlinkapi/images.go4
-rw-r--r--test/e2e/libpod_suite_test.go74
-rw-r--r--test/e2e/run_signal_test.go2
-rw-r--r--test/e2e/run_test.go2
-rw-r--r--test/e2e/search_test.go30
-rw-r--r--test/registries.conf2
34 files changed, 695 insertions, 343 deletions
diff --git a/cmd/podman/load.go b/cmd/podman/load.go
index 565b09184..ffef9f6a6 100644
--- a/cmd/podman/load.go
+++ b/cmd/podman/load.go
@@ -6,9 +6,11 @@ import (
"io/ioutil"
"os"
+ "github.com/containers/image/directory"
+ dockerarchive "github.com/containers/image/docker/archive"
+ ociarchive "github.com/containers/image/oci/archive"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
- "github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/libpod/image"
"github.com/urfave/cli"
)
@@ -45,10 +47,10 @@ var (
func loadCmd(c *cli.Context) error {
args := c.Args()
- var image string
+ var imageName string
if len(args) == 1 {
- image = args[0]
+ imageName = args[0]
}
if len(args) > 1 {
return errors.New("too many arguments. Requires exactly 1")
@@ -104,20 +106,24 @@ func loadCmd(c *cli.Context) error {
ctx := getContext()
- src := libpod.DockerArchive + ":" + input
- newImages, err := runtime.ImageRuntime().LoadFromArchive(ctx, src, c.String("signature-policy"), writer)
+ var newImages []*image.Image
+ src, err := dockerarchive.ParseReference(input) // FIXME? We should add dockerarchive.NewReference()
+ if err == nil {
+ newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer)
+ }
if err != nil {
// generate full src name with specified image:tag
- fullSrc := libpod.OCIArchive + ":" + input
- if image != "" {
- fullSrc = fullSrc + ":" + image
+ src, err := ociarchive.NewReference(input, imageName) // imageName may be ""
+ if err == nil {
+ newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer)
}
- newImages, err = runtime.ImageRuntime().LoadFromArchive(ctx, fullSrc, c.String("signature-policy"), writer)
if err != nil {
- src = libpod.DirTransport + ":" + input
- newImages, err = runtime.ImageRuntime().LoadFromArchive(ctx, src, c.String("signature-policy"), writer)
+ src, err := directory.NewReference(input)
+ if err == nil {
+ newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer)
+ }
if err != nil {
- return errors.Wrapf(err, "error pulling %q", src)
+ return errors.Wrapf(err, "error pulling %q", input)
}
}
}
diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go
index 431c1e0ed..a1d685735 100644
--- a/cmd/podman/pull.go
+++ b/cmd/podman/pull.go
@@ -6,10 +6,11 @@ import (
"os"
"strings"
+ dockerarchive "github.com/containers/image/docker/archive"
+ "github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
- "github.com/projectatomic/libpod/libpod"
image2 "github.com/projectatomic/libpod/libpod/image"
"github.com/projectatomic/libpod/pkg/util"
"github.com/sirupsen/logrus"
@@ -110,9 +111,13 @@ func pullCmd(c *cli.Context) error {
forceSecure = c.Bool("tls-verify")
}
- // Possible for docker-archive to have multiple tags, so use NewFromLoad instead
- if strings.Contains(image, libpod.DockerArchive) {
- newImage, err := runtime.ImageRuntime().LoadFromArchive(getContext(), image, c.String("signature-policy"), writer)
+ // Possible for docker-archive to have multiple tags, so use LoadFromArchiveReference instead
+ if strings.HasPrefix(image, dockerarchive.Transport.Name()+":") {
+ srcRef, err := alltransports.ParseImageName(image)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing %q", image)
+ }
+ newImage, err := runtime.ImageRuntime().LoadFromArchiveReference(getContext(), srcRef, c.String("signature-policy"), writer)
if err != nil {
return errors.Wrapf(err, "error pulling image from %q", image)
}
diff --git a/cmd/podman/push.go b/cmd/podman/push.go
index 74882adb2..3c2e59e58 100644
--- a/cmd/podman/push.go
+++ b/cmd/podman/push.go
@@ -6,12 +6,12 @@ import (
"os"
"strings"
+ "github.com/containers/image/directory"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
- "github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/libpod/image"
"github.com/projectatomic/libpod/pkg/util"
"github.com/urfave/cli"
@@ -101,7 +101,7 @@ func pushCmd(c *cli.Context) error {
// --compress and --format can only be used for the "dir" transport
splitArg := strings.SplitN(destName, ":", 2)
if c.IsSet("compress") || c.IsSet("format") {
- if splitArg[0] != libpod.DirTransport {
+ if splitArg[0] != directory.Transport.Name() {
return errors.Errorf("--compress and --format can be set only when pushing to a directory using the 'dir' transport")
}
}
@@ -164,6 +164,5 @@ func pushCmd(c *cli.Context) error {
return err
}
- //return runtime.PushImage(srcName, destName, options)
- return newImage.PushImage(getContext(), destName, manifestType, c.String("authfile"), c.String("signature-policy"), writer, c.Bool("compress"), so, &dockerRegistryOptions, forceSecure, nil)
+ return newImage.PushImageToHeuristicDestination(getContext(), destName, manifestType, c.String("authfile"), c.String("signature-policy"), writer, c.Bool("compress"), so, &dockerRegistryOptions, forceSecure, nil)
}
diff --git a/cmd/podman/save.go b/cmd/podman/save.go
index 016fa580a..6a0d12885 100644
--- a/cmd/podman/save.go
+++ b/cmd/podman/save.go
@@ -6,12 +6,15 @@ import (
"os"
"strings"
+ "github.com/containers/image/directory"
+ dockerarchive "github.com/containers/image/docker/archive"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
+ ociarchive "github.com/containers/image/oci/archive"
+ "github.com/containers/image/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
- "github.com/projectatomic/libpod/libpod"
libpodImage "github.com/projectatomic/libpod/libpod/image"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
@@ -93,20 +96,43 @@ func saveCmd(c *cli.Context) error {
return err
}
- var dst, manifestType string
+ source := args[0]
+ newImage, err := runtime.ImageRuntime().NewFromLocal(source)
+ if err != nil {
+ return err
+ }
+
+ var destRef types.ImageReference
+ var manifestType string
switch c.String("format") {
- case libpod.OCIArchive:
- dst = libpod.OCIArchive + ":" + output
+ case "oci-archive":
+ destImageName := imageNameForSaveDestination(newImage, source)
+ destRef, err = ociarchive.NewReference(output, destImageName) // destImageName may be ""
+ if err != nil {
+ return errors.Wrapf(err, "error getting OCI archive ImageReference for (%q, %q)", output, destImageName)
+ }
case "oci-dir":
- dst = libpod.DirTransport + ":" + output
+ destRef, err = directory.NewReference(output)
+ if err != nil {
+ return errors.Wrapf(err, "error getting directory ImageReference for %q", output)
+ }
manifestType = imgspecv1.MediaTypeImageManifest
case "docker-dir":
- dst = libpod.DirTransport + ":" + output
+ destRef, err = directory.NewReference(output)
+ if err != nil {
+ return errors.Wrapf(err, "error getting directory ImageReference for %q", output)
+ }
manifestType = manifest.DockerV2Schema2MediaType
- case libpod.DockerArchive:
- fallthrough
- case "":
- dst = libpod.DockerArchive + ":" + output
+ case "docker-archive", "":
+ dst := output
+ destImageName := imageNameForSaveDestination(newImage, source)
+ if destImageName != "" {
+ dst = fmt.Sprintf("%s:%s", dst, destImageName)
+ }
+ destRef, err = dockerarchive.ParseReference(dst) // FIXME? Add dockerarchive.NewReference
+ if err != nil {
+ return errors.Wrapf(err, "error getting Docker archive ImageReference for %q", dst)
+ }
default:
return errors.Errorf("unknown format option %q", c.String("format"))
}
@@ -119,28 +145,8 @@ func saveCmd(c *cli.Context) error {
return err
}
}
- source := args[0]
- newImage, err := runtime.ImageRuntime().NewFromLocal(source)
- if err != nil {
- return err
- }
- dest := dst
- // need dest to be in the format transport:path:reference for the following transports
- if (strings.Contains(dst, libpod.OCIArchive) || strings.Contains(dst, libpod.DockerArchive)) && !strings.Contains(newImage.ID(), source) {
- prepend := ""
- if !strings.Contains(source, libpodImage.DefaultLocalRepo) {
- // we need to check if localhost was added to the image name in NewFromLocal
- for _, name := range newImage.Names() {
- // if the user searched for the image whose tag was prepended with localhost, we'll need to prepend localhost to successfully search
- if strings.Contains(name, libpodImage.DefaultLocalRepo) && strings.Contains(name, source) {
- prepend = fmt.Sprintf("%s/", libpodImage.DefaultLocalRepo)
- break
- }
- }
- }
- dest = fmt.Sprintf("%s:%s%s", dst, prepend, source)
- }
- if err := newImage.PushImage(getContext(), dest, manifestType, "", "", writer, c.Bool("compress"), libpodImage.SigningOptions{}, &libpodImage.DockerRegistryOptions{}, false, additionaltags); err != nil {
+
+ if err := newImage.PushImageToReference(getContext(), destRef, manifestType, "", "", writer, c.Bool("compress"), libpodImage.SigningOptions{}, &libpodImage.DockerRegistryOptions{}, false, additionaltags); err != nil {
if err2 := os.Remove(output); err2 != nil {
logrus.Errorf("error deleting %q: %v", output, err)
}
@@ -149,3 +155,25 @@ func saveCmd(c *cli.Context) error {
return nil
}
+
+// imageNameForSaveDestination returns a Docker-like reference appropriate for saving img,
+// which the user referred to as imgUserInput; or an empty string, if there is no appropriate
+// reference.
+func imageNameForSaveDestination(img *libpodImage.Image, imgUserInput string) string {
+ if strings.Contains(img.ID(), imgUserInput) {
+ return ""
+ }
+
+ prepend := ""
+ if !strings.Contains(imgUserInput, libpodImage.DefaultLocalRepo) {
+ // we need to check if localhost was added to the image name in NewFromLocal
+ for _, name := range img.Names() {
+ // if the user searched for the image whose tag was prepended with localhost, we'll need to prepend localhost to successfully search
+ if strings.Contains(name, libpodImage.DefaultLocalRepo) && strings.Contains(name, imgUserInput) {
+ prepend = fmt.Sprintf("%s/", libpodImage.DefaultLocalRepo)
+ break
+ }
+ }
+ }
+ return fmt.Sprintf("%s%s", prepend, imgUserInput)
+}
diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go
index 1a2cf22c6..1e364b48f 100644
--- a/cmd/podman/umount.go
+++ b/cmd/podman/umount.go
@@ -3,6 +3,7 @@ package main
import (
"fmt"
+ "github.com/containers/storage"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
"github.com/projectatomic/libpod/libpod"
@@ -16,13 +17,24 @@ var (
Name: "all, a",
Usage: "umount all of the currently mounted containers",
},
+ cli.BoolFlag{
+ Name: "force, f",
+ Usage: "force the complete umount all of the currently mounted containers",
+ },
}
+ description = `
+Container storage increments a mount counter each time a container is mounted.
+When a container is unmounted, the mount counter is decremented and the
+container's root filesystem is physically unmounted only when the mount
+counter reaches zero indicating no other processes are using the mount.
+An unmount can be forced with the --force flag.
+`
umountCommand = cli.Command{
Name: "umount",
Aliases: []string{"unmount"},
Usage: "Unmounts working container's root filesystem",
- Description: "Unmounts working container's root filesystem",
+ Description: description,
Flags: umountFlags,
Action: umountCmd,
ArgsUsage: "CONTAINER-NAME-OR-ID",
@@ -36,6 +48,7 @@ func umountCmd(c *cli.Context) error {
}
defer runtime.Shutdown(false)
+ force := c.Bool("force")
umountAll := c.Bool("all")
args := c.Args()
if len(args) == 0 && !umountAll {
@@ -58,7 +71,7 @@ func umountCmd(c *cli.Context) error {
continue
}
- if err = ctr.Unmount(); err != nil {
+ if err = ctr.Unmount(force); err != nil {
if lastError != nil {
logrus.Error(lastError)
}
@@ -78,7 +91,10 @@ func umountCmd(c *cli.Context) error {
continue
}
- if err = ctr.Unmount(); err != nil {
+ if err = ctr.Unmount(force); err != nil {
+ if umountAll && errors.Cause(err) == storage.ErrLayerNotMounted {
+ continue
+ }
if lastError != nil {
logrus.Error(lastError)
}
diff --git a/completions/bash/podman b/completions/bash/podman
index 2c43a2d8f..563597f0b 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1358,6 +1358,8 @@ _podman_umount() {
local boolean_options="
--all
-a
+ --force
+ -f
--help
-h
"
diff --git a/docs/podman-export.1.md b/docs/podman-export.1.md
index 6029beeb5..9adc1499b 100644
--- a/docs/podman-export.1.md
+++ b/docs/podman-export.1.md
@@ -1,7 +1,7 @@
% podman-export "1"
## NAME
-podman export - Export container's filesystem contents as a tar archive
+podman\-export - Export container's filesystem contents as a tar archive
## SYNOPSIS
**podman export** [*options*] *container*
diff --git a/docs/podman-umount.1.md b/docs/podman-umount.1.md
index c13781dbf..70f30869a 100644
--- a/docs/podman-umount.1.md
+++ b/docs/podman-umount.1.md
@@ -7,13 +7,28 @@ podman\-umount - Unmount the specified working containers' root file system.
**podman umount** *container* ...
## DESCRIPTION
-Unmounts the specified containers' root file system.
+Unmounts the specified containers' root file system, if no other processes
+are using it.
+
+Container storage increments a mount counter each time a container is mounted.
+When a container is unmounted, the mount counter is decremented and the
+container's root filesystem is physically unmounted only when the mount
+counter reaches zero indicating no other processes are using the mount.
+An unmount can be forced with the --force flag.
## OPTIONS
**--all, -a**
All of the currently mounted containers will be unmounted.
+**--force, -f**
+
+Force the unmounting of specified containers' root file system, even if other
+processes have mounted it.
+
+Note: This could cause other processes that are using the file system to fail,
+as the mount point could be removed without their knowledge.
+
## EXAMPLE
podman umount containerID
diff --git a/docs/podman-varlink.1.md b/docs/podman-varlink.1.md
index 1e63ddec3..d53457e22 100644
--- a/docs/podman-varlink.1.md
+++ b/docs/podman-varlink.1.md
@@ -26,13 +26,13 @@ second. A value of `0` means no timeout and the session will not expire.
Run the podman varlink service manually and accept the default timeout.
```
-$ podman varlink unix:/run/podman/io.projectatomic.podman
+# podman varlink unix:/run/podman/io.projectatomic.podman
```
Run the podman varlink service manually with a 5 second timeout.
```
-$ podman varlink --timeout 5000 unix:/run/podman/io.projectatomic.podman
+# podman varlink --timeout 5000 unix:/run/podman/io.projectatomic.podman
```
## CONFIGURATION
@@ -40,9 +40,11 @@ $ podman varlink --timeout 5000 unix:/run/podman/io.projectatomic.podman
Users of the podman varlink service should enable the _io.projectatomic.podman.socket_ and _io.projectatomic.podman.service_.
This is the preferred method for running the varlink service.
-You can do this via systemctl
+You can do this via systemctl.
-systemctl enable --now io.projectatomic.podman.socket
+```
+# systemctl enable --now io.projectatomic.podman.socket
+```
## SEE ALSO
podman(1), systemctl(1)
diff --git a/docs/podman-version.1.md b/docs/podman-version.1.md
index f1a1505d9..0c9b9ceed 100644
--- a/docs/podman-version.1.md
+++ b/docs/podman-version.1.md
@@ -7,7 +7,7 @@ podman\-version - Display the PODMAN Version Information
**podman version** [*options*]
## DESCRIPTION
-Shows the the following information: Version, Go Version, Git Commit, Build Time,
+Shows the following information: Version, Go Version, Git Commit, Build Time,
OS, and Architecture.
## OPTIONS
diff --git a/libpod/container_api.go b/libpod/container_api.go
index b5104048e..73fd96960 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -441,7 +441,7 @@ func (c *Container) Mount() (string, error) {
}
// Unmount unmounts a container's filesystem on the host
-func (c *Container) Unmount() error {
+func (c *Container) Unmount(force bool) error {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@@ -469,7 +469,7 @@ func (c *Container) Unmount() error {
return errors.Wrapf(err, "can't unmount %s last mount, it is still in use", c.ID())
}
}
- return c.unmount()
+ return c.unmount(force)
}
// Pause pauses a container
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 66d1e44ad..7b5932541 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -839,7 +839,7 @@ func (c *Container) cleanupStorage() error {
return nil
}
- if err := c.unmount(); err != nil {
+ if err := c.unmount(false); err != nil {
// If the container has already been removed, warn but don't
// error
// We still want to be able to kick the container out of the
@@ -1338,9 +1338,9 @@ func (c *Container) mount() (string, error) {
}
// unmount unmounts the container's root filesystem
-func (c *Container) unmount() error {
+func (c *Container) unmount(force bool) error {
// Also unmount storage
- if _, err := c.runtime.storageService.UnmountContainerImage(c.ID()); err != nil {
+ if _, err := c.runtime.storageService.UnmountContainerImage(c.ID(), force); err != nil {
return errors.Wrapf(err, "error unmounting container %s root filesystem", c.ID())
}
diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go
index 838edf2d0..97a151396 100644
--- a/libpod/image/docker_registry_options.go
+++ b/libpod/image/docker_registry_options.go
@@ -23,18 +23,19 @@ type DockerRegistryOptions struct {
DockerInsecureSkipTLSVerify bool
}
-// GetSystemContext constructs a new system context from the given signaturePolicy path and the
-// values in the DockerRegistryOptions
-func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath, authFile string, forceCompress bool, additionalDockerArchiveTags []reference.NamedTagged) *types.SystemContext {
+// GetSystemContext constructs a new system context from a parent context. the values in the DockerRegistryOptions, and other parameters.
+func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, additionalDockerArchiveTags []reference.NamedTagged) *types.SystemContext {
sc := &types.SystemContext{
- SignaturePolicyPath: signaturePolicyPath,
DockerAuthConfig: o.DockerRegistryCreds,
DockerCertPath: o.DockerCertPath,
DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify,
- AuthFilePath: authFile,
- DirForceCompress: forceCompress,
DockerArchiveAdditionalTags: additionalDockerArchiveTags,
}
+ if parent != nil {
+ sc.SignaturePolicyPath = parent.SignaturePolicyPath
+ sc.AuthFilePath = parent.AuthFilePath
+ sc.DirForceCompress = parent.DirForceCompress
+ }
return sc
}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 112eeb015..9447ec9e1 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -15,6 +15,7 @@ import (
"github.com/containers/image/manifest"
is "github.com/containers/image/storage"
"github.com/containers/image/tarball"
+ "github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
"github.com/containers/storage"
@@ -144,7 +145,7 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile
if signaturePolicyPath == "" {
signaturePolicyPath = ir.SignaturePolicyPath
}
- imageName, err := newImage.pullImage(ctx, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, forceSecure)
+ imageName, err := ir.pullImageFromHeuristicSource(ctx, name, writer, authfile, signaturePolicyPath, signingoptions, dockeroptions, forceSecure)
if err != nil {
return nil, errors.Wrapf(err, "unable to pull %s", name)
}
@@ -158,22 +159,17 @@ func (ir *Runtime) New(ctx context.Context, name, signaturePolicyPath, authfile
return &newImage, nil
}
-// LoadFromArchive creates a new image object for images pulled from a tar archive (podman load)
+// LoadFromArchiveReference creates a new image object for images pulled from a tar archive and the like (podman load)
// This function is needed because it is possible for a tar archive to have multiple tags for one image
-func (ir *Runtime) LoadFromArchive(ctx context.Context, name, signaturePolicyPath string, writer io.Writer) ([]*Image, error) {
+func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.ImageReference, signaturePolicyPath string, writer io.Writer) ([]*Image, error) {
var newImages []*Image
- newImage := Image{
- InputName: name,
- Local: false,
- imageruntime: ir,
- }
if signaturePolicyPath == "" {
signaturePolicyPath = ir.SignaturePolicyPath
}
- imageNames, err := newImage.pullImage(ctx, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}, false)
+ imageNames, err := ir.pullImageFromReference(ctx, srcRef, writer, "", signaturePolicyPath, SigningOptions{}, &DockerRegistryOptions{}, false)
if err != nil {
- return nil, errors.Wrapf(err, "unable to pull %s", name)
+ return nil, errors.Wrapf(err, "unable to pull %s", transports.ImageName(srcRef))
}
for _, name := range imageNames {
@@ -513,8 +509,9 @@ func (i *Image) UntagImage(tag string) error {
return nil
}
-// PushImage pushes the given image to a location described by the given path
-func (i *Image) PushImage(ctx context.Context, destination, manifestMIMEType, authFile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, forceSecure bool, additionalDockerArchiveTags []reference.NamedTagged) error {
+// PushImageToHeuristicDestination pushes the given image to "destination", which is heuristically parsed.
+// Use PushImageToReference if the destination is known precisely.
+func (i *Image) PushImageToHeuristicDestination(ctx context.Context, destination, manifestMIMEType, authFile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, forceSecure bool, additionalDockerArchiveTags []reference.NamedTagged) error {
if destination == "" {
return errors.Wrapf(syscall.EINVAL, "destination image name must be specified")
}
@@ -532,7 +529,11 @@ func (i *Image) PushImage(ctx context.Context, destination, manifestMIMEType, au
return err
}
}
+ return i.PushImageToReference(ctx, dest, manifestMIMEType, authFile, signaturePolicyPath, writer, forceCompress, signingOptions, dockerRegistryOptions, forceSecure, additionalDockerArchiveTags)
+}
+// PushImageToReference pushes the given image to a location described by the given path
+func (i *Image) PushImageToReference(ctx context.Context, dest types.ImageReference, manifestMIMEType, authFile, signaturePolicyPath string, writer io.Writer, forceCompress bool, signingOptions SigningOptions, dockerRegistryOptions *DockerRegistryOptions, forceSecure bool, additionalDockerArchiveTags []reference.NamedTagged) error {
sc := GetSystemContext(signaturePolicyPath, authFile, forceCompress)
policyContext, err := getPolicyContext(sc)
@@ -550,13 +551,13 @@ func (i *Image) PushImage(ctx context.Context, destination, manifestMIMEType, au
if err != nil {
return err
}
- copyOptions := getCopyOptions(writer, signaturePolicyPath, nil, dockerRegistryOptions, signingOptions, authFile, manifestMIMEType, forceCompress, additionalDockerArchiveTags)
- if strings.HasPrefix(DockerTransport, dest.Transport().Name()) {
- imgRef, err := reference.Parse(dest.DockerReference().String())
- if err != nil {
- return err
+ copyOptions := getCopyOptions(sc, writer, nil, dockerRegistryOptions, signingOptions, manifestMIMEType, additionalDockerArchiveTags)
+ if dest.Transport().Name() == DockerTransport {
+ imgRef := dest.DockerReference()
+ if imgRef == nil { // This should never happen; such references can’t be created.
+ return fmt.Errorf("internal error: DockerTransport reference %s does not have a DockerReference", transports.ImageName(dest))
}
- registry := reference.Domain(imgRef.(reference.Named))
+ registry := reference.Domain(imgRef)
if util.StringInSlice(registry, insecureRegistries) && !forceSecure {
copyOptions.DestinationCtx.DockerInsecureSkipTLSVerify = true
@@ -873,8 +874,7 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) {
// Import imports and image into the store and returns an image
func (ir *Runtime) Import(ctx context.Context, path, reference string, writer io.Writer, signingOptions SigningOptions, imageConfig ociv1.Image) (*Image, error) {
- file := TarballTransport + ":" + path
- src, err := alltransports.ParseImageName(file)
+ src, err := tarball.Transport.ParseReference(path)
if err != nil {
return nil, errors.Wrapf(err, "error parsing image name %q", path)
}
@@ -906,7 +906,7 @@ func (ir *Runtime) Import(ctx context.Context, path, reference string, writer io
return nil, err
}
defer policyContext.Destroy()
- copyOptions := getCopyOptions(writer, "", nil, nil, signingOptions, "", "", false, nil)
+ copyOptions := getCopyOptions(sc, writer, nil, nil, signingOptions, "", nil)
dest, err := is.Transport.ParseStoreReference(ir.store, reference)
if err != nil {
errors.Wrapf(err, "error getting image reference for %q", reference)
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index cc60c8894..ff978d563 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -15,7 +15,7 @@ import (
ociarchive "github.com/containers/image/oci/archive"
"github.com/containers/image/pkg/sysregistries"
is "github.com/containers/image/storage"
- "github.com/containers/image/tarball"
+ "github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
"github.com/pkg/errors"
@@ -34,37 +34,46 @@ var (
// DirTransport is the transport for pushing and pulling
// images to and from a directory
DirTransport = directory.Transport.Name()
- // TransportNames are the supported transports in string form
- TransportNames = [...]string{DefaultTransport, DockerArchive, OCIArchive, "ostree:", "dir:"}
- // TarballTransport is the transport for importing a tar archive
- // and creating a filesystem image
- TarballTransport = tarball.Transport.Name()
// DockerTransport is the transport for docker registries
- DockerTransport = docker.Transport.Name() + "://"
+ DockerTransport = docker.Transport.Name()
// AtomicTransport is the transport for atomic registries
AtomicTransport = "atomic"
// DefaultTransport is a prefix that we apply to an image name
- DefaultTransport = DockerTransport
+ // NOTE: This is a string prefix, not actually a transport name usable for transports.Get();
+ // and because syntaxes of image names are transport-dependent, the prefix is not really interchangeable;
+ // each user implicitly assumes the appended string is a Docker-like reference.
+ DefaultTransport = DockerTransport + "://"
// DefaultLocalRepo is the default local repository for local image operations
// Remote pulls will still use defined registries
DefaultLocalRepo = "localhost"
)
-// pullRefPair records a pair of prepared image references to try to pull (if not DockerArchive) or to pull all (if DockerArchive)
+// pullRefPair records a pair of prepared image references to pull.
type pullRefPair struct {
image string
srcRef types.ImageReference
dstRef types.ImageReference
}
-// pullRefName records a prepared source reference and a destination name to try to pull (if not DockerArchive) or to pull all (if DockerArchive)
-type pullRefName struct {
- image string
- srcRef types.ImageReference
- dstName string
+// pullGoal represents the prepared image references and decided behavior to be executed by imagePull
+type pullGoal struct {
+ refPairs []pullRefPair
+ pullAllPairs bool // Pull all refPairs instead of stopping on first success.
+ usedSearchRegistries bool // refPairs construction has depended on registries.GetRegistries()
+ searchedRegistries []string // The list of search registries used; set only if usedSearchRegistries
}
-func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (*pullRefPair, error) {
+// singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair.
+func singlePullRefPairGoal(rp pullRefPair) *pullGoal {
+ return &pullGoal{
+ refPairs: []pullRefPair{rp},
+ pullAllPairs: false, // Does not really make a difference.
+ usedSearchRegistries: false,
+ searchedRegistries: nil,
+ }
+}
+
+func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (pullRefPair, error) {
imgPart, err := decompose(destName)
if err == nil && !imgPart.hasRegistry {
// If the image doesn't have a registry, set it as the default repo
@@ -79,24 +88,31 @@ func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string)
}
destRef, err := is.Transport.ParseStoreReference(ir.store, reference)
if err != nil {
- return nil, errors.Wrapf(err, "error parsing dest reference name")
+ return pullRefPair{}, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
}
- return &pullRefPair{
+ return pullRefPair{
image: destName,
srcRef: srcRef,
dstRef: destRef,
}, nil
}
-// returns a list of pullRefPair with the srcRef and DstRef based on the transport being used
-func (ir *Runtime) getPullListFromRef(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) ([]*pullRefPair, error) {
- var pullRefPairs []*pullRefPair
- splitArr := strings.Split(imgName, ":")
- archFile := splitArr[len(splitArr)-1]
+// getSinglePullRefPairGoal calls getPullRefPair with the specified parameters, and returns a single-pair goal for the return value.
+func (ir *Runtime) getSinglePullRefPairGoal(srcRef types.ImageReference, destName string) (*pullGoal, error) {
+ rp, err := ir.getPullRefPair(srcRef, destName)
+ if err != nil {
+ return nil, err
+ }
+ return singlePullRefPairGoal(rp), nil
+}
+// pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport.
+func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoal, error) {
// supports pulling from docker-archive, oci, and registries
- if srcRef.Transport().Name() == DockerArchive {
- tarSource, err := tarfile.NewSourceFromFile(archFile)
+ switch srcRef.Transport().Name() {
+ case DockerArchive:
+ archivePath := srcRef.StringWithinTransport()
+ tarSource, err := tarfile.NewSourceFromFile(archivePath)
if err != nil {
return nil, err
}
@@ -112,33 +128,35 @@ func (ir *Runtime) getPullListFromRef(ctx context.Context, srcRef types.ImageRef
if err != nil {
return nil, err
}
- pullInfo, err := ir.getPullRefPair(srcRef, reference)
+ return ir.getSinglePullRefPairGoal(srcRef, reference)
+ }
+
+ if len(manifest[0].RepoTags) == 0 {
+ // If the input image has no repotags, we need to feed it a dest anyways
+ digest, err := getImageDigest(ctx, srcRef, sc)
if err != nil {
return nil, err
}
- pullRefPairs = append(pullRefPairs, pullInfo)
- } else {
- var dest []string
- if len(manifest[0].RepoTags) > 0 {
- dest = append(dest, manifest[0].RepoTags...)
- } else {
- // If the input image has no repotags, we need to feed it a dest anyways
- digest, err := getImageDigest(ctx, srcRef, sc)
- if err != nil {
- return nil, err
- }
- dest = append(dest, digest)
- }
- // Need to load in all the repo tags from the manifest
- for _, dst := range dest {
- pullInfo, err := ir.getPullRefPair(srcRef, dst)
- if err != nil {
- return nil, err
- }
- pullRefPairs = append(pullRefPairs, pullInfo)
+ return ir.getSinglePullRefPairGoal(srcRef, digest)
+ }
+
+ // Need to load in all the repo tags from the manifest
+ res := []pullRefPair{}
+ for _, dst := range manifest[0].RepoTags {
+ pullInfo, err := ir.getPullRefPair(srcRef, dst)
+ if err != nil {
+ return nil, err
}
+ res = append(res, pullInfo)
}
- } else if srcRef.Transport().Name() == OCIArchive {
+ return &pullGoal{
+ refPairs: res,
+ pullAllPairs: true,
+ usedSearchRegistries: false,
+ searchedRegistries: nil,
+ }, nil
+
+ case OCIArchive:
// retrieve the manifest from index.json to access the image name
manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
if err != nil {
@@ -156,55 +174,57 @@ func (ir *Runtime) getPullListFromRef(ctx context.Context, srcRef types.ImageRef
} else {
dest = manifest.Annotations["org.opencontainers.image.ref.name"]
}
- pullInfo, err := ir.getPullRefPair(srcRef, dest)
- if err != nil {
- return nil, err
- }
- pullRefPairs = append(pullRefPairs, pullInfo)
- } else if srcRef.Transport().Name() == DirTransport {
- // supports pull from a directory
- image := splitArr[1]
+ return ir.getSinglePullRefPairGoal(srcRef, dest)
+
+ case DirTransport:
+ path := srcRef.StringWithinTransport()
+ image := path
// remove leading "/"
if image[:1] == "/" {
// Instead of removing the leading /, set localhost as the registry
// so docker.io isn't prepended, and the path becomes the repository
image = DefaultLocalRepo + image
}
- pullInfo, err := ir.getPullRefPair(srcRef, image)
- if err != nil {
- return nil, err
- }
- pullRefPairs = append(pullRefPairs, pullInfo)
- } else {
- pullInfo, err := ir.getPullRefPair(srcRef, imgName)
- if err != nil {
- return nil, err
- }
- pullRefPairs = append(pullRefPairs, pullInfo)
+ return ir.getSinglePullRefPairGoal(srcRef, image)
+
+ default:
+ return ir.getSinglePullRefPairGoal(srcRef, imgName)
}
- return pullRefPairs, nil
}
-// pullImage pulls an image from configured registries
-// By default, only the latest tag (or a specific tag if requested) will be
-// pulled.
-func (i *Image) pullImage(ctx context.Context, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, forceSecure bool) ([]string, error) {
- // pullImage copies the image from the source to the destination
- var pullRefPairs []*pullRefPair
+// pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries.
+// Use pullImageFromReference if the source is known precisely.
+func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, forceSecure bool) ([]string, error) {
+ var goal *pullGoal
sc := GetSystemContext(signaturePolicyPath, authfile, false)
- srcRef, err := alltransports.ParseImageName(i.InputName)
+ srcRef, err := alltransports.ParseImageName(inputName)
if err != nil {
// could be trying to pull from registry with short name
- pullRefPairs, err = i.refPairsFromPossiblyUnqualifiedName()
+ goal, err = ir.pullGoalFromPossiblyUnqualifiedName(inputName)
if err != nil {
return nil, errors.Wrap(err, "error getting default registries to try")
}
} else {
- pullRefPairs, err = i.imageruntime.getPullListFromRef(ctx, srcRef, i.InputName, sc)
+ goal, err = ir.pullGoalFromImageReference(ctx, srcRef, inputName, sc)
if err != nil {
- return nil, errors.Wrapf(err, "error getting pullRefPair info to pull image %q", i.InputName)
+ return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName)
}
}
+ return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, forceSecure)
+}
+
+// pullImageFromReference pulls an image from a types.imageReference.
+func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.ImageReference, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, forceSecure bool) ([]string, error) {
+ sc := GetSystemContext(signaturePolicyPath, authfile, false)
+ goal, err := ir.pullGoalFromImageReference(ctx, srcRef, transports.ImageName(srcRef), sc)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef))
+ }
+ return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, forceSecure)
+}
+
+// doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead.
+func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goal pullGoal, writer io.Writer, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, forceSecure bool) ([]string, error) {
policyContext, err := getPolicyContext(sc)
if err != nil {
return nil, err
@@ -216,14 +236,15 @@ func (i *Image) pullImage(ctx context.Context, writer io.Writer, authfile, signa
return nil, err
}
var images []string
- for _, imageInfo := range pullRefPairs {
- copyOptions := getCopyOptions(writer, signaturePolicyPath, dockerOptions, nil, signingOptions, authfile, "", false, nil)
- if strings.HasPrefix(DockerTransport, imageInfo.srcRef.Transport().Name()) {
- imgRef, err := reference.Parse(imageInfo.srcRef.DockerReference().String())
- if err != nil {
- return nil, err
+ for _, imageInfo := range goal.refPairs {
+ copyOptions := getCopyOptions(sc, writer, dockerOptions, nil, signingOptions, "", nil)
+ if imageInfo.srcRef.Transport().Name() == DockerTransport {
+ imgRef := imageInfo.srcRef.DockerReference()
+ if imgRef == nil { // This should never happen; such references can’t be created.
+ return nil, fmt.Errorf("internal error: DockerTransport reference %s does not have a DockerReference",
+ transports.ImageName(imageInfo.srcRef))
}
- registry := reference.Domain(imgRef.(reference.Named))
+ registry := reference.Domain(imgRef)
if util.StringInSlice(registry, insecureRegistries) && !forceSecure {
copyOptions.SourceCtx.DockerInsecureSkipTLSVerify = true
@@ -231,7 +252,7 @@ func (i *Image) pullImage(ctx context.Context, writer io.Writer, authfile, signa
}
}
// Print the following statement only when pulling from a docker or atomic registry
- if writer != nil && (strings.HasPrefix(DockerTransport, imageInfo.srcRef.Transport().Name()) || imageInfo.srcRef.Transport().Name() == AtomicTransport) {
+ if writer != nil && (imageInfo.srcRef.Transport().Name() == DockerTransport || imageInfo.srcRef.Transport().Name() == AtomicTransport) {
io.WriteString(writer, fmt.Sprintf("Trying to pull %s...", imageInfo.image))
}
if err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions); err != nil {
@@ -239,7 +260,7 @@ func (i *Image) pullImage(ctx context.Context, writer io.Writer, authfile, signa
io.WriteString(writer, "Failed\n")
}
} else {
- if imageInfo.srcRef.Transport().Name() != DockerArchive {
+ if !goal.pullAllPairs {
return []string{imageInfo.image}, nil
}
images = append(images, imageInfo.image)
@@ -248,15 +269,7 @@ func (i *Image) pullImage(ctx context.Context, writer io.Writer, authfile, signa
// If no image was found, we should handle. Lets be nicer to the user and see if we can figure out why.
if len(images) == 0 {
registryPath := sysregistries.RegistriesConfPath(&types.SystemContext{})
- searchRegistries, err := registries.GetRegistries()
- if err != nil {
- return nil, err
- }
- hasRegistryInName, err := i.hasRegistry()
- if err != nil {
- return nil, err
- }
- if !hasRegistryInName && len(searchRegistries) == 0 {
+ if goal.usedSearchRegistries && len(goal.searchedRegistries) == 0 {
return nil, errors.Errorf("image name provided is a short name and no search registries are defined in %s.", registryPath)
}
return nil, errors.Errorf("unable to find image in the registries defined in %q", registryPath)
@@ -270,19 +283,15 @@ func hasShaInInputName(inputName string) bool {
return strings.Contains(inputName, "@sha256:")
}
-// refNamesFromPossiblyUnqualifiedName looks at a decomposed image and determines the possible
-// image names to try pulling in combination with the registries.conf file as well
-func refNamesFromPossiblyUnqualifiedName(inputName string) ([]*pullRefName, error) {
- var (
- pullNames []*pullRefName
- imageName string
- )
-
+// pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible
+// image references to try pulling in combination with the registries.conf file as well
+func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullGoal, error) {
decomposedImage, err := decompose(inputName)
if err != nil {
return nil, err
}
if decomposedImage.hasRegistry {
+ var imageName, destName string
if hasShaInInputName(inputName) {
imageName = fmt.Sprintf("%s%s", decomposedImage.transport, inputName)
} else {
@@ -292,67 +301,52 @@ func refNamesFromPossiblyUnqualifiedName(inputName string) ([]*pullRefName, erro
if err != nil {
return nil, errors.Wrapf(err, "unable to parse '%s'", inputName)
}
- ps := pullRefName{
- image: inputName,
- srcRef: srcRef,
- }
if hasShaInInputName(inputName) {
- ps.dstName = decomposedImage.assemble()
+ destName = decomposedImage.assemble()
} else {
- ps.dstName = ps.image
+ destName = inputName
}
- pullNames = append(pullNames, &ps)
-
- } else {
- searchRegistries, err := registries.GetRegistries()
+ destRef, err := is.Transport.ParseStoreReference(ir.store, destName)
if err != nil {
- return nil, err
+ return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName)
}
- for _, registry := range searchRegistries {
- decomposedImage.registry = registry
- imageName := decomposedImage.assembleWithTransport()
- if hasShaInInputName(inputName) {
- imageName = fmt.Sprintf("%s%s/%s", decomposedImage.transport, registry, inputName)
- }
- srcRef, err := alltransports.ParseImageName(imageName)
- if err != nil {
- return nil, errors.Wrapf(err, "unable to parse '%s'", inputName)
- }
- ps := pullRefName{
- image: decomposedImage.assemble(),
- srcRef: srcRef,
- }
- ps.dstName = ps.image
- pullNames = append(pullNames, &ps)
+ ps := pullRefPair{
+ image: inputName,
+ srcRef: srcRef,
+ dstRef: destRef,
}
+ return singlePullRefPairGoal(ps), nil
}
- return pullNames, nil
-}
-// refPairsFromPossiblyUnqualifiedName looks at a decomposed image and determines the possible
-// image references to try pulling in combination with the registries.conf file as well
-func (i *Image) refPairsFromPossiblyUnqualifiedName() ([]*pullRefPair, error) {
- refNames, err := refNamesFromPossiblyUnqualifiedName(i.InputName)
+ searchRegistries, err := registries.GetRegistries()
if err != nil {
return nil, err
}
- return i.imageruntime.pullRefPairsFromRefNames(refNames)
-}
-
-// pullRefPairsFromNames converts a []*pullRefName to []*pullRefPair
-func (ir *Runtime) pullRefPairsFromRefNames(refNames []*pullRefName) ([]*pullRefPair, error) {
- // Here we construct the destination references
- res := make([]*pullRefPair, len(refNames))
- for i, rn := range refNames {
- destRef, err := is.Transport.ParseStoreReference(ir.store, rn.dstName)
+ var refPairs []pullRefPair
+ for _, registry := range searchRegistries {
+ decomposedImage.registry = registry
+ imageName := decomposedImage.assembleWithTransport()
+ if hasShaInInputName(inputName) {
+ imageName = fmt.Sprintf("%s%s/%s", decomposedImage.transport, registry, inputName)
+ }
+ srcRef, err := alltransports.ParseImageName(imageName)
if err != nil {
- return nil, errors.Wrapf(err, "error parsing dest reference name")
+ return nil, errors.Wrapf(err, "unable to parse '%s'", inputName)
}
- res[i] = &pullRefPair{
- image: rn.image,
- srcRef: rn.srcRef,
- dstRef: destRef,
+ ps := pullRefPair{
+ image: decomposedImage.assemble(),
+ srcRef: srcRef,
+ }
+ ps.dstRef, err = is.Transport.ParseStoreReference(ir.store, ps.image)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing dest reference name %#v", ps.image)
}
+ refPairs = append(refPairs, ps)
}
- return res, nil
+ return &pullGoal{
+ refPairs: refPairs,
+ pullAllPairs: false,
+ usedSearchRegistries: true,
+ searchedRegistries: searchRegistries,
+ }, nil
}
diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go
index 535dd4e9d..5ef8c47a5 100644
--- a/libpod/image/pull_test.go
+++ b/libpod/image/pull_test.go
@@ -1,24 +1,278 @@
package image
import (
+ "context"
+ "fmt"
"io/ioutil"
"os"
+ "path/filepath"
+ "strings"
"testing"
"github.com/containers/image/transports"
+ "github.com/containers/image/transports/alltransports"
+ "github.com/containers/image/types"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/idtools"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
+// newTestRuntime returns a *Runtime implementation and a cleanup function which the caller is expected to call.
+func newTestRuntime(t *testing.T) (*Runtime, func()) {
+ wd, err := ioutil.TempDir("", "testStorageRuntime")
+ require.NoError(t, err)
+ err = os.MkdirAll(wd, 0700)
+ require.NoError(t, err)
+
+ store, err := storage.GetStore(storage.StoreOptions{
+ RunRoot: filepath.Join(wd, "run"),
+ GraphRoot: filepath.Join(wd, "root"),
+ GraphDriverName: "vfs",
+ GraphDriverOptions: []string{},
+ UIDMap: []idtools.IDMap{{
+ ContainerID: 0,
+ HostID: os.Getuid(),
+ Size: 1,
+ }},
+ GIDMap: []idtools.IDMap{{
+ ContainerID: 0,
+ HostID: os.Getgid(),
+ Size: 1,
+ }},
+ })
+ require.NoError(t, err)
+
+ ir := NewImageRuntimeFromStore(store)
+ cleanup := func() { _ = os.RemoveAll(wd) }
+ return ir, cleanup
+}
+
+// storageReferenceWithoutLocation returns ref.StringWithinTransport(),
+// stripping the [store-specification] prefix from containers/image/storage reference format.
+func storageReferenceWithoutLocation(ref types.ImageReference) string {
+ res := ref.StringWithinTransport()
+ if res[0] == '[' {
+ closeIndex := strings.IndexRune(res, ']')
+ if closeIndex > 0 {
+ res = res[closeIndex+1:]
+ }
+ }
+ return res
+}
+
+func TestGetPullRefPair(t *testing.T) {
+ const imageID = "@0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+ const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+
+ ir, cleanup := newTestRuntime(t)
+ defer cleanup()
+
+ for _, c := range []struct{ srcName, destName, expectedImage, expectedDstName string }{
+ // == Source does not have a Docker reference (as is the case for docker-archive:, oci-archive, dir:); destination formats:
+ { // registry/name, no tag:
+ "dir:/dev/this-does-not-exist", "example.com/from-directory",
+ "example.com/from-directory", "example.com/from-directory:latest",
+ },
+ { // name, no registry, no tag:
+ "dir:/dev/this-does-not-exist", "from-directory",
+ // FIXME(?) Adding a registry also adds a :latest tag. OTOH that actually matches the used destination.
+ // Either way it is surprising that the localhost/ addition changes this. (mitr hoping to remove the "image" member).
+ "localhost/from-directory:latest", "localhost/from-directory:latest",
+ },
+ { // registry/name:tag :
+ "dir:/dev/this-does-not-exist", "example.com/from-directory:notlatest",
+ "example.com/from-directory:notlatest", "example.com/from-directory:notlatest",
+ },
+ { // name:tag, no registry:
+ "dir:/dev/this-does-not-exist", "from-directory:notlatest",
+ "localhost/from-directory:notlatest", "localhost/from-directory:notlatest",
+ },
+ { // name@digest, no registry:
+ "dir:/dev/this-does-not-exist", "from-directory" + digestSuffix,
+ // FIXME?! Why is this dropping the digest, and adding :none?!
+ "localhost/from-directory:none", "localhost/from-directory:none",
+ },
+ { // registry/name@digest:
+ "dir:/dev/this-does-not-exist", "example.com/from-directory" + digestSuffix,
+ "example.com/from-directory" + digestSuffix, "example.com/from-directory" + digestSuffix,
+ },
+ { // ns/name:tag, no registry:
+ // FIXME: This is interpreted as "registry == ns"
+ "dir:/dev/this-does-not-exist", "ns/from-directory:notlatest",
+ "ns/from-directory:notlatest", "docker.io/ns/from-directory:notlatest",
+ },
+ { // containers-storage image ID
+ "dir:/dev/this-does-not-exist", imageID,
+ imageID, imageID,
+ },
+ // == Source does have a Docker reference.
+ // In that case getPullListFromRef uses the full transport:name input as a destName,
+ // which would be invalid in the returned dstName - but dstName is derived from the source, so it does not really matter _so_ much.
+ // Note that unlike real-world use we use different :source and :destination to verify the data flow in more detail.
+ { // registry/name:tag
+ "docker://example.com/busybox:source", "docker://example.com/busybox:destination",
+ "docker://example.com/busybox:destination", "example.com/busybox:source",
+ },
+ { // Implied docker.io/library and :latest
+ "docker://busybox", "docker://busybox:destination",
+ "docker://busybox:destination", "docker.io/library/busybox:latest",
+ },
+ // == Invalid destination format.
+ {"tarball:/dev/null", "tarball:/dev/null", "", ""},
+ } {
+ testDescription := fmt.Sprintf("%#v %#v", c.srcName, c.destName)
+ srcRef, err := alltransports.ParseImageName(c.srcName)
+ require.NoError(t, err, testDescription)
+
+ res, err := ir.getPullRefPair(srcRef, c.destName)
+ if c.expectedDstName == "" {
+ assert.Error(t, err, testDescription)
+ } else {
+ require.NoError(t, err, testDescription)
+ assert.Equal(t, c.expectedImage, res.image, testDescription)
+ assert.Equal(t, srcRef, res.srcRef, testDescription)
+ assert.Equal(t, c.expectedDstName, storageReferenceWithoutLocation(res.dstRef), testDescription)
+ }
+ }
+}
+
+func TestPullGoalFromImageReference(t *testing.T) {
+ ir, cleanup := newTestRuntime(t)
+ defer cleanup()
+
+ type expected struct{ image, dstName string }
+ for _, c := range []struct {
+ srcName string
+ expected []expected
+ expectedPullAllPairs bool
+ }{
+ // == docker-archive:
+ {"docker-archive:/dev/this-does-not-exist", nil, false}, // Input does not exist.
+ {"docker-archive:/dev/null", nil, false}, // Input exists but does not contain a manifest.
+ // FIXME: The implementation has extra code for len(manifest) == 0?! That will fail in getImageDigest anyway.
+ { // RepoTags is empty
+ "docker-archive:testdata/docker-unnamed.tar.xz",
+ []expected{{"@ec9293436c2e66da44edb9efb8d41f6b13baf62283ebe846468bc992d76d7951", "@ec9293436c2e66da44edb9efb8d41f6b13baf62283ebe846468bc992d76d7951"}},
+ false,
+ },
+ { // RepoTags is a [docker.io/library/]name:latest, normalized to the short format.
+ "docker-archive:testdata/docker-name-only.tar.xz",
+ []expected{{"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"}},
+ true,
+ },
+ { // RepoTags is a registry/name:latest
+ "docker-archive:testdata/docker-registry-name.tar.xz",
+ []expected{{"example.com/empty:latest", "example.com/empty:latest"}},
+ true,
+ },
+ { // RepoTags has multiple items for a single image
+ "docker-archive:testdata/docker-two-names.tar.xz",
+ []expected{
+ {"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"},
+ {"example.com/empty:latest", "example.com/empty:latest"},
+ },
+ true,
+ },
+ { // FIXME: Two images in a single archive - only the "first" one (whichever it is) is returned
+ // (and docker-archive: then refuses to read anything when the manifest has more than 1 item)
+ "docker-archive:testdata/docker-two-images.tar.xz",
+ []expected{{"example.com/empty:latest", "example.com/empty:latest"}},
+ // "example.com/empty/but:different" exists but is ignored
+ true,
+ },
+
+ // == oci-archive:
+ {"oci-archive:/dev/this-does-not-exist", nil, false}, // Input does not exist.
+ {"oci-archive:/dev/null", nil, false}, // Input exists but does not contain a manifest.
+ // FIXME: The remaining tests are commented out for now, because oci-archive: does not work unprivileged.
+ // { // No name annotation
+ // "oci-archive:testdata/oci-unnamed.tar.gz",
+ // []expected{{"@5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6", "@5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6"}},
+ // false,
+ // },
+ // { // Name is a name:latest (no normalization is defined).
+ // "oci-archive:testdata/oci-name-only.tar.gz",
+ // []expected{{"localhost/pretty-empty:latest", "localhost/pretty-empty:latest"}},
+ // false,
+ // },
+ // { // Name is a registry/name:latest
+ // "oci-archive:testdata/oci-registry-name.tar.gz",
+ // []expected{{"example.com/empty:latest", "example.com/empty:latest"}},
+ // false,
+ // },
+ // // Name exists, but is an invalid Docker reference; such names will fail when creating dstReference.
+ // {"oci-archive:testdata/oci-non-docker-name.tar.gz", nil, false},
+ // Maybe test support of two images in a single archive? It should be transparently handled by adding a reference to srcRef.
+
+ // == dir:
+ { // Absolute path
+ "dir:/dev/this-does-not-exist",
+ []expected{{"localhost/dev/this-does-not-exist", "localhost/dev/this-does-not-exist:latest"}},
+ false,
+ },
+ { // Relative path, single element.
+ // FIXME? Note the :latest difference in .image.
+ "dir:this-does-not-exist",
+ []expected{{"localhost/this-does-not-exist:latest", "localhost/this-does-not-exist:latest"}},
+ false,
+ },
+ { // Relative path, multiple elements.
+ // FIXME: This does not add localhost/, so dstName is normalized to docker.io/testdata.
+ "dir:testdata/this-does-not-exist",
+ []expected{{"testdata/this-does-not-exist", "docker.io/testdata/this-does-not-exist:latest"}},
+ false,
+ },
+
+ // == Others, notably:
+ // === docker:// (has ImageReference.DockerReference)
+ { // Fully-specified input
+ "docker://docker.io/library/busybox:latest",
+ []expected{{"docker://docker.io/library/busybox:latest", "docker.io/library/busybox:latest"}},
+ false,
+ },
+ { // Minimal form of the input
+ "docker://busybox",
+ []expected{{"docker://busybox", "docker.io/library/busybox:latest"}},
+ false,
+ },
+
+ // === tarball: (as an example of what happens when ImageReference.DockerReference is nil).
+ // FIXME? This tries to parse "tarball:/dev/null" as a storageReference, and fails.
+ // (This is NOT an API promise that the results will continue to be this way.)
+ {"tarball:/dev/null", nil, false},
+ } {
+ srcRef, err := alltransports.ParseImageName(c.srcName)
+ require.NoError(t, err, c.srcName)
+
+ res, err := ir.pullGoalFromImageReference(context.Background(), srcRef, c.srcName, nil)
+ if len(c.expected) == 0 {
+ assert.Error(t, err, c.srcName)
+ } else {
+ require.NoError(t, err, c.srcName)
+ require.Len(t, res.refPairs, len(c.expected), c.srcName)
+ for i, e := range c.expected {
+ testDescription := fmt.Sprintf("%s #%d", c.srcName, i)
+ assert.Equal(t, e.image, res.refPairs[i].image, testDescription)
+ assert.Equal(t, srcRef, res.refPairs[i].srcRef, testDescription)
+ assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription)
+ }
+ assert.Equal(t, c.expectedPullAllPairs, res.pullAllPairs, c.srcName)
+ assert.False(t, res.usedSearchRegistries, c.srcName)
+ assert.Nil(t, res.searchedRegistries, c.srcName)
+ }
+ }
+}
+
const registriesConfWithSearch = `[registries.search]
registries = ['example.com', 'docker.io']
`
-func TestRefNamesFromPossiblyUnqualifiedName(t *testing.T) {
+func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) {
const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
- type pullRefStrings struct{ image, srcRef, dstName string } // pullRefName with string data only
+ type pullRefStrings struct{ image, srcRef, dstName string } // pullRefPair with string data only
- registriesConf, err := ioutil.TempFile("", "TestRefNamesFromPossiblyUnqualifiedName")
+ registriesConf, err := ioutil.TempFile("", "TestPullGoalFromPossiblyUnqualifiedName")
require.NoError(t, err)
defer registriesConf.Close()
defer os.Remove(registriesConf.Name())
@@ -26,6 +280,9 @@ func TestRefNamesFromPossiblyUnqualifiedName(t *testing.T) {
err = ioutil.WriteFile(registriesConf.Name(), []byte(registriesConfWithSearch), 0600)
require.NoError(t, err)
+ ir, cleanup := newTestRuntime(t)
+ defer cleanup()
+
// Environment is per-process, so this looks very unsafe; actually it seems fine because tests are not
// run in parallel unless they opt in by calling t.Parallel(). So don’t do that.
oldRCP, hasRCP := os.LookupEnv("REGISTRIES_CONFIG_PATH")
@@ -39,60 +296,68 @@ func TestRefNamesFromPossiblyUnqualifiedName(t *testing.T) {
os.Setenv("REGISTRIES_CONFIG_PATH", registriesConf.Name())
for _, c := range []struct {
- input string
- expected []pullRefStrings
+ input string
+ expected []pullRefStrings
+ expectedUsedSearchRegistries bool
}{
- {"#", nil}, // Clearly invalid.
+ {"#", nil, false}, // Clearly invalid.
{ // Fully-explicit docker.io, name-only.
"docker.io/library/busybox",
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- []pullRefStrings{{"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox"}},
+ []pullRefStrings{{"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
+ false,
},
{ // docker.io with implied /library/, name-only.
"docker.io/busybox",
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- // The .dstName fields differ for the explicit/implicit /library/ cases, but StorageTransport.ParseStoreReference normalizes that.
- []pullRefStrings{{"docker.io/busybox", "docker://busybox:latest", "docker.io/busybox"}},
+ []pullRefStrings{{"docker.io/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}},
+ false,
},
{ // Qualified example.com, name-only.
"example.com/ns/busybox",
- []pullRefStrings{{"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox"}},
+ []pullRefStrings{{"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}},
+ false,
},
{ // Qualified example.com, name:tag.
"example.com/ns/busybox:notlatest",
[]pullRefStrings{{"example.com/ns/busybox:notlatest", "docker://example.com/ns/busybox:notlatest", "example.com/ns/busybox:notlatest"}},
+ false,
},
{ // Qualified example.com, name@digest.
"example.com/ns/busybox" + digestSuffix,
[]pullRefStrings{{"example.com/ns/busybox" + digestSuffix, "docker://example.com/ns/busybox" + digestSuffix,
// FIXME?! Why is .dstName dropping the digest, and adding :none?!
"example.com/ns/busybox:none"}},
+ false,
},
// Qualified example.com, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input.
- {"example.com/ns/busybox:notlatest" + digestSuffix, nil},
+ {"example.com/ns/busybox:notlatest" + digestSuffix, nil, false},
{ // Unqualified, single-name, name-only
"busybox",
[]pullRefStrings{
{"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"},
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- {"docker.io/busybox:latest", "docker://busybox:latest", "docker.io/busybox:latest"},
+ {"docker.io/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"},
},
+ true,
},
{ // Unqualified, namespaced, name-only
"ns/busybox",
// FIXME: This is interpreted as "registry == ns", and actual pull happens from docker.io/ns/busybox:latest;
// example.com should be first in the list but isn't used at all.
[]pullRefStrings{
- {"ns/busybox", "docker://ns/busybox:latest", "ns/busybox"},
+ {"ns/busybox", "docker://ns/busybox:latest", "docker.io/ns/busybox:latest"},
},
+ false,
},
{ // Unqualified, name:tag
"busybox:notlatest",
[]pullRefStrings{
{"example.com/busybox:notlatest", "docker://example.com/busybox:notlatest", "example.com/busybox:notlatest"},
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- {"docker.io/busybox:notlatest", "docker://busybox:notlatest", "docker.io/busybox:notlatest"},
+ {"docker.io/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"},
},
+ true,
},
{ // Unqualified, name@digest
"busybox" + digestSuffix,
@@ -100,26 +365,31 @@ func TestRefNamesFromPossiblyUnqualifiedName(t *testing.T) {
// FIXME?! Why is .input and .dstName dropping the digest, and adding :none?!
{"example.com/busybox:none", "docker://example.com/busybox" + digestSuffix, "example.com/busybox:none"},
// (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".)
- {"docker.io/busybox:none", "docker://busybox" + digestSuffix, "docker.io/busybox:none"},
+ {"docker.io/busybox:none", "docker://busybox" + digestSuffix, "docker.io/library/busybox:none"},
},
+ true,
},
// Unqualified, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input.
- {"busybox:notlatest" + digestSuffix, nil},
+ {"busybox:notlatest" + digestSuffix, nil, false},
} {
- res, err := refNamesFromPossiblyUnqualifiedName(c.input)
+ res, err := ir.pullGoalFromPossiblyUnqualifiedName(c.input)
if len(c.expected) == 0 {
assert.Error(t, err, c.input)
} else {
assert.NoError(t, err, c.input)
- strings := make([]pullRefStrings, len(res))
- for i, rn := range res {
- strings[i] = pullRefStrings{
- image: rn.image,
- srcRef: transports.ImageName(rn.srcRef),
- dstName: rn.dstName,
- }
+ for i, e := range c.expected {
+ testDescription := fmt.Sprintf("%s #%d", c.input, i)
+ assert.Equal(t, e.image, res.refPairs[i].image, testDescription)
+ assert.Equal(t, e.srcRef, transports.ImageName(res.refPairs[i].srcRef), testDescription)
+ assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription)
+ }
+ assert.False(t, res.pullAllPairs, c.input)
+ assert.Equal(t, c.expectedUsedSearchRegistries, res.usedSearchRegistries, c.input)
+ if !c.expectedUsedSearchRegistries {
+ assert.Nil(t, res.searchedRegistries, c.input)
+ } else {
+ assert.Equal(t, []string{"example.com", "docker.io"}, res.searchedRegistries, c.input)
}
- assert.Equal(t, c.expected, strings, c.input)
}
}
}
diff --git a/libpod/image/testdata/docker-name-only.tar.xz b/libpod/image/testdata/docker-name-only.tar.xz
new file mode 100644
index 000000000..0cad9f108
--- /dev/null
+++ b/libpod/image/testdata/docker-name-only.tar.xz
Binary files differ
diff --git a/libpod/image/testdata/docker-registry-name.tar.xz b/libpod/image/testdata/docker-registry-name.tar.xz
new file mode 100644
index 000000000..181816c2e
--- /dev/null
+++ b/libpod/image/testdata/docker-registry-name.tar.xz
Binary files differ
diff --git a/libpod/image/testdata/docker-two-images.tar.xz b/libpod/image/testdata/docker-two-images.tar.xz
new file mode 100644
index 000000000..148d8a86b
--- /dev/null
+++ b/libpod/image/testdata/docker-two-images.tar.xz
Binary files differ
diff --git a/libpod/image/testdata/docker-two-names.tar.xz b/libpod/image/testdata/docker-two-names.tar.xz
new file mode 100644
index 000000000..07fbc479c
--- /dev/null
+++ b/libpod/image/testdata/docker-two-names.tar.xz
Binary files differ
diff --git a/libpod/image/testdata/docker-unnamed.tar.xz b/libpod/image/testdata/docker-unnamed.tar.xz
new file mode 100644
index 000000000..ba6ea1bae
--- /dev/null
+++ b/libpod/image/testdata/docker-unnamed.tar.xz
Binary files differ
diff --git a/libpod/image/testdata/oci-name-only.tar.gz b/libpod/image/testdata/oci-name-only.tar.gz
new file mode 100644
index 000000000..57bc07564
--- /dev/null
+++ b/libpod/image/testdata/oci-name-only.tar.gz
Binary files differ
diff --git a/libpod/image/testdata/oci-non-docker-name.tar.gz b/libpod/image/testdata/oci-non-docker-name.tar.gz
new file mode 100644
index 000000000..5ffc0eabd
--- /dev/null
+++ b/libpod/image/testdata/oci-non-docker-name.tar.gz
Binary files differ
diff --git a/libpod/image/testdata/oci-registry-name.tar.gz b/libpod/image/testdata/oci-registry-name.tar.gz
new file mode 100644
index 000000000..e6df87339
--- /dev/null
+++ b/libpod/image/testdata/oci-registry-name.tar.gz
Binary files differ
diff --git a/libpod/image/testdata/oci-unnamed.tar.gz b/libpod/image/testdata/oci-unnamed.tar.gz
new file mode 100644
index 000000000..de445fdf8
--- /dev/null
+++ b/libpod/image/testdata/oci-unnamed.tar.gz
Binary files differ
diff --git a/libpod/image/utils.go b/libpod/image/utils.go
index de85ca67e..9a75ca6dc 100644
--- a/libpod/image/utils.go
+++ b/libpod/image/utils.go
@@ -42,16 +42,16 @@ func findImageInRepotags(search imageParts, images []*Image) (*storage.Image, er
return results[0], nil
}
-// getCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters
-func getCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, authFile, manifestType string, forceCompress bool, additionalDockerArchiveTags []reference.NamedTagged) *cp.Options {
+// getCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters, inheriting some from sc.
+func getCopyOptions(sc *types.SystemContext, reportWriter io.Writer, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, manifestType string, additionalDockerArchiveTags []reference.NamedTagged) *cp.Options {
if srcDockerRegistry == nil {
srcDockerRegistry = &DockerRegistryOptions{}
}
if destDockerRegistry == nil {
destDockerRegistry = &DockerRegistryOptions{}
}
- srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath, authFile, forceCompress, additionalDockerArchiveTags)
- destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath, authFile, forceCompress, additionalDockerArchiveTags)
+ srcContext := srcDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags)
+ destContext := destDockerRegistry.GetSystemContext(sc, additionalDockerArchiveTags)
return &cp.Options{
RemoveSignatures: signing.RemoveSignatures,
SignBy: signing.SignBy,
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index d127d753f..47dad41da 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -5,11 +5,6 @@ import (
"fmt"
"io"
- "github.com/containers/image/directory"
- "github.com/containers/image/docker"
- dockerarchive "github.com/containers/image/docker/archive"
- ociarchive "github.com/containers/image/oci/archive"
- "github.com/containers/image/tarball"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -21,27 +16,6 @@ import (
// Runtime API
-var (
- // DockerArchive is the transport we prepend to an image name
- // when saving to docker-archive
- DockerArchive = dockerarchive.Transport.Name()
- // OCIArchive is the transport we prepend to an image name
- // when saving to oci-archive
- OCIArchive = ociarchive.Transport.Name()
- // DirTransport is the transport for pushing and pulling
- // images to and from a directory
- DirTransport = directory.Transport.Name()
- // TransportNames are the supported transports in string form
- TransportNames = [...]string{DefaultTransport, DockerArchive, OCIArchive, "ostree:", "dir:"}
- // TarballTransport is the transport for importing a tar archive
- // and creating a filesystem image
- TarballTransport = tarball.Transport.Name()
- // Docker is the transport for docker registries
- Docker = docker.Transport.Name()
- // Atomic is the transport for atomic registries
- Atomic = "atomic"
-)
-
// CopyOptions contains the options given when pushing or pulling images
type CopyOptions struct {
// Compression specifies the type of compression which is applied to
diff --git a/libpod/storage.go b/libpod/storage.go
index 9c5fc858e..10827f13e 100644
--- a/libpod/storage.go
+++ b/libpod/storage.go
@@ -231,7 +231,7 @@ func (r *storageService) MountContainerImage(idOrName string) (string, error) {
return mountPoint, nil
}
-func (r *storageService) UnmountContainerImage(idOrName string) (bool, error) {
+func (r *storageService) UnmountContainerImage(idOrName string, force bool) (bool, error) {
if idOrName == "" {
return false, ErrEmptyID
}
@@ -239,7 +239,17 @@ func (r *storageService) UnmountContainerImage(idOrName string) (bool, error) {
if err != nil {
return false, err
}
- mounted, err := r.store.Unmount(container.ID, false)
+
+ if !force {
+ mounted, err := r.store.Mounted(container.ID)
+ if err != nil {
+ return false, err
+ }
+ if mounted == 0 {
+ return false, storage.ErrLayerNotMounted
+ }
+ }
+ mounted, err := r.store.Unmount(container.ID, force)
if err != nil {
logrus.Debugf("failed to unmount container %q: %v", container.ID, err)
return false, err
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index a129f7099..385c7c1bc 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -348,7 +348,7 @@ func (i *LibpodAPI) PushImage(call ioprojectatomicpodman.VarlinkCall, name, tag
so := image.SigningOptions{}
- if err := newImage.PushImage(getContext(), destname, "", "", "", nil, false, so, &dockerRegistryOptions, false, nil); err != nil {
+ if err := newImage.PushImageToHeuristicDestination(getContext(), destname, "", "", "", nil, false, so, &dockerRegistryOptions, false, nil); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyPushImage(newImage.ID())
@@ -517,7 +517,7 @@ func (i *LibpodAPI) ExportImage(call ioprojectatomicpodman.VarlinkCall, name, de
return err
}
- if err := newImage.PushImage(getContext(), destination, "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}, false, additionalTags); err != nil {
+ if err := newImage.PushImageToHeuristicDestination(getContext(), destination, "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}, false, additionalTags); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyExportImage(newImage.ID())
diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go
index ab09d5004..e95b03cb9 100644
--- a/test/e2e/libpod_suite_test.go
+++ b/test/e2e/libpod_suite_test.go
@@ -1,6 +1,7 @@
package integration
import (
+ "bufio"
"context"
"encoding/json"
"fmt"
@@ -60,6 +61,12 @@ type PodmanTest struct {
TempDir string
}
+// HostOS is a simple struct for the test os
+type HostOS struct {
+ Distribution string
+ Version string
+}
+
// TestLibpod ginkgo master function
func TestLibpod(t *testing.T) {
if reexec.Init() {
@@ -91,7 +98,20 @@ var _ = BeforeSuite(func() {
os.Exit(1)
}
}
-
+ host := GetHostDistributionInfo()
+ if host.Distribution == "rhel" && strings.HasPrefix(host.Version, "7") {
+ f, err := os.OpenFile("/proc/sys/user/max_user_namespaces", os.O_WRONLY, 0644)
+ if err != nil {
+ fmt.Println("Unable to enable userspace on RHEL 7")
+ os.Exit(1)
+ }
+ _, err = f.WriteString("15000")
+ if err != nil {
+ fmt.Println("Unable to enable userspace on RHEL 7")
+ os.Exit(1)
+ }
+ f.Close()
+ }
})
// CreateTempDirin
@@ -101,6 +121,7 @@ func CreateTempDirInTempDir() (string, error) {
// PodmanCreate creates a PodmanTest instance for the tests
func PodmanCreate(tempDir string) PodmanTest {
+
cwd, _ := os.Getwd()
podmanBinary := filepath.Join(cwd, "../../bin/podman")
@@ -123,7 +144,7 @@ func PodmanCreate(tempDir string) PodmanTest {
runCBinary := "/usr/bin/runc"
CNIConfigDir := "/etc/cni/net.d"
- return PodmanTest{
+ p := PodmanTest{
PodmanBinary: podmanBinary,
ConmonBinary: conmonBinary,
CrioRoot: filepath.Join(tempDir, "crio"),
@@ -135,6 +156,10 @@ func PodmanCreate(tempDir string) PodmanTest {
ArtifactPath: ARTIFACT_DIR,
TempDir: tempDir,
}
+
+ // Setup registries.conf ENV variable
+ p.setDefaultRegistriesConfigEnv()
+ return p
}
//MakeOptions assembles all the podman main options
@@ -201,6 +226,9 @@ func (p *PodmanTest) Cleanup() {
if err := os.RemoveAll(p.TempDir); err != nil {
fmt.Printf("%q\n", err)
}
+
+ // Clean up the registries configuration file ENV variable set in Create
+ resetRegistriesConfigEnv()
}
// CleanupPod cleans up the temporary store
@@ -571,24 +599,40 @@ func (p *PodmanTest) BuildImage(dockerfile, imageName string, layers string) {
Expect(session.ExitCode()).To(Equal(0))
}
-//GetHostDistribution returns the dist in string format. If the
-//distribution cannot be determined, an empty string will be returned.
-func (p *PodmanTest) GetHostDistribution() string {
- content, err := ioutil.ReadFile("/etc/os-release")
+//GetHostDistributionInfo returns a struct with its distribution name and version
+func GetHostDistributionInfo() HostOS {
+ f, err := os.Open("/etc/os-release")
+ defer f.Close()
if err != nil {
- return ""
+ return HostOS{}
}
- for _, line := range content {
- if strings.HasPrefix(fmt.Sprintf("%x", line), "ID") {
- fields := strings.Split(fmt.Sprintf("%x", line), "=")
- if len(fields) < 2 {
- return ""
- }
- return strings.Trim(fields[1], "\"")
+ l := bufio.NewScanner(f)
+ host := HostOS{}
+ for l.Scan() {
+ if strings.HasPrefix(l.Text(), "ID=") {
+ host.Distribution = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1)
+ }
+ if strings.HasPrefix(l.Text(), "VERSION_ID=") {
+ host.Version = strings.Replace(strings.TrimSpace(strings.Join(strings.Split(l.Text(), "=")[1:], "")), "\"", "", -1)
}
}
- return ""
+ return host
+}
+
+func (p *PodmanTest) setDefaultRegistriesConfigEnv() {
+ defaultFile := filepath.Join(INTEGRATION_ROOT, "test/registries.conf")
+ os.Setenv("REGISTRIES_CONFIG_PATH", defaultFile)
+}
+
+func (p *PodmanTest) setRegistriesConfigEnv(b []byte) {
+ outfile := filepath.Join(p.TempDir, "registries.conf")
+ os.Setenv("REGISTRIES_CONFIG_PATH", outfile)
+ ioutil.WriteFile(outfile, b, 0644)
+}
+
+func resetRegistriesConfigEnv() {
+ os.Setenv("REGISTRIES_CONFIG_PATH", "")
}
// IsKernelNewThan compares the current kernel version to one provided. If
diff --git a/test/e2e/run_signal_test.go b/test/e2e/run_signal_test.go
index bd00b8aa9..02b6f4941 100644
--- a/test/e2e/run_signal_test.go
+++ b/test/e2e/run_signal_test.go
@@ -63,7 +63,7 @@ var _ = Describe("Podman run with --sig-proxy", func() {
udsPath := filepath.Join(udsDir, "fifo")
syscall.Mkfifo(udsPath, 0600)
- _, pid := podmanTest.PodmanPID([]string{"run", "-it", "-v", fmt.Sprintf("%s:/h", udsDir), fedoraMinimal, "bash", "-c", sigCatch})
+ _, pid := podmanTest.PodmanPID([]string{"run", "-it", "-v", fmt.Sprintf("%s:/h:Z", udsDir), fedoraMinimal, "bash", "-c", sigCatch})
uds, _ := os.OpenFile(udsPath, os.O_RDONLY, 0600)
defer uds.Close()
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 1c86d48bc..7bba1e31e 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -562,7 +562,7 @@ USER mail`
err = os.MkdirAll(vol2, 0755)
Expect(err).To(BeNil())
- session := podmanTest.Podman([]string{"run", "--volume", vol1 + ":/myvol1:ro", "--volume", vol2 + ":/myvol2", ALPINE, "touch", "/myvol2/foo.txt"})
+ session := podmanTest.Podman([]string{"run", "--volume", vol1 + ":/myvol1:z", "--volume", vol2 + ":/myvol2:z", ALPINE, "touch", "/myvol2/foo.txt"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go
index bdbd5e770..7b9612a35 100644
--- a/test/e2e/search_test.go
+++ b/test/e2e/search_test.go
@@ -2,9 +2,7 @@ package integration
import (
"fmt"
- "io/ioutil"
"os"
- "path/filepath"
"strconv"
. "github.com/onsi/ginkgo"
@@ -177,10 +175,7 @@ var _ = Describe("Podman search", func() {
Expect(push.ExitCode()).To(Equal(0))
// registries.conf set up
- regFileBytes := []byte(regFileContents)
- outfile := filepath.Join(podmanTest.TempDir, "registries.conf")
- os.Setenv("REGISTRIES_CONFIG_PATH", outfile)
- ioutil.WriteFile(outfile, regFileBytes, 0644)
+ podmanTest.setRegistriesConfigEnv([]byte(regFileContents))
search := podmanTest.Podman([]string{"search", "localhost:5000/my-alpine"})
search.WaitWithDefaultTimeout()
@@ -191,7 +186,7 @@ var _ = Describe("Podman search", func() {
Expect(search.ErrorToString()).Should(BeEmpty())
// cleanup
- os.Setenv("REGISTRIES_CONFIG_PATH", "")
+ resetRegistriesConfigEnv()
})
It("podman search doesn't attempt HTTP if force secure is true", func() {
@@ -208,10 +203,7 @@ var _ = Describe("Podman search", func() {
Expect(push.ExitCode()).To(Equal(0))
// registries.conf set up
- regFileBytes := []byte(regFileContents)
- outfile := filepath.Join(podmanTest.TempDir, "registries.conf")
- os.Setenv("REGISTRIES_CONFIG_PATH", outfile)
- ioutil.WriteFile(outfile, regFileBytes, 0644)
+ podmanTest.setRegistriesConfigEnv([]byte(regFileContents))
search := podmanTest.Podman([]string{"search", "localhost:5000/my-alpine", "--tls-verify=true"})
search.WaitWithDefaultTimeout()
@@ -222,7 +214,7 @@ var _ = Describe("Podman search", func() {
Expect(match).Should(BeTrue())
// cleanup
- os.Setenv("REGISTRIES_CONFIG_PATH", "")
+ resetRegistriesConfigEnv()
})
It("podman search doesn't attempt HTTP if registry is not listed as insecure", func() {
@@ -239,10 +231,7 @@ var _ = Describe("Podman search", func() {
Expect(push.ExitCode()).To(Equal(0))
// registries.conf set up
- regFileBytes := []byte(badRegFileContents)
- outfile := filepath.Join(podmanTest.TempDir, "registries.conf")
- os.Setenv("REGISTRIES_CONFIG_PATH", outfile)
- ioutil.WriteFile(outfile, regFileBytes, 0644)
+ podmanTest.setRegistriesConfigEnv([]byte(badRegFileContents))
search := podmanTest.Podman([]string{"search", "localhost:5000/my-alpine"})
search.WaitWithDefaultTimeout()
@@ -253,7 +242,7 @@ var _ = Describe("Podman search", func() {
Expect(match).Should(BeTrue())
// cleanup
- os.Setenv("REGISTRIES_CONFIG_PATH", "")
+ resetRegistriesConfigEnv()
})
It("podman search doesn't attempt HTTP if one registry is not listed as insecure", func() {
@@ -278,10 +267,7 @@ var _ = Describe("Podman search", func() {
Expect(push.ExitCode()).To(Equal(0))
// registries.conf set up
- regFileBytes := []byte(regFileContents2)
- outfile := filepath.Join(podmanTest.TempDir, "registries.conf")
- os.Setenv("REGISTRIES_CONFIG_PATH", outfile)
- ioutil.WriteFile(outfile, regFileBytes, 0644)
+ podmanTest.setRegistriesConfigEnv([]byte(regFileContents2))
search := podmanTest.Podman([]string{"search", "my-alpine"})
search.WaitWithDefaultTimeout()
@@ -292,6 +278,6 @@ var _ = Describe("Podman search", func() {
Expect(match).Should(BeTrue())
// cleanup
- os.Setenv("REGISTRIES_CONFIG_PATH", "")
+ resetRegistriesConfigEnv()
})
})
diff --git a/test/registries.conf b/test/registries.conf
index f3bf092b0..6c9d39bbc 100644
--- a/test/registries.conf
+++ b/test/registries.conf
@@ -1,5 +1,5 @@
[registries.search]
-registries = ['registry.access.redhat.com', 'registry.fedoraproject.org', 'docker.io']
+registries = ['docker.io', 'quay.io']
[registries.insecure]
registries = []