diff --git a/cmd/kpod/load.go b/cmd/kpod/load.go index 6061a16e..6fba9eae 100644 --- a/cmd/kpod/load.go +++ b/cmd/kpod/load.go @@ -2,22 +2,14 @@ package main import ( "io" + "io/ioutil" "os" - "io/ioutil" - - "github.com/containers/storage" "github.com/kubernetes-incubator/cri-o/libpod/images" "github.com/pkg/errors" "github.com/urfave/cli" ) -type loadOptions struct { - input string - quiet bool - image string -} - var ( loadFlags = []cli.Flag{ cli.StringFlag{ @@ -44,14 +36,6 @@ var ( // loadCmd gets the image/file to be loaded from the command line // and calls loadImage to load the image to containers-storage func loadCmd(c *cli.Context) error { - config, err := getConfig(c) - if err != nil { - return errors.Wrapf(err, "could not get config") - } - store, err := getStore(config) - if err != nil { - return err - } args := c.Args() var image string @@ -62,6 +46,12 @@ func loadCmd(c *cli.Context) error { return errors.New("too many arguments. Requires exactly 1") } + runtime, err := getRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + input := c.String("input") if input == "/dev/stdin" { @@ -93,33 +83,22 @@ func loadCmd(c *cli.Context) error { } } - opts := loadOptions{ - input: input, - quiet: c.Bool("quiet"), - image: image, + var output io.Writer + if !c.Bool("quiet") { + output = os.Stdout } - return loadImage(store, opts) -} - -// loadImage loads the image from docker-archive or oci to containers-storage -// using the pullImage function -func loadImage(store storage.Store, opts loadOptions) error { - loadOpts := images.CopyOptions{ - Quiet: opts.quiet, - Store: store, - } - - src := images.DockerArchive + ":" + opts.input - if err := images.PullImage(src, false, loadOpts); err != nil { - src = images.OCIArchive + ":" + opts.input + src := images.DockerArchive + ":" + input + if err := runtime.PullImage(src, false, "", output); err != nil { + src = images.OCIArchive + ":" + input // generate full src name with specified image:tag - if opts.image != "" { - src = src + ":" + opts.image + if image != "" { + src = src + ":" + image } - if err := images.PullImage(src, false, loadOpts); err != nil { - return errors.Wrapf(err, "error pulling from %q", opts.input) + if err := runtime.PullImage(src, false, "", output); err != nil { + return errors.Wrapf(err, "error pulling %q", src) } } + return nil } diff --git a/cmd/kpod/pull.go b/cmd/kpod/pull.go index b2495726..e7e78cea 100644 --- a/cmd/kpod/pull.go +++ b/cmd/kpod/pull.go @@ -4,6 +4,7 @@ import ( "os" "fmt" + "github.com/containers/image/docker/reference" "github.com/containers/image/pkg/sysregistries" "github.com/containers/image/transports/alltransports" @@ -21,6 +22,11 @@ var ( Hidden: true, Usage: "Download all tagged images in the repository", }, + cli.StringFlag{ + Name: "signature-policy", + Usage: "`pathname` of signature policy file (not usually used)", + Hidden: true, + }, } pullDescription = "Pulls an image from a registry and stores it locally.\n" + @@ -134,6 +140,9 @@ func pullCmd(c *cli.Context) error { 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 { @@ -141,7 +150,7 @@ func pullCmd(c *cli.Context) error { } for _, fqname := range fqRegistries { fmt.Printf("Trying to pull %s...", fqname) - if err := runtime.PullImage(fqname, c.Bool("all-tags"), os.Stdout); err != nil { + if err := runtime.PullImage(fqname, c.Bool("all-tags"), c.String("signature-policy"), os.Stdout); err != nil { fmt.Printf(" Failed\n") } else { return nil diff --git a/cmd/kpod/push.go b/cmd/kpod/push.go index be9d3a1e..ffc477c0 100644 --- a/cmd/kpod/push.go +++ b/cmd/kpod/push.go @@ -2,12 +2,13 @@ package main import ( "fmt" + "io" "os" "github.com/containers/image/types" "github.com/containers/storage/pkg/archive" + "github.com/kubernetes-incubator/cri-o/libpod" "github.com/kubernetes-incubator/cri-o/libpod/common" - "github.com/kubernetes-incubator/cri-o/libpod/images" "github.com/pkg/errors" "github.com/urfave/cli" "golang.org/x/crypto/ssh/terminal" @@ -70,7 +71,6 @@ func pushCmd(c *cli.Context) error { srcName := c.Args().Get(0) destName := c.Args().Get(1) - signaturePolicy := c.String("signature-policy") registryCredsString := c.String("creds") certPath := c.String("cert-dir") skipVerify := !c.BoolT("tls-verify") @@ -94,19 +94,20 @@ func pushCmd(c *cli.Context) error { registryCreds = creds } - config, err := getConfig(c) + runtime, err := getRuntime(c) if err != nil { - return errors.Wrapf(err, "Could not get config") + return errors.Wrapf(err, "could not create runtime") } - store, err := getStore(config) - if err != nil { - return err + defer runtime.Shutdown(false) + + var writer io.Writer + if !c.Bool("quiet") { + writer = os.Stdout } - options := images.CopyOptions{ + options := libpod.CopyOptions{ Compression: archive.Uncompressed, - SignaturePolicyPath: signaturePolicy, - Store: store, + SignaturePolicyPath: c.String("signature-policy"), DockerRegistryOptions: common.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, DockerCertPath: certPath, @@ -117,8 +118,6 @@ func pushCmd(c *cli.Context) error { SignBy: signBy, }, } - if !c.Bool("quiet") { - options.ReportWriter = os.Stderr - } - return images.PushImage(srcName, destName, options) + + return runtime.PushImage(srcName, destName, options, writer) } diff --git a/cmd/kpod/save.go b/cmd/kpod/save.go index 54680a2e..b1413a7e 100644 --- a/cmd/kpod/save.go +++ b/cmd/kpod/save.go @@ -1,22 +1,16 @@ package main import ( + "io" "os" - "github.com/containers/storage" + "github.com/kubernetes-incubator/cri-o/libpod" "github.com/kubernetes-incubator/cri-o/libpod/images" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) -type saveOptions struct { - output string - quiet bool - format string - images []string -} - var ( saveFlags = []cli.Flag{ cli.StringFlag{ @@ -54,17 +48,18 @@ func saveCmd(c *cli.Context) error { return errors.Errorf("need at least 1 argument") } - config, err := getConfig(c) + runtime, err := getRuntime(c) if err != nil { - return errors.Wrapf(err, "could not get config") + return errors.Wrapf(err, "could not create runtime") } - store, err := getStore(config) - if err != nil { - return err + defer runtime.Shutdown(false) + + var writer io.Writer + if !c.Bool("quiet") { + writer = os.Stdout } output := c.String("output") - if output == "/dev/stdout" { fi := os.Stdout if logrus.IsTerminal(fi) { @@ -72,41 +67,27 @@ func saveCmd(c *cli.Context) error { } } - opts := saveOptions{ - output: output, - quiet: c.Bool("quiet"), - format: c.String("format"), - images: args, - } - - return saveImage(store, opts) -} - -// saveImage pushes the image to docker-archive or oci by -// calling pushImage -func saveImage(store storage.Store, opts saveOptions) error { var dst string - switch opts.format { + switch c.String("format") { case images.OCIArchive: - dst = images.OCIArchive + ":" + opts.output + dst = images.OCIArchive + ":" + output case images.DockerArchive: fallthrough case "": - dst = images.DockerArchive + ":" + opts.output + dst = images.DockerArchive + ":" + output default: - return errors.Errorf("unknown format option %q", opts.format) + return errors.Errorf("unknown format option %q", c.String("format")) } - saveOpts := images.CopyOptions{ + saveOpts := libpod.CopyOptions{ SignaturePolicyPath: "", - Store: store, } // only one image is supported for now // future pull requests will fix this - for _, image := range opts.images { + for _, image := range args { dest := dst + ":" + image - if err := images.PushImage(image, dest, saveOpts); err != nil { + if err := runtime.PushImage(image, dest, saveOpts, writer); err != nil { return errors.Wrapf(err, "unable to save %q", image) } } diff --git a/cmd/kpod/tag.go b/cmd/kpod/tag.go index 78978ebc..b9c38060 100644 --- a/cmd/kpod/tag.go +++ b/cmd/kpod/tag.go @@ -28,6 +28,8 @@ func tagCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "could not create runtime") } + defer runtime.Shutdown(false) + img, err := runtime.GetImage(args[0]) if err != nil { return err @@ -50,7 +52,7 @@ func addImageNames(runtime *libpod.Runtime, image *storage.Image, addNames []str } for _, name := range names { if err := runtime.TagImage(image, name); err != nil { - return errors.Wrapf(err, "error adding names (%v) to image %q", name, image.ID) + return errors.Wrapf(err, "error adding name (%v) to image %q", name, image.ID) } } return nil diff --git a/libpod/image.go b/libpod/image.go index 131df3df..59df09d3 100644 --- a/libpod/image.go +++ b/libpod/image.go @@ -1,13 +1,16 @@ package libpod import ( + "fmt" "io" "strings" "syscall" cp "github.com/containers/image/copy" + dockerarchive "github.com/containers/image/docker/archive" "github.com/containers/image/docker/tarfile" "github.com/containers/image/manifest" + ociarchive "github.com/containers/image/oci/archive" "github.com/containers/image/signature" is "github.com/containers/image/storage" "github.com/containers/image/transports/alltransports" @@ -28,6 +31,15 @@ const ( DefaultRegistry = "docker://" ) +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() +) + // CopyOptions contains the options given when pushing or pulling images type CopyOptions struct { // Compression specifies the type of compression which is applied to @@ -59,12 +71,25 @@ type ImageFilter func(*storage.Image) bool // 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, reportWriter io.Writer) error { +func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath string, reportWriter io.Writer) error { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return fmt.Errorf("runtime is not valid") + } + // 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) if err != nil { defaultName := DefaultRegistry + imgName @@ -76,10 +101,11 @@ func (r *Runtime) PullImage(imgName string, allTags bool, reportWriter io.Writer } splitArr := strings.Split(imgName, ":") + archFile := splitArr[len(splitArr)-1] // supports pulling from docker-archive, oci, and registries - if splitArr[0] == "docker-archive" { - tarSource := tarfile.NewSource(splitArr[len(splitArr)-1]) + if srcRef.Transport().Name() == DockerArchive { + tarSource := tarfile.NewSource(archFile) manifest, err := tarSource.LoadTarManifest() if err != nil { return errors.Errorf("error retrieving manifest.json: %v", err) @@ -91,7 +117,7 @@ func (r *Runtime) PullImage(imgName string, allTags bool, reportWriter io.Writer } 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(r.imageContext) + newImg, err := srcRef.NewImage(sc) if err != nil { return err } @@ -104,9 +130,17 @@ func (r *Runtime) PullImage(imgName string, allTags bool, reportWriter io.Writer } } } - } else if splitArr[0] == "oci" { - // needs to be implemented in future - return errors.Errorf("oci not supported") + } 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) + } + + 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") + } + images = append(images, manifest.Annotations["org.opencontainers.image.ref.name"]) } else { images = append(images, imgName) } @@ -122,10 +156,13 @@ func (r *Runtime) PullImage(imgName string, allTags bool, reportWriter io.Writer } defer policyContext.Destroy() - copyOptions := common.GetCopyOptions(reportWriter, "", nil, nil, common.SigningOptions{}) - + copyOptions := common.GetCopyOptions(reportWriter, signaturePolicyPath, nil, nil, common.SigningOptions{}) for _, image := range images { - destRef, err := is.Transport.ParseStoreReference(r.store, srcRef.DockerReference().String()) + 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) } @@ -138,6 +175,13 @@ func (r *Runtime) PullImage(imgName string, allTags bool, reportWriter io.Writer // 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 { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return fmt.Errorf("runtime is not valid") + } + // PushImage pushes the src image to the destination //func PushImage(source, destination string, options CopyOptions) error { if source == "" || destination == "" { @@ -150,14 +194,19 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio return errors.Wrapf(err, "error getting destination imageReference for %q", destination) } - policyContext, err := common.GetPolicyContext(r.GetConfig().SignaturePolicyPath) + signaturePolicyPath := r.config.SignaturePolicyPath + if options.SignaturePolicyPath != "" { + signaturePolicyPath = options.SignaturePolicyPath + } + + policyContext, err := common.GetPolicyContext(signaturePolicyPath) if err != nil { - return errors.Wrapf(err, "Could not get default policy context for signature policy path %q", r.GetConfig().SignaturePolicyPath) + return errors.Wrapf(err, "Could not get default policy context for signature policy path %q", signaturePolicyPath) } defer policyContext.Destroy() // Look up the image name and its layer, then build the imagePushData from // the image - img, err := r.GetImage(source) + img, err := r.getImage(source) if err != nil { return errors.Wrapf(err, "error locating image %q for importing settings", source) } @@ -175,7 +224,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio return errors.Wrapf(err, "error copying layers and metadata") } - copyOptions := common.GetCopyOptions(reportWriter, r.GetConfig().SignaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions) + copyOptions := common.GetCopyOptions(reportWriter, signaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions) // Copy the image to the remote destination err = cp.Image(policyContext, dest, src, copyOptions) @@ -187,6 +236,13 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio // TagImage adds a tag to the given image func (r *Runtime) TagImage(image *storage.Image, tag string) error { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return fmt.Errorf("runtime is not valid") + } + tags, err := r.store.Names(image.ID) if err != nil { return err @@ -202,6 +258,13 @@ func (r *Runtime) TagImage(image *storage.Image, tag string) error { // UntagImage removes a tag from the given image func (r *Runtime) UntagImage(image *storage.Image, tag string) error { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return fmt.Errorf("runtime is not valid") + } + tags, err := r.store.Names(image.ID) if err != nil { return err @@ -219,6 +282,13 @@ func (r *Runtime) UntagImage(image *storage.Image, tag string) error { // RemoveImage deletes an image from local storage // Images being used by running containers cannot be removed func (r *Runtime) RemoveImage(image *storage.Image) error { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return fmt.Errorf("runtime is not valid") + } + _, err := r.store.DeleteImage(image.ID, false) return err } @@ -227,6 +297,16 @@ func (r *Runtime) RemoveImage(image *storage.Image) error { // storage // If no matching image can be found, an error is returned func (r *Runtime) GetImage(image string) (*storage.Image, error) { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return nil, fmt.Errorf("runtime is not valid") + } + return r.getImage(image) +} + +func (r *Runtime) getImage(image string) (*storage.Image, error) { var img *storage.Image ref, err := is.Transport.ParseStoreReference(r.store, image) if err == nil { @@ -247,7 +327,14 @@ func (r *Runtime) GetImage(image string) (*storage.Image, error) { // GetImageRef searches for and returns a new types.Image matching the given name or ID in the given store. func (r *Runtime) GetImageRef(image string) (types.Image, error) { - img, err := r.GetImage(image) + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return nil, fmt.Errorf("runtime is not valid") + } + + img, err := r.getImage(image) if err != nil { return nil, errors.Wrapf(err, "unable to locate image %q", image) }