package main import ( "io" "os" "strings" "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" 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" ) const ( ociManifestDir = "oci-dir" v2s2ManifestDir = "docker-dir" ) var ( saveFlags = []cli.Flag{ cli.BoolFlag{ Name: "compress", Usage: "compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)", }, cli.StringFlag{ Name: "output, o", Usage: "Write to a file, default is STDOUT", Value: "/dev/stdout", }, cli.BoolFlag{ Name: "quiet, q", Usage: "Suppress the output", }, cli.StringFlag{ Name: "format", Usage: "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-dir (directory with v2s2 manifest type)", }, } saveDescription = ` Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive` saveCommand = cli.Command{ Name: "save", Usage: "Save image to an archive", Description: saveDescription, Flags: saveFlags, Action: saveCmd, ArgsUsage: "", SkipArgReorder: true, } ) // saveCmd saves the image to either docker-archive or oci func saveCmd(c *cli.Context) error { args := c.Args() if len(args) == 0 { return errors.Errorf("need at least 1 argument") } if err := validateFlags(c, saveFlags); err != nil { return err } runtime, err := libpodruntime.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) if c.IsSet("compress") && (c.String("format") != ociManifestDir && c.String("format") != v2s2ManifestDir && c.String("format") == "") { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") } var writer io.Writer if !c.Bool("quiet") { writer = os.Stderr } output := c.String("output") if output == "/dev/stdout" { fi := os.Stdout if logrus.IsTerminal(fi) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } } if err := validateFileName(output); err != nil { return err } var dst, manifestType string switch c.String("format") { case libpod.OCIArchive: dst = libpod.OCIArchive + ":" + output case "oci-dir": dst = libpod.DirTransport + ":" + output manifestType = imgspecv1.MediaTypeImageManifest case "docker-dir": dst = libpod.DirTransport + ":" + output manifestType = manifest.DockerV2Schema2MediaType case libpod.DockerArchive: fallthrough case "": dst = libpod.DockerArchive + ":" + output default: return errors.Errorf("unknown format option %q", c.String("format")) } // supports saving multiple tags to the same tar archive var additionaltags []reference.NamedTagged if len(args) > 1 { additionaltags, err = libpodImage.GetAdditionalTags(args[1:]) if err != nil { return err } } newImage, err := runtime.ImageRuntime().NewFromLocal(args[0]) 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(), args[0]) { dest = dst + ":" + args[0] } if err := newImage.PushImage(getContext(), dest, 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) } return errors.Wrapf(err, "unable to save %q", args) } return nil }