diff options
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 Binary files differnew file mode 100644 index 000000000..0cad9f108 --- /dev/null +++ b/libpod/image/testdata/docker-name-only.tar.xz diff --git a/libpod/image/testdata/docker-registry-name.tar.xz b/libpod/image/testdata/docker-registry-name.tar.xz Binary files differnew file mode 100644 index 000000000..181816c2e --- /dev/null +++ b/libpod/image/testdata/docker-registry-name.tar.xz diff --git a/libpod/image/testdata/docker-two-images.tar.xz b/libpod/image/testdata/docker-two-images.tar.xz Binary files differnew file mode 100644 index 000000000..148d8a86b --- /dev/null +++ b/libpod/image/testdata/docker-two-images.tar.xz diff --git a/libpod/image/testdata/docker-two-names.tar.xz b/libpod/image/testdata/docker-two-names.tar.xz Binary files differnew file mode 100644 index 000000000..07fbc479c --- /dev/null +++ b/libpod/image/testdata/docker-two-names.tar.xz diff --git a/libpod/image/testdata/docker-unnamed.tar.xz b/libpod/image/testdata/docker-unnamed.tar.xz Binary files differnew file mode 100644 index 000000000..ba6ea1bae --- /dev/null +++ b/libpod/image/testdata/docker-unnamed.tar.xz diff --git a/libpod/image/testdata/oci-name-only.tar.gz b/libpod/image/testdata/oci-name-only.tar.gz Binary files differnew file mode 100644 index 000000000..57bc07564 --- /dev/null +++ b/libpod/image/testdata/oci-name-only.tar.gz diff --git a/libpod/image/testdata/oci-non-docker-name.tar.gz b/libpod/image/testdata/oci-non-docker-name.tar.gz Binary files differnew file mode 100644 index 000000000..5ffc0eabd --- /dev/null +++ b/libpod/image/testdata/oci-non-docker-name.tar.gz diff --git a/libpod/image/testdata/oci-registry-name.tar.gz b/libpod/image/testdata/oci-registry-name.tar.gz Binary files differnew file mode 100644 index 000000000..e6df87339 --- /dev/null +++ b/libpod/image/testdata/oci-registry-name.tar.gz diff --git a/libpod/image/testdata/oci-unnamed.tar.gz b/libpod/image/testdata/oci-unnamed.tar.gz Binary files differnew file mode 100644 index 000000000..de445fdf8 --- /dev/null +++ b/libpod/image/testdata/oci-unnamed.tar.gz 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 = [] |