diff options
Diffstat (limited to 'cmd/podman/images')
-rw-r--r-- | cmd/podman/images/build.go | 491 | ||||
-rw-r--r-- | cmd/podman/images/diff.go | 5 | ||||
-rw-r--r-- | cmd/podman/images/history.go | 21 | ||||
-rw-r--r-- | cmd/podman/images/image.go | 3 | ||||
-rw-r--r-- | cmd/podman/images/import.go | 23 | ||||
-rw-r--r-- | cmd/podman/images/load.go | 20 | ||||
-rw-r--r-- | cmd/podman/images/prune.go | 19 | ||||
-rw-r--r-- | cmd/podman/images/pull.go | 5 | ||||
-rw-r--r-- | cmd/podman/images/push.go | 4 | ||||
-rw-r--r-- | cmd/podman/images/rm.go | 7 | ||||
-rw-r--r-- | cmd/podman/images/save.go | 23 | ||||
-rw-r--r-- | cmd/podman/images/search.go | 8 | ||||
-rw-r--r-- | cmd/podman/images/tag.go | 16 | ||||
-rw-r--r-- | cmd/podman/images/trust.go | 27 | ||||
-rw-r--r-- | cmd/podman/images/trust_set.go | 56 | ||||
-rw-r--r-- | cmd/podman/images/trust_show.go | 77 | ||||
-rw-r--r-- | cmd/podman/images/untag.go | 16 |
17 files changed, 791 insertions, 30 deletions
diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go new file mode 100644 index 000000000..06a7efd25 --- /dev/null +++ b/cmd/podman/images/build.go @@ -0,0 +1,491 @@ +package images + +import ( + "os" + "path/filepath" + "strings" + + "github.com/containers/buildah" + "github.com/containers/buildah/imagebuildah" + buildahCLI "github.com/containers/buildah/pkg/cli" + "github.com/containers/buildah/pkg/parse" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// buildFlagsWrapper are local to cmd/ as the build code is using Buildah-internal +// types. Hence, after parsing, we are converting buildFlagsWrapper to the entities' +// options which essentially embed the Buildah types. +type buildFlagsWrapper struct { + // Buildah stuff first + buildahCLI.BudResults + buildahCLI.LayerResults + buildahCLI.FromAndBudResults + buildahCLI.NameSpaceResults + buildahCLI.UserNSResults + + // SquashAll squashes all layers into a single layer. + SquashAll bool +} + +var ( + // Command: podman _diff_ Object_ID + buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory." + buildCmd = &cobra.Command{ + Use: "build [flags] [CONTEXT]", + Short: "Build an image using instructions from Containerfiles", + Long: buildDescription, + TraverseChildren: true, + RunE: build, + Example: `podman build . + podman build --creds=username:password -t imageName -f Containerfile.simple . + podman build --layers --force-rm --tag imageName .`, + } + + imageBuildCmd = &cobra.Command{ + Args: buildCmd.Args, + Use: buildCmd.Use, + Short: buildCmd.Short, + Long: buildCmd.Long, + RunE: buildCmd.RunE, + Example: `podman image build . + podman image build --creds=username:password -t imageName -f Containerfile.simple . + podman image build --layers --force-rm --tag imageName .`, + } + + buildOpts = buildFlagsWrapper{} +) + +// useLayers returns false if BUILDAH_LAYERS is set to "0" or "false" +// otherwise it returns true +func useLayers() string { + layers := os.Getenv("BUILDAH_LAYERS") + if strings.ToLower(layers) == "false" || layers == "0" { + return "false" + } + return "true" +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: buildCmd, + }) + buildFlags(buildCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageBuildCmd, + Parent: imageCmd, + }) + buildFlags(imageBuildCmd.Flags()) +} + +func buildFlags(flags *pflag.FlagSet) { + // Podman flags + flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer") + + // Bud flags + budFlags := buildahCLI.GetBudFlags(&buildOpts.BudResults) + // --pull flag + flag := budFlags.Lookup("pull") + if err := flag.Value.Set("true"); err != nil { + logrus.Errorf("unable to set --pull to true: %v", err) + } + flag.DefValue = "true" + flags.AddFlagSet(&budFlags) + + // Layer flags + layerFlags := buildahCLI.GetLayerFlags(&buildOpts.LayerResults) + // --layers flag + flag = layerFlags.Lookup("layers") + useLayersVal := useLayers() + if err := flag.Value.Set(useLayersVal); err != nil { + logrus.Errorf("unable to set --layers to %v: %v", useLayersVal, err) + } + flag.DefValue = useLayersVal + // --force-rm flag + flag = layerFlags.Lookup("force-rm") + if err := flag.Value.Set("true"); err != nil { + logrus.Errorf("unable to set --force-rm to true: %v", err) + } + flag.DefValue = "true" + flags.AddFlagSet(&layerFlags) + + // FromAndBud flags + fromAndBudFlags, err := buildahCLI.GetFromAndBudFlags(&buildOpts.FromAndBudResults, &buildOpts.UserNSResults, &buildOpts.NameSpaceResults) + if err != nil { + logrus.Errorf("error setting up build flags: %v", err) + os.Exit(1) + } + flags.AddFlagSet(&fromAndBudFlags) +} + +// build executes the build command. +func build(cmd *cobra.Command, args []string) error { + if (cmd.Flags().Changed("squash") && cmd.Flags().Changed("layers")) || + (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("layers")) || + (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("squash")) { + return errors.New("cannot specify --squash, --squash-all and --layers options together") + } + + contextDir, containerFiles, err := extractContextAndFiles(args, buildOpts.File) + if err != nil { + return err + } + + ie, err := registry.NewImageEngine(cmd, args) + if err != nil { + return err + } + + apiBuildOpts, err := buildFlagsWrapperToOptions(cmd, contextDir, &buildOpts) + if err != nil { + return err + } + + _, err = ie.Build(registry.GetContext(), containerFiles, *apiBuildOpts) + return err +} + +// extractContextAndFiles parses args and files to extract a context directory +// and {Container,Docker}files. +// +// TODO: this was copied and altered from the v1 client which in turn was +// copied and altered from the Buildah code. Ideally, all of this code should +// be cleanly consolidated into a package that is shared between Buildah and +// Podman. +func extractContextAndFiles(args, files []string) (string, []string, error) { + // Extract container files from the CLI (i.e., --file/-f) first. + var containerFiles []string + for _, f := range files { + if f == "-" { + containerFiles = append(containerFiles, "/dev/stdin") + } else { + containerFiles = append(containerFiles, f) + } + } + + // Determine context directory. + var contextDir string + if len(args) > 0 { + // The context directory could be a URL. Try to handle that. + tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", args[0]) + if err != nil { + return "", nil, errors.Wrapf(err, "error prepping temporary context directory") + } + if tempDir != "" { + // We had to download it to a temporary directory. + // Delete it later. + defer func() { + if err = os.RemoveAll(tempDir); err != nil { + logrus.Errorf("error removing temporary directory %q: %v", contextDir, err) + } + }() + contextDir = filepath.Join(tempDir, subDir) + } else { + // Nope, it was local. Use it as is. + absDir, err := filepath.Abs(args[0]) + if err != nil { + return "", nil, errors.Wrapf(err, "error determining path to directory %q", args[0]) + } + contextDir = absDir + } + } else { + // No context directory or URL was specified. Try to use the home of + // the first locally-available Containerfile. + for i := range containerFiles { + if strings.HasPrefix(containerFiles[i], "http://") || + strings.HasPrefix(containerFiles[i], "https://") || + strings.HasPrefix(containerFiles[i], "git://") || + strings.HasPrefix(containerFiles[i], "github.com/") { + continue + } + absFile, err := filepath.Abs(containerFiles[i]) + if err != nil { + return "", nil, errors.Wrapf(err, "error determining path to file %q", containerFiles[i]) + } + contextDir = filepath.Dir(absFile) + break + } + } + + if contextDir == "" { + return "", nil, errors.Errorf("no context directory and no Containerfile specified") + } + if !utils.IsDir(contextDir) { + return "", nil, errors.Errorf("context must be a directory: %q", contextDir) + } + if len(containerFiles) == 0 { + if utils.FileExists(filepath.Join(contextDir, "Containerfile")) { + containerFiles = append(containerFiles, filepath.Join(contextDir, "Containerfile")) + } else { + containerFiles = append(containerFiles, filepath.Join(contextDir, "Dockerfile")) + } + } + + return contextDir, containerFiles, nil +} + +// buildFlagsWrapperToOptions converts the local build flags to the build options used +// in the API which embed Buildah types used across the build code. Doing the +// conversion here prevents the API from doing that (redundantly). +// +// TODO: this code should really be in Buildah. +func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buildFlagsWrapper) (*entities.BuildOptions, error) { + output := "" + tags := []string{} + if c.Flag("tag").Changed { + tags = flags.Tag + if len(tags) > 0 { + output = tags[0] + tags = tags[1:] + } + } + + pullPolicy := imagebuildah.PullNever + if flags.Pull { + pullPolicy = imagebuildah.PullIfMissing + } + if flags.PullAlways { + pullPolicy = imagebuildah.PullAlways + } + + args := make(map[string]string) + if c.Flag("build-arg").Changed { + for _, arg := range flags.BuildArg { + av := strings.SplitN(arg, "=", 2) + if len(av) > 1 { + args[av[0]] = av[1] + } else { + delete(args, av[0]) + } + } + } + // Check to see if the BUILDAH_LAYERS environment variable is set and + // override command-line. + if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok { + flags.Layers = true + } + + // `buildah bud --layers=false` acts like `docker build --squash` does. + // That is all of the new layers created during the build process are + // condensed into one, any layers present prior to this build are + // retained without condensing. `buildah bud --squash` squashes both + // new and old layers down into one. Translate Podman commands into + // Buildah. Squash invoked, retain old layers, squash new layers into + // one. + if c.Flags().Changed("squash") && buildOpts.Squash { + flags.Squash = false + flags.Layers = false + } + // Squash-all invoked, squash both new and old layers into one. + if c.Flags().Changed("squash-all") { + flags.Squash = true + flags.Layers = false + } + + var stdin, stdout, stderr, reporter *os.File + stdin = os.Stdin + stdout = os.Stdout + stderr = os.Stderr + reporter = os.Stderr + + if c.Flag("logfile").Changed { + f, err := os.OpenFile(flags.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if err != nil { + return nil, errors.Errorf("error opening logfile %q: %v", flags.Logfile, err) + } + defer f.Close() + logrus.SetOutput(f) + stdout = f + stderr = f + reporter = f + } + + var memoryLimit, memorySwap int64 + var err error + if c.Flags().Changed("memory") { + memoryLimit, err = units.RAMInBytes(flags.Memory) + if err != nil { + return nil, err + } + } + + if c.Flags().Changed("memory-swap") { + memorySwap, err = units.RAMInBytes(flags.MemorySwap) + if err != nil { + return nil, err + } + } + + nsValues, err := getNsValues(flags) + if err != nil { + return nil, err + } + + networkPolicy := buildah.NetworkDefault + for _, ns := range nsValues { + if ns.Name == "none" { + networkPolicy = buildah.NetworkDisabled + break + } else if !filepath.IsAbs(ns.Path) { + networkPolicy = buildah.NetworkEnabled + break + } + } + + // `buildah bud --layers=false` acts like `docker build --squash` does. + // That is all of the new layers created during the build process are + // condensed into one, any layers present prior to this build are retained + // without condensing. `buildah bud --squash` squashes both new and old + // layers down into one. Translate Podman commands into Buildah. + // Squash invoked, retain old layers, squash new layers into one. + if c.Flags().Changed("squash") && flags.Squash { + flags.Squash = false + flags.Layers = false + } + // Squash-all invoked, squash both new and old layers into one. + if c.Flags().Changed("squash-all") { + flags.Squash = true + flags.Layers = false + } + + compression := imagebuildah.Gzip + if flags.DisableCompression { + compression = imagebuildah.Uncompressed + } + + isolation, err := parse.IsolationOption(flags.Isolation) + if err != nil { + return nil, errors.Wrapf(err, "error parsing ID mapping options") + } + + usernsOption, idmappingOptions, err := parse.IDMappingOptions(c, isolation) + if err != nil { + return nil, errors.Wrapf(err, "error parsing ID mapping options") + } + nsValues = append(nsValues, usernsOption...) + + systemContext, err := parse.SystemContextFromOptions(c) + if err != nil { + return nil, errors.Wrapf(err, "error building system context") + } + + format := "" + flags.Format = strings.ToLower(flags.Format) + switch { + case strings.HasPrefix(flags.Format, buildah.OCI): + format = buildah.OCIv1ImageManifest + case strings.HasPrefix(flags.Format, buildah.DOCKER): + format = buildah.Dockerv2ImageManifest + default: + return nil, errors.Errorf("unrecognized image type %q", flags.Format) + } + + runtimeFlags := []string{} + for _, arg := range flags.RuntimeFlags { + runtimeFlags = append(runtimeFlags, "--"+arg) + } + + // FIXME: the code below needs to be enabled (and adjusted) once the + // global/root flags are supported. + + // conf, err := runtime.GetConfig() + // if err != nil { + // return err + // } + // if conf != nil && conf.Engine.CgroupManager == config.SystemdCgroupsManager { + // runtimeFlags = append(runtimeFlags, "--systemd-cgroup") + // } + + opts := imagebuildah.BuildOptions{ + AddCapabilities: flags.CapAdd, + AdditionalTags: tags, + Annotations: flags.Annotation, + Architecture: flags.Arch, + Args: args, + BlobDirectory: flags.BlobCache, + CNIConfigDir: flags.CNIConfigDir, + CNIPluginPath: flags.CNIPlugInPath, + CommonBuildOpts: &buildah.CommonBuildOptions{ + AddHost: flags.AddHost, + CgroupParent: flags.CgroupParent, + CPUPeriod: flags.CPUPeriod, + CPUQuota: flags.CPUQuota, + CPUShares: flags.CPUShares, + CPUSetCPUs: flags.CPUSetCPUs, + CPUSetMems: flags.CPUSetMems, + Memory: memoryLimit, + MemorySwap: memorySwap, + ShmSize: flags.ShmSize, + Ulimit: flags.Ulimit, + Volumes: flags.Volumes, + }, + Compression: compression, + ConfigureNetwork: networkPolicy, + ContextDirectory: contextDir, + // DefaultMountsFilePath: FIXME: this requires global flags to be working! + Devices: flags.Devices, + DropCapabilities: flags.CapDrop, + Err: stderr, + ForceRmIntermediateCtrs: flags.ForceRm, + IDMappingOptions: idmappingOptions, + IIDFile: flags.Iidfile, + In: stdin, + Isolation: isolation, + Labels: flags.Label, + Layers: flags.Layers, + NamespaceOptions: nsValues, + NoCache: flags.NoCache, + OS: flags.OS, + Out: stdout, + Output: output, + OutputFormat: format, + PullPolicy: pullPolicy, + Quiet: flags.Quiet, + RemoveIntermediateCtrs: flags.Rm, + ReportWriter: reporter, + RuntimeArgs: runtimeFlags, + SignBy: flags.SignBy, + SignaturePolicyPath: flags.SignaturePolicy, + Squash: flags.Squash, + SystemContext: systemContext, + Target: flags.Target, + TransientMounts: flags.Volumes, + } + + return &entities.BuildOptions{BuildOptions: opts}, nil +} + +func getNsValues(flags *buildFlagsWrapper) ([]buildah.NamespaceOption, error) { + var ret []buildah.NamespaceOption + if flags.Network != "" { + switch { + case flags.Network == "host": + ret = append(ret, buildah.NamespaceOption{ + Name: string(specs.NetworkNamespace), + Host: true, + }) + case flags.Network == "container": + ret = append(ret, buildah.NamespaceOption{ + Name: string(specs.NetworkNamespace), + }) + case flags.Network[0] == '/': + ret = append(ret, buildah.NamespaceOption{ + Name: string(specs.NetworkNamespace), + Path: flags.Network, + }) + default: + return nil, errors.Errorf("unsupported configuration network=%s", flags.Network) + } + } + return ret, nil +} diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index 7cfacfc6c..c24f98369 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -28,9 +29,11 @@ func init() { Command: diffCmd, Parent: imageCmd, }) + diffFlags(diffCmd.Flags()) +} +func diffFlags(flags *pflag.FlagSet) { diffOpts = &entities.DiffOptions{} - flags := diffCmd.Flags() flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.") flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index ce153aa46..17a80557e 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -15,6 +15,7 @@ import ( "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -32,6 +33,15 @@ var ( RunE: history, } + imageHistoryCmd = &cobra.Command{ + Args: historyCmd.Args, + Use: historyCmd.Use, + Short: historyCmd.Short, + Long: historyCmd.Long, + RunE: historyCmd.RunE, + Example: `podman image history imageID`, + } + opts = struct { human bool noTrunc bool @@ -45,8 +55,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: historyCmd, }) + historyFlags(historyCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageHistoryCmd, + Parent: imageCmd, + }) + historyFlags(imageHistoryCmd.Flags()) +} - flags := historyCmd.Flags() +func historyFlags(flags *pflag.FlagSet) { flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go index 604f49251..790c16c05 100644 --- a/cmd/podman/images/image.go +++ b/cmd/podman/images/image.go @@ -2,6 +2,7 @@ package images import ( "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -16,7 +17,7 @@ var ( Short: "Manage images", Long: "Manage images", TraverseChildren: true, - RunE: registry.SubCommandExists, + RunE: validate.SubCommandExists, } ) diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go index 1c0568762..0e16128ce 100644 --- a/cmd/podman/images/import.go +++ b/cmd/podman/images/import.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,6 +27,17 @@ var ( cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported cat ctr.tar | podman import -`, } + + imageImportCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: importCommand.Use, + Short: importCommand.Short, + Long: importCommand.Long, + RunE: importCommand.RunE, + Example: `podman image import http://example.com/ctr.tar url-image + cat ctr.tar | podman -q image import --message "importing the ctr.tar tarball" - image-imported + cat ctr.tar | podman image import -`, + } ) var ( @@ -37,8 +49,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: importCommand, }) + importFlags(importCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageImportCommand, + Parent: imageCmd, + }) + importFlags(imageImportCommand.Flags()) +} - flags := importCommand.Flags() +func importFlags(flags *pflag.FlagSet) { flags.StringArrayVarP(&importOpts.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") flags.StringVarP(&importOpts.Message, "message", "m", "", "Set commit message for imported image") flags.BoolVarP(&importOpts.Quiet, "quiet", "q", false, "Suppress output") diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index f49f95002..4bbffd432 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -15,6 +15,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -27,6 +28,14 @@ var ( RunE: load, Args: cobra.MaximumNArgs(1), } + + imageLoadCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: loadCommand.Use, + Short: loadCommand.Short, + Long: loadCommand.Long, + RunE: loadCommand.RunE, + } ) var ( @@ -38,8 +47,16 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: loadCommand, }) + loadFlags(loadCommand.Flags()) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageLoadCommand, + Parent: imageCmd, + }) + loadFlags(imageLoadCommand.Flags()) +} - flags := loadCommand.Flags() +func loadFlags(flags *pflag.FlagSet) { flags.StringVarP(&loadOpts.Input, "input", "i", "", "Read from specified archive file (default: stdin)") flags.BoolVarP(&loadOpts.Quiet, "quiet", "q", false, "Suppress the output") flags.StringVar(&loadOpts.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file") @@ -61,7 +78,6 @@ func load(cmd *cobra.Command, args []string) error { loadOpts.Tag = "latest" } if r, ok := ref.(reference.Named); ok { - fmt.Println(r.Name()) loadOpts.Name = r.Name() } } diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index eb9e4a7e4..7c9e3eb61 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -6,8 +6,9 @@ import ( "os" "strings" - "github.com/containers/libpod/cmd/podman/common" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -19,7 +20,7 @@ var ( If an image is not being used by a container, it will be removed from the system.` pruneCmd = &cobra.Command{ Use: "prune", - Args: common.NoArgs, + Args: validate.NoArgs, Short: "Remove unused images", Long: pruneDescription, RunE: prune, @@ -71,17 +72,5 @@ Are you sure you want to continue? [y/N] `) return err } - for _, i := range results.Report.Id { - fmt.Println(i) - } - - for _, e := range results.Report.Err { - fmt.Fprint(os.Stderr, e.Error()+"\n") - } - - if results.Size > 0 { - fmt.Fprintf(os.Stdout, "Size: %d\n", results.Size) - } - - return nil + return utils.PrintImagePruneResults(results) } diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index fead5f7ed..9e883703f 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" @@ -45,6 +45,7 @@ var ( Short: pullCmd.Short, Long: pullCmd.Long, RunE: pullCmd.RunE, + Args: cobra.ExactArgs(1), Example: `podman image pull imageName podman image pull fedora:latest`, } @@ -74,7 +75,7 @@ func init() { // pullFlags set the flags for the pull command. func pullFlags(flags *pflag.FlagSet) { flags.BoolVar(&pullOptions.AllTags, "all-tags", false, "All tagged images in the repository will be pulled") - flags.StringVar(&pullOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pullOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&pullOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") flags.StringVar(&pullOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.StringVar(&pullOptions.OverrideArch, "override-arch", "", "Use `ARCH` instead of the architecture of the machine for choosing images") diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 0b3502d61..dd536213f 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -3,7 +3,7 @@ package images import ( "os" - buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" @@ -70,7 +70,7 @@ func init() { // pushFlags set the flags for the push command. func pushFlags(flags *pflag.FlagSet) { - flags.StringVar(&pushOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pushOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&pushOptions.CertDir, "cert-dir", "", "Path to a directory containing TLS certificates and keys") flags.BoolVar(&pushOptions.Compress, "compress", false, "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)") flags.StringVar(&pushOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index 1cf5fa365..4b9920532 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -5,6 +5,7 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/errorhandling" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -48,7 +49,9 @@ func rm(cmd *cobra.Command, args []string) error { return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") } - report, err := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts) + // Note: certain image-removal errors are non-fatal. Hence, the report + // might be set even if err != nil. + report, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts) if report != nil { for _, u := range report.Untagged { fmt.Println("Untagged: " + u) @@ -62,5 +65,5 @@ func rm(cmd *cobra.Command, args []string) error { registry.SetExitCode(report.ExitCode) } - return err + return errorhandling.JoinErrors(rmErrors) } diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 8f7832074..56953e41c 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -43,6 +44,16 @@ var ( podman save --format docker-dir -o ubuntu-dir ubuntu podman save > alpine-all.tar alpine:latest`, } + imageSaveCommand = &cobra.Command{ + Args: saveCommand.Args, + Use: saveCommand.Use, + Short: saveCommand.Short, + Long: saveCommand.Long, + RunE: saveCommand.RunE, + Example: `podman image save --quiet -o myimage.tar imageID + podman image save --format docker-dir -o ubuntu-dir ubuntu + podman image save > alpine-all.tar alpine:latest`, + } ) var ( @@ -54,7 +65,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: saveCommand, }) - flags := saveCommand.Flags() + saveFlags(saveCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageSaveCommand, + Parent: imageCmd, + }) + saveFlags(imageSaveCommand.Flags()) +} + +func saveFlags(flags *pflag.FlagSet) { flags.BoolVar(&saveOpts.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") flags.StringVar(&saveOpts.Format, "format", define.V2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") flags.StringVarP(&saveOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index a8abfb339..640d497c3 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -5,8 +5,8 @@ import ( "reflect" "strings" - buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/formats" + "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" @@ -87,7 +87,7 @@ func searchFlags(flags *pflag.FlagSet) { flags.StringVar(&searchOptions.Format, "format", "", "Change the output format to a Go template") flags.IntVar(&searchOptions.Limit, "limit", 0, "Limit the number of results") flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") - flags.StringVar(&searchOptions.Authfile, "authfile", buildahcli.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&searchOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") if registry.IsRemote() { _ = flags.MarkHidden("authfile") @@ -105,6 +105,10 @@ func imageSearch(cmd *cobra.Command, args []string) error { return errors.Errorf("search requires exactly one argument") } + if searchOptions.Limit > 100 { + return errors.Errorf("Limit %d is outside the range of [1, 100]", searchOptions.Limit) + } + // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of diff --git a/cmd/podman/images/tag.go b/cmd/podman/images/tag.go index 411313a9b..dae3416c4 100644 --- a/cmd/podman/images/tag.go +++ b/cmd/podman/images/tag.go @@ -18,6 +18,17 @@ var ( podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageTagCommand = &cobra.Command{ + Args: tagCommand.Args, + Use: tagCommand.Use, + Short: tagCommand.Short, + Long: tagCommand.Long, + RunE: tagCommand.RunE, + Example: `podman image tag 0e3bbc2 fedora:latest + podman image tag imageID:latest myNewImage:newTag + podman image tag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -25,6 +36,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: tagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageTagCommand, + Parent: imageCmd, + }) } func tag(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/trust.go b/cmd/podman/images/trust.go new file mode 100644 index 000000000..88a567871 --- /dev/null +++ b/cmd/podman/images/trust.go @@ -0,0 +1,27 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + trustDescription = `Manages which registries you trust as a source of container images based on their location. + The location is determined by the transport and the registry host of the image. Using this container image docker://quay.io/podman/stable as an example, docker is the transport and quay.io is the registry host.` + trustCmd = &cobra.Command{ + Use: "trust", + Short: "Manage container image trust policy", + Long: trustDescription, + RunE: validate.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: trustCmd, + Parent: imageCmd, + }) +} diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go new file mode 100644 index 000000000..5868f5546 --- /dev/null +++ b/cmd/podman/images/trust_set.go @@ -0,0 +1,56 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + setTrustDescription = "Set default trust policy or add a new trust policy for a registry" + setTrustCommand = &cobra.Command{ + Use: "set [flags] REGISTRY", + Short: "Set default trust policy or a new trust policy for a registry", + Long: setTrustDescription, + Example: "", + RunE: setTrust, + Args: cobra.ExactArgs(1), + } +) + +var ( + setOptions entities.SetTrustOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: setTrustCommand, + Parent: trustCmd, + }) + setFlags := setTrustCommand.Flags() + setFlags.StringVar(&setOptions.PolicyPath, "policypath", "", "") + _ = setFlags.MarkHidden("policypath") + setFlags.StringSliceVarP(&setOptions.PubKeysFile, "pubkeysfile", "f", []string{}, `Path of installed public key(s) to trust for TARGET. +Absolute path to keys is added to policy.json. May +used multiple times to define multiple public keys. +File(s) must exist before using this command`) + setFlags.StringVarP(&setOptions.Type, "type", "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") +} + +func setTrust(cmd *cobra.Command, args []string) error { + validTrustTypes := []string{"accept", "insecureAcceptAnything", "reject", "signedBy"} + + valid, err := image.IsValidImageURI(args[0]) + if err != nil || !valid { + return errors.Wrapf(err, "invalid image uri %s", args[0]) + } + + if !util.StringInSlice(setOptions.Type, validTrustTypes) { + return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type) + } + return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions) +} diff --git a/cmd/podman/images/trust_show.go b/cmd/podman/images/trust_show.go new file mode 100644 index 000000000..23ee6c709 --- /dev/null +++ b/cmd/podman/images/trust_show.go @@ -0,0 +1,77 @@ +package images + +import ( + "fmt" + "os" + "text/tabwriter" + "text/template" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + showTrustDescription = "Display trust policy for the system" + showTrustCommand = &cobra.Command{ + Use: "show [flags] [REGISTRY]", + Short: "Display trust policy for the system", + Long: showTrustDescription, + RunE: showTrust, + Example: "", + } +) + +var ( + showTrustOptions entities.ShowTrustOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: showTrustCommand, + Parent: trustCmd, + }) + showFlags := showTrustCommand.Flags() + showFlags.BoolVarP(&showTrustOptions.JSON, "json", "j", false, "Output as json") + showFlags.StringVar(&showTrustOptions.PolicyPath, "policypath", "", "") + showFlags.BoolVar(&showTrustOptions.Raw, "raw", false, "Output raw policy file") + _ = showFlags.MarkHidden("policypath") + showFlags.StringVar(&showTrustOptions.RegistryPath, "registrypath", "", "") + _ = showFlags.MarkHidden("registrypath") + +} + +func showTrust(cmd *cobra.Command, args []string) error { + report, err := registry.ImageEngine().ShowTrust(registry.Context(), args, showTrustOptions) + if err != nil { + return err + } + if showTrustOptions.Raw { + fmt.Println(report.Raw) + return nil + } + if showTrustOptions.JSON { + b, err := json.MarshalIndent(report.Policies, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + } + + row := "{{.RepoName}}\t{{.Type}}\t{{.GPGId}}\t{{.SignatureStore}}\n" + format := "{{range . }}" + row + "{{end}}" + tmpl, err := template.New("listContainers").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + if err := tmpl.Execute(w, report.Policies); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + return nil +} diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go index 3218844b7..266a3f115 100644 --- a/cmd/podman/images/untag.go +++ b/cmd/podman/images/untag.go @@ -17,6 +17,17 @@ var ( podman untag imageID:latest otherImageName:latest podman untag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageUntagCommand = &cobra.Command{ + Args: untagCommand.Args, + Use: untagCommand.Use, + Short: untagCommand.Short, + Long: untagCommand.Long, + RunE: untagCommand.RunE, + Example: `podman image untag 0e3bbc2 + podman image untag imageID:latest otherImageName:latest + podman image untag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -24,6 +35,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: untagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageUntagCommand, + Parent: imageCmd, + }) } func untag(cmd *cobra.Command, args []string) error { |