diff --git a/cmd/kpod/load.go b/cmd/kpod/load.go index 0a264dc9..b93d2fc9 100644 --- a/cmd/kpod/load.go +++ b/cmd/kpod/load.go @@ -95,14 +95,19 @@ func loadCmd(c *cli.Context) error { output = os.Stdout } + options := libpod.CopyOptions{ + SignaturePolicyPath: c.String("signature-policy"), + Writer: output, + } + src := libpod.DockerArchive + ":" + input - if err := runtime.PullImage(src, false, c.String("signature-policy"), output); err != nil { + if err := runtime.PullImage(src, options); err != nil { src = libpod.OCIArchive + ":" + input // generate full src name with specified image:tag if image != "" { src = src + ":" + image } - if err := runtime.PullImage(src, false, "", output); err != nil { + if err := runtime.PullImage(src, options); err != nil { return errors.Wrapf(err, "error pulling %q", src) } } diff --git a/cmd/kpod/logout.go b/cmd/kpod/logout.go index 9438b81a..58734615 100644 --- a/cmd/kpod/logout.go +++ b/cmd/kpod/logout.go @@ -38,6 +38,9 @@ func logoutCmd(c *cli.Context) error { if len(args) > 1 { return errors.Errorf("too many arguments, logout takes only 1 argument") } + if len(args) == 0 { + return errors.Errorf("registry must be given") + } var server string if len(args) == 1 { server = args[0] diff --git a/cmd/kpod/pull.go b/cmd/kpod/pull.go index 0c06f458..9cd4c2d2 100644 --- a/cmd/kpod/pull.go +++ b/cmd/kpod/pull.go @@ -1,14 +1,14 @@ package main import ( + "fmt" "os" - "fmt" + "golang.org/x/crypto/ssh/terminal" - "github.com/containers/image/docker/reference" - "github.com/containers/image/pkg/sysregistries" - "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" + "github.com/kubernetes-incubator/cri-o/libpod" + "github.com/kubernetes-incubator/cri-o/libpod/common" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -16,16 +16,18 @@ import ( var ( pullFlags = []cli.Flag{ - cli.BoolFlag{ - // all-tags is hidden since it has not been implemented yet - Name: "all-tags, a", - Hidden: true, - Usage: "Download all tagged images in the repository", - }, cli.StringFlag{ Name: "signature-policy", Usage: "`pathname` of signature policy file (not usually used)", }, + cli.StringFlag{ + Name: "authfile", + Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", + }, + cli.StringFlag{ + Name: "creds", + Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", + }, } pullDescription = "Pulls an image from a registry and stores it locally.\n" + @@ -41,83 +43,14 @@ var ( } ) -// struct for when a user passes a short or incomplete -// image name -type imagePullStruct struct { - imageName string - tag string - registry string - hasRegistry bool - transport string -} - -func (ips imagePullStruct) returnFQName() string { - return fmt.Sprintf("%s%s/%s:%s", ips.transport, ips.registry, ips.imageName, ips.tag) -} - -func getRegistriesToTry(image string) ([]string, error) { - var registries []string - var imageError = fmt.Sprintf("unable to parse '%s'\n", image) - imgRef, err := reference.Parse(image) - if err != nil { - return nil, errors.Wrapf(err, imageError) - } - tagged, isTagged := imgRef.(reference.NamedTagged) - tag := "latest" - if isTagged { - tag = tagged.Tag() - } - hasDomain := true - registry := reference.Domain(imgRef.(reference.Named)) - if registry == "" { - hasDomain = false - } - imageName := reference.Path(imgRef.(reference.Named)) - pImage := imagePullStruct{ - imageName, - tag, - registry, - hasDomain, - "docker://", - } - if pImage.hasRegistry { - // If input has a registry, we have to assume they included an image - // name but maybe not a tag - pullRef, err := alltransports.ParseImageName(pImage.returnFQName()) - if err != nil { - return nil, errors.Errorf(imageError) - } - registries = append(registries, pullRef.DockerReference().String()) - } else { - // No registry means we check the globals registries configuration file - // and assemble a list of candidate sources to try - registryConfigPath := "" - envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") - if len(envOverride) > 0 { - registryConfigPath = envOverride - } - searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) - if err != nil { - fmt.Println(err) - return nil, errors.Errorf("unable to parse the registries.conf file and"+ - " the image name '%s' is incomplete.", imageName) - } - for _, searchRegistry := range searchRegistries { - pImage.registry = searchRegistry - pullRef, err := alltransports.ParseImageName(pImage.returnFQName()) - if err != nil { - return nil, errors.Errorf("unable to parse '%s'", pImage.returnFQName()) - } - registries = append(registries, pullRef.DockerReference().String()) - } - } - return registries, nil -} - // pullCmd gets the data from the command line and calls pullImage // to copy an image from a registry to a local machine func pullCmd(c *cli.Context) error { - var fqRegistries []string + runtime, err := getRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) args := c.Args() if len(args) == 0 { @@ -132,31 +65,34 @@ func pullCmd(c *cli.Context) error { return err } image := args[0] - srcRef, err := alltransports.ParseImageName(image) - if err != nil { - fqRegistries, err = getRegistriesToTry(image) - if err != nil { - fmt.Println(err) - } - } else { - fqRegistries = append(fqRegistries, srcRef.DockerReference().String()) - } - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - for _, fqname := range fqRegistries { - fmt.Printf("Trying to pull %s...", fqname) - if err := runtime.PullImage(fqname, c.Bool("all-tags"), c.String("signature-policy"), os.Stdout); err != nil { - fmt.Printf(" Failed\n") - } else { - return nil + var registryCreds *types.DockerAuthConfig + if c.String("creds") != "" { + creds, err := common.ParseRegistryCreds(c.String("creds")) + if err != nil { + if err == common.ErrNoPassword { + fmt.Print("Password: ") + password, err := terminal.ReadPassword(0) + if err != nil { + return errors.Wrapf(err, "could not read password from terminal") + } + creds.Password = string(password) + } else { + return err + } } + registryCreds = creds } - return errors.Errorf("error pulling image from %q", image) + + options := libpod.CopyOptions{ + SignaturePolicyPath: c.String("signature-policy"), + AuthFile: c.String("authfile"), + DockerRegistryOptions: common.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + }, + Writer: os.Stdout, + } + + return runtime.PullImage(image, options) + } diff --git a/cmd/kpod/push.go b/cmd/kpod/push.go index a019f54a..506d97f4 100644 --- a/cmd/kpod/push.go +++ b/cmd/kpod/push.go @@ -45,6 +45,10 @@ var ( Name: "quiet, q", Usage: "don't output progress information when pushing images", }, + cli.StringFlag{ + Name: "authfile", + Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", + }, } pushDescription = fmt.Sprintf(` Pushes an image to a specified location. @@ -120,7 +124,9 @@ func pushCmd(c *cli.Context) error { RemoveSignatures: removeSignatures, SignBy: signBy, }, + AuthFile: c.String("authfile"), + Writer: writer, } - return runtime.PushImage(srcName, destName, options, writer) + return runtime.PushImage(srcName, destName, options) } diff --git a/cmd/kpod/save.go b/cmd/kpod/save.go index fac73e65..cfe90a95 100644 --- a/cmd/kpod/save.go +++ b/cmd/kpod/save.go @@ -83,13 +83,14 @@ func saveCmd(c *cli.Context) error { saveOpts := libpod.CopyOptions{ SignaturePolicyPath: "", + Writer: writer, } // only one image is supported for now // future pull requests will fix this for _, image := range args { dest := dst + ":" + image - if err := runtime.PushImage(image, dest, saveOpts, writer); err != nil { + if err := runtime.PushImage(image, dest, saveOpts); err != nil { return errors.Wrapf(err, "unable to save %q", image) } } diff --git a/completions/bash/kpod b/completions/bash/kpod index 8f06d56c..e23bf693 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -170,6 +170,8 @@ _kpod_logs() { _kpod_pull() { local options_with_args=" + --authfile + --creds --signature-policy " local boolean_options=" @@ -222,18 +224,20 @@ _kpod_mount() { _kpod_push() { local boolean_options=" - --disable-compression - -D - --quiet - -q - --signature-policy - --certs - --tls-verify - --remove-signatures - --sign-by + --disable-compression + -D + --quiet + -q + --remove-signatures + --tls-verify " local options_with_args=" + --authfile + --cert-dir + --creds + --sign-by + --signature-policy " local all_options="$options_with_args $boolean_options" diff --git a/docs/kpod-pull.1.md b/docs/kpod-pull.1.md index 91b9af71..a3f5a5e8 100644 --- a/docs/kpod-pull.1.md +++ b/docs/kpod-pull.1.md @@ -21,10 +21,10 @@ images from archives and local storage using different transports. ## imageID Image stored in local container/storage -## DESTINATION +## SOURCE - The DESTINATION is a location to store container images - The Image "DESTINATION" uses a "transport":"details" format. + The SOURCE is a location to get container images + The Image "SOURCE" uses a "transport":"details" format. Multiple transports are supported: @@ -32,7 +32,7 @@ Image stored in local container/storage An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection. **docker://**_docker-reference_ - An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$HOME/.docker/config.json`, which is set e.g. using `(docker login)`. + An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set e.g. using `(kpod login)`. **docker-archive:**_path_[**:**_docker-reference_] An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest. @@ -40,7 +40,7 @@ Image stored in local container/storage **docker-daemon:**_docker-reference_ An image _docker-reference_ stored in the docker daemon internal storage. _docker-reference_ must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID). - **oci:**_path_**:**_tag_ + **oci-archive:**_path_**:**_tag_ An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_. **ostree:**_image_[**@**_/absolute/repo/path_] @@ -54,6 +54,14 @@ Image stored in local container/storage ## OPTIONS +**--authfile** + +Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json + +**--creds** + +Credentials (USERNAME:PASSWORD) to use for authenticating to a registry + **--signature-policy="PATHNAME"** Pathname of a signature policy file to use. It is not recommended that this @@ -75,8 +83,30 @@ Writing manifest to image destination Storing signatures ``` +``` +# kpod pull --authfile temp-auths/myauths.json docker://docker.io/umohnani/finaltest +Trying to pull docker.io/umohnani/finaltest:latest...Getting image source signatures +Copying blob sha256:6d987f6f42797d81a318c40d442369ba3dc124883a0964d40b0c8f4f7561d913 + 1.90 MB / 1.90 MB [========================================================] 0s +Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156 + 1.41 KB / 1.41 KB [========================================================] 0s +Writing manifest to image destination +Storing signatures +``` + +``` +# kpod pull docker.io/umohnani/finaltest +Trying to pull docker.io/umohnani/finaltest:latest...Getting image source signatures +Copying blob sha256:6d987f6f42797d81a318c40d442369ba3dc124883a0964d40b0c8f4f7561d913 + 1.90 MB / 1.90 MB [========================================================] 0s +Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156 + 1.41 KB / 1.41 KB [========================================================] 0s +Writing manifest to image destination +Storing signatures +``` + ## SEE ALSO -kpod(1), crio(8), crio.conf(5) +kpod(1), kpod-push(1), crio(8), crio.conf(5) ## HISTORY July 2017, Originally compiled by Urvashi Mohnani diff --git a/docs/kpod-push.1.md b/docs/kpod-push.1.md index 6970a172..80161503 100644 --- a/docs/kpod-push.1.md +++ b/docs/kpod-push.1.md @@ -26,7 +26,7 @@ Image stored in local container/storage An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection. **docker://**_docker-reference_ - An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$HOME/.docker/config.json`, which is set e.g. using `(docker login)`. + An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set e.g. using `(kpod login)`. **docker-archive:**_path_[**:**_docker-reference_] An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest. @@ -34,7 +34,7 @@ Image stored in local container/storage **docker-daemon:**_docker-reference_ An image _docker-reference_ stored in the docker daemon internal storage. _docker-reference_ must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID). - **oci:**_path_**:**_tag_ + **oci-archive:**_path_**:**_tag_ An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_. **ostree:**_image_[**@**_/absolute/repo/path_] @@ -42,6 +42,10 @@ Image stored in local container/storage ## OPTIONS +**--authfile** + +Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json + **--creds="CREDENTIALS"** Credentials (USERNAME:PASSWORD) to use for authenticating to a registry @@ -84,7 +88,7 @@ This example extracts the imageID image to a local directory in docker format. This example extracts the imageID image to a local directory in oci format. - `# kpod push imageID oci:/path/to/layout` + `# kpod push imageID oci-archive:/path/to/layout:image:tag` This example extracts the imageID image to a container registry named registry.example.com @@ -94,5 +98,19 @@ This example extracts the imageID image and puts into the local docker container `# kpod push imageID docker-daemon:image:tag` +This example pushes the alpine image to umohnani/alpine on dockerhub and reads the creds from +the path given to --authfile + +``` +# kpod push --authfile temp-auths/myauths.json alpine docker://docker.io/umohnani/alpine +Getting image source signatures +Copying blob sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0 + 4.03 MB / 4.03 MB [========================================================] 1s +Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156 + 1.41 KB / 1.41 KB [========================================================] 1s +Writing manifest to image destination +Storing signatures +``` + ## SEE ALSO -kpod(1) +kpod(1), kpod-pull(1), crio(8), crio.conf(5) diff --git a/libpod/common/common.go b/libpod/common/common.go index 332d4c9c..775d391d 100644 --- a/libpod/common/common.go +++ b/libpod/common/common.go @@ -17,15 +17,15 @@ var ( ) // 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) *cp.Options { +func GetCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, authFile string) *cp.Options { if srcDockerRegistry == nil { srcDockerRegistry = &DockerRegistryOptions{} } if destDockerRegistry == nil { destDockerRegistry = &DockerRegistryOptions{} } - srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath) - destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath) + srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath, authFile) + destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath, authFile) return &cp.Options{ RemoveSignatures: signing.RemoveSignatures, SignBy: signing.SignBy, diff --git a/libpod/common/docker_registry_options.go b/libpod/common/docker_registry_options.go index fdbaa059..24fa5c03 100644 --- a/libpod/common/docker_registry_options.go +++ b/libpod/common/docker_registry_options.go @@ -22,12 +22,13 @@ type DockerRegistryOptions struct { // GetSystemContext constructs a new system context from the given signaturePolicy path and the // values in the DockerRegistryOptions -func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath string) *types.SystemContext { +func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath, authFile string) *types.SystemContext { sc := &types.SystemContext{ SignaturePolicyPath: signaturePolicyPath, DockerAuthConfig: o.DockerRegistryCreds, DockerCertPath: o.DockerCertPath, DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify, + AuthFilePath: authFile, } return sc } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 23653645..a614d2e4 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "os" "strings" "syscall" "time" @@ -14,6 +15,7 @@ import ( "github.com/containers/image/docker/tarfile" "github.com/containers/image/manifest" ociarchive "github.com/containers/image/oci/archive" + "github.com/containers/image/pkg/sysregistries" "github.com/containers/image/signature" is "github.com/containers/image/storage" "github.com/containers/image/transports" @@ -42,6 +44,9 @@ var ( // 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 = "dir" ) // CopyOptions contains the options given when pushing or pulling images @@ -61,6 +66,10 @@ type CopyOptions struct { // SigningPolicyPath this points to a alternative signature policy file, used mainly for testing SignaturePolicyPath string + // AuthFile is the path of the cached credentials file defined by the user + AuthFile string + // Writer is the reportWriter for the output + Writer io.Writer } // Image API @@ -76,45 +85,124 @@ type ImageFilterParams struct { ImageInput string } +// struct for when a user passes a short or incomplete +// image name +type imageDecomposeStruct struct { + imageName string + tag string + registry string + hasRegistry bool + transport string +} + // ImageFilter is a function to determine whether an image is included in // command output. Images to be outputted are tested using the function. A true // return will include the image, a false return will exclude it. type ImageFilter func(*storage.Image, *types.ImageInspectInfo) bool -// PullImage pulls an image from configured registries -// By default, only the latest tag (or a specific tag if requested) will be -// pulled. If allTags is true, all tags for the requested image will be pulled. -// Signature validation will be performed if the Runtime has been appropriately -// configured -func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath string, reportWriter io.Writer) error { - r.lock.Lock() - defer r.lock.Unlock() +func (ips imageDecomposeStruct) returnFQName() string { + return fmt.Sprintf("%s%s/%s:%s", ips.transport, ips.registry, ips.imageName, ips.tag) +} - if !r.valid { - return ErrRuntimeStopped - } - - // PullImage copies the image from the source to the destination - var ( - images []string - ) - - if signaturePolicyPath == "" { - signaturePolicyPath = r.config.SignaturePolicyPath - } - - sc := common.GetSystemContext(signaturePolicyPath, "") - - srcRef, err := alltransports.ParseImageName(imgName) +func getRegistriesToTry(image string, store storage.Store) ([]*pullStruct, error) { + var pStructs []*pullStruct + var imageError = fmt.Sprintf("unable to parse '%s'\n", image) + imgRef, err := reference.Parse(image) if err != nil { - defaultName := DefaultRegistry + imgName - srcRef2, err2 := alltransports.ParseImageName(defaultName) - if err2 != nil { - return errors.Errorf("error parsing image name %q: %v", defaultName, err2) + return nil, errors.Wrapf(err, imageError) + } + tagged, isTagged := imgRef.(reference.NamedTagged) + tag := "latest" + if isTagged { + tag = tagged.Tag() + } + hasDomain := true + registry := reference.Domain(imgRef.(reference.Named)) + if registry == "" { + hasDomain = false + } + imageName := reference.Path(imgRef.(reference.Named)) + pImage := imageDecomposeStruct{ + imageName, + tag, + registry, + hasDomain, + "docker://", + } + if pImage.hasRegistry { + // If input has a registry, we have to assume they included an image + // name but maybe not a tag + srcRef, err := alltransports.ParseImageName(pImage.returnFQName()) + if err != nil { + return nil, errors.Errorf(imageError) + } + pStruct := &pullStruct{ + image: srcRef.DockerReference().String(), + srcRef: srcRef, + } + pStructs = append(pStructs, pStruct) + } else { + // No registry means we check the globals registries configuration file + // and assemble a list of candidate sources to try + registryConfigPath := "" + envOverride := os.Getenv("REGISTRIES_CONFIG_PATH") + if len(envOverride) > 0 { + registryConfigPath = envOverride + } + searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath}) + if err != nil { + fmt.Println(err) + return nil, errors.Errorf("unable to parse the registries.conf file and"+ + " the image name '%s' is incomplete.", imageName) + } + for _, searchRegistry := range searchRegistries { + pImage.registry = searchRegistry + srcRef, err := alltransports.ParseImageName(pImage.returnFQName()) + if err != nil { + return nil, errors.Errorf("unable to parse '%s'", pImage.returnFQName()) + } + pStruct := &pullStruct{ + image: srcRef.DockerReference().String(), + srcRef: srcRef, + } + pStructs = append(pStructs, pStruct) } - srcRef = srcRef2 } + for _, pStruct := range pStructs { + destRef, err := is.Transport.ParseStoreReference(store, pStruct.image) + if err != nil { + return nil, errors.Errorf("error parsing dest reference name: %v", err) + } + pStruct.dstRef = destRef + } + return pStructs, nil +} + +type pullStruct struct { + image string + srcRef types.ImageReference + dstRef types.ImageReference +} + +func (r *Runtime) getPullStruct(srcRef types.ImageReference, destName string) (*pullStruct, error) { + reference := destName + if srcRef.DockerReference() != nil { + reference = srcRef.DockerReference().String() + } + destRef, err := is.Transport.ParseStoreReference(r.store, reference) + if err != nil { + return nil, errors.Errorf("error parsing dest reference name: %v", err) + } + return &pullStruct{ + image: destName, + srcRef: srcRef, + dstRef: destRef, + }, nil +} + +func (r *Runtime) getPullListFromRef(srcRef types.ImageReference, imgName string, sc *types.SystemContext) ([]*pullStruct, error) { + var pullStructs []*pullStruct splitArr := strings.Split(imgName, ":") archFile := splitArr[len(splitArr)-1] @@ -123,41 +211,107 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st tarSource := tarfile.NewSource(archFile) manifest, err := tarSource.LoadTarManifest() if err != nil { - return errors.Errorf("error retrieving manifest.json: %v", err) + return nil, errors.Errorf("error retrieving manifest.json: %v", err) } - // to pull all the images stored in one tar file - for i := range manifest { - if manifest[i].RepoTags != nil { - images = append(images, manifest[i].RepoTags[0]) - } else { - // create an image object and use the hex value of the digest as the image ID - // for parsing the store reference - newImg, err := srcRef.NewImage(sc) - if err != nil { - return err - } - defer newImg.Close() - digest := newImg.ConfigInfo().Digest - if err := digest.Validate(); err == nil { - images = append(images, "@"+digest.Hex()) - } else { - return errors.Wrapf(err, "error getting config info") - } + // to pull the first image stored in the tar file + if len(manifest) == 0 { + // create an image object and use the hex value of the digest as the image ID + // for parsing the store reference + newImg, err := srcRef.NewImage(sc) + if err != nil { + return nil, err } + defer newImg.Close() + digest := newImg.ConfigInfo().Digest + if err := digest.Validate(); err == nil { + pullInfo, err := r.getPullStruct(srcRef, "@"+digest.Hex()) + if err != nil { + return nil, err + } + pullStructs = append(pullStructs, pullInfo) + } else { + return nil, errors.Wrapf(err, "error getting config info") + } + } else { + pullInfo, err := r.getPullStruct(srcRef, manifest[0].RepoTags[0]) + if err != nil { + return nil, err + } + pullStructs = append(pullStructs, pullInfo) } + } else if srcRef.Transport().Name() == OCIArchive { // retrieve the manifest from index.json to access the image name manifest, err := ociarchive.LoadManifestDescriptor(srcRef) if err != nil { - return errors.Wrapf(err, "error loading manifest for %q", srcRef) + return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef) } if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { - return errors.Errorf("error, archive doesn't have a name annotation. Cannot store image with no name") + return nil, errors.Errorf("error, archive doesn't have a name annotation. Cannot store image with no name") } - images = append(images, manifest.Annotations["org.opencontainers.image.ref.name"]) + pullInfo, err := r.getPullStruct(srcRef, manifest.Annotations["org.opencontainers.image.ref.name"]) + if err != nil { + return nil, err + } + pullStructs = append(pullStructs, pullInfo) + } else if srcRef.Transport().Name() == DirTransport { + // supports pull from a directory + image := splitArr[1] + // remove leading "/" + if image[:1] == "/" { + image = image[1:] + } + pullInfo, err := r.getPullStruct(srcRef, image) + if err != nil { + return nil, err + } + pullStructs = append(pullStructs, pullInfo) } else { - images = append(images, imgName) + pullInfo, err := r.getPullStruct(srcRef, imgName) + if err != nil { + return nil, err + } + pullStructs = append(pullStructs, pullInfo) + } + return pullStructs, nil +} + +// PullImage pulls an image from configured registries +// By default, only the latest tag (or a specific tag if requested) will be +// pulled. If allTags is true, all tags for the requested image will be pulled. +// Signature validation will be performed if the Runtime has been appropriately +// configured +func (r *Runtime) PullImage(imgName string, options CopyOptions) error { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return ErrRuntimeStopped + } + + // PullImage copies the image from the source to the destination + var pullStructs []*pullStruct + + signaturePolicyPath := r.config.SignaturePolicyPath + if options.SignaturePolicyPath != "" { + signaturePolicyPath = options.SignaturePolicyPath + } + + sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile) + + srcRef, err := alltransports.ParseImageName(imgName) + if err != nil { + // could be trying to pull from registry with short name + pullStructs, err = getRegistriesToTry(imgName, r.store) + if err != nil { + return errors.Wrap(err, "error getting default registries to try") + } + } else { + pullStructs, err = r.getPullListFromRef(srcRef, imgName, sc) + if err != nil { + return errors.Wrapf(err, "error getting pullStruct info to pull image %q", imgName) + } } policy, err := signature.DefaultPolicy(sc) @@ -171,25 +325,21 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st } defer policyContext.Destroy() - copyOptions := common.GetCopyOptions(reportWriter, signaturePolicyPath, nil, nil, common.SigningOptions{}) - for _, image := range images { - reference := image - if srcRef.DockerReference() != nil { - reference = srcRef.DockerReference().String() - } - destRef, err := is.Transport.ParseStoreReference(r.store, reference) - if err != nil { - return errors.Errorf("error parsing dest reference name: %v", err) - } - if err = cp.Image(policyContext, destRef, srcRef, copyOptions); err != nil { - return errors.Errorf("error loading image %q: %v", image, err) + copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, &options.DockerRegistryOptions, nil, options.SigningOptions, options.AuthFile) + + for _, imageInfo := range pullStructs { + fmt.Printf("Trying to pull %s...\n", imageInfo.image) + if err = cp.Image(policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions); err != nil { + fmt.Println("Failed") + } else { + return nil } } - return nil + return errors.Wrapf(err, "error pulling image from %q", imgName) } // PushImage pushes the given image to a location described by the given path -func (r *Runtime) PushImage(source string, destination string, options CopyOptions, reportWriter io.Writer) error { +func (r *Runtime) PushImage(source string, destination string, options CopyOptions) error { r.lock.Lock() defer r.lock.Unlock() @@ -214,9 +364,16 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio signaturePolicyPath = options.SignaturePolicyPath } - policyContext, err := common.GetPolicyContext(signaturePolicyPath) + sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile) + + policy, err := signature.DefaultPolicy(sc) if err != nil { - return errors.Wrapf(err, "Could not get default policy context for signature policy path %q", signaturePolicyPath) + return err + } + + policyContext, err := signature.NewPolicyContext(policy) + if err != nil { + return err } defer policyContext.Destroy() // Look up the image name and its layer, then build the imagePushData from @@ -239,7 +396,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio return errors.Wrapf(err, "error copying layers and metadata") } - copyOptions := common.GetCopyOptions(reportWriter, signaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions) + copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions, options.AuthFile) // Copy the image to the remote destination err = cp.Image(policyContext, dest, src, copyOptions) diff --git a/test/kpod_pull.bats b/test/kpod_pull.bats index 3e58397d..c12c6241 100644 --- a/test/kpod_pull.bats +++ b/test/kpod_pull.bats @@ -76,3 +76,63 @@ function teardown() { echo "$output" [ "$status" -eq 0 ] } + +@test "kpod pull from docker-archive" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull alpine + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alp.tar alpine + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker-archive:alp.tar + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine + echo "$output" + [ "$status" -eq 0 ] + rm -f alp.tar +} + +@test "kpod pull from oci-archive" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull alpine + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} save --format oci-archive -o oci-alp.tar alpine + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull oci-archive:oci-alp.tar + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine + echo "$output" + [ "$status" -eq 0 ] + rm -f oci-alp.tar +} + +@test "kpod pull from local directory" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull alpine + echo "$output" + [ "$status" -eq 0 ] + run mkdir test_pull_dir + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} push alpine dir:test_pull_dir + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull dir:test_pull_dir + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi test_pull_dir + echo "$output" + [ "$status" -eq 0 ] + rm -rf test_pull_dir +} diff --git a/test/kpod_push.bats b/test/kpod_push.bats index caa6916b..3345f2ab 100644 --- a/test/kpod_push.bats +++ b/test/kpod_push.bats @@ -49,17 +49,14 @@ function teardown() { [ "$status" -eq 0 ] } -@test "kpod push to oci without compression" { +@test "kpod push to oci-archive without compression" { run ${KPOD_BINARY} $KPOD_OPTIONS pull "$IMAGE" echo "$output" [ "$status" -eq 0 ] - run mkdir /tmp/oci-busybox + run ${KPOD_BINARY} $KPOD_OPTIONS push "$IMAGE" oci-archive:/tmp/oci-busybox.tar:alpine echo "$output" [ "$status" -eq 0 ] - run ${KPOD_BINARY} $KPOD_OPTIONS push "$IMAGE" oci:/tmp/oci-busybox:busybox - echo "$output" - [ "$status" -eq 0 ] - rm -rf /tmp/oci-busybox + rm -f /tmp/oci-busybox.tar run ${KPOD_BINARY} $KPOD_OPTIONS rmi "$IMAGE" echo "$output" [ "$status" -eq 0 ]