diff options
157 files changed, 11498 insertions, 4723 deletions
@@ -289,7 +289,7 @@ uninstall: install.tools: .install.gitvalidation .install.gometalinter .install.md2man .install.ginkgo ## Install needed tools .install.vndr: .gopathok - $(GO) get github.com/LK4D4/vndr + $(GO) get -u github.com/LK4D4/vndr .install.ginkgo: .gopathok if [ ! -x "$(GOBIN)/ginkgo" ]; then \ @@ -323,7 +323,7 @@ install.tools: .install.gitvalidation .install.gometalinter .install.md2man .ins make all install; \ fi -varlink_generate: .gopathok cmd/podman/varlink/iopodman.go +varlink_generate: .gopathok cmd/podman/varlink/iopodman.go ## Generate varlink varlink_api_generate: .gopathok API.md .PHONY: install.libseccomp.sudo @@ -348,10 +348,12 @@ build-all-new-commits: # Validate that all the commits build on top of $(GIT_BASE_BRANCH) git rebase $(GIT_BASE_BRANCH) -x make -vendor: - vndr -whitelist "github.com/varlink/go" \ - -whitelist "github.com/onsi/ginkgo" \ - -whitelist "github.com/onsi/gomega" +vendor: .install.vndr + $(GOPATH)/bin/vndr \ + -whitelist "github.com/varlink/go" \ + -whitelist "github.com/onsi/ginkgo" \ + -whitelist "github.com/onsi/gomega" \ + -whitelist "gopkg.in/fsnotify.v1" .PHONY: \ .gopathok \ diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go index bcfee6891..8b0fd7ffd 100644 --- a/cmd/podman/attach.go +++ b/cmd/podman/attach.go @@ -3,57 +3,56 @@ package main import ( "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - attachFlags = []cli.Flag{ - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.", - }, - cli.BoolFlag{ - Name: "no-stdin", - Usage: "Do not attach STDIN. The default is false.", - }, - cli.BoolTFlag{ - Name: "sig-proxy", - Usage: "Proxy received signals to the process (default true)", - }, - LatestFlag, - } + attachCommand cliconfig.AttachValues attachDescription = "The podman attach command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively." - attachCommand = cli.Command{ - Name: "attach", - Usage: "Attach to a running container", - Description: attachDescription, - Flags: sortFlags(attachFlags), - Action: attachCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + _attachCommand = &cobra.Command{ + Use: "attach", + Short: "Attach to a running container", + Long: attachDescription, + RunE: func(cmd *cobra.Command, args []string) error { + attachCommand.InputArgs = args + attachCommand.GlobalFlags = MainGlobalOpts + return attachCmd(&attachCommand) + }, + Example: "", } ) -func attachCmd(c *cli.Context) error { - args := c.Args() +func init() { + attachCommand.Command = _attachCommand + flags := attachCommand.Flags() + flags.StringVar(&attachCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") + flags.BoolVar(&attachCommand.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") + flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true)") + + flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + rootCmd.AddCommand(attachCommand.Command) +} + +func attachCmd(c *cliconfig.AttachValues) error { + args := c.InputArgs var ctr *libpod.Container - if err := validateFlags(c, attachFlags); err != nil { - return err - } - if len(c.Args()) > 1 || (len(c.Args()) == 0 && !c.Bool("latest")) { + + if len(c.InputArgs) > 1 || (len(c.InputArgs) == 0 && !c.Latest) { return errors.Errorf("attach requires the name or id of one running container or the latest flag") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { ctr, err = runtime.GetLatestContainer() } else { ctr, err = runtime.LookupContainer(args[0]) @@ -72,11 +71,11 @@ func attachCmd(c *cli.Context) error { } inputStream := os.Stdin - if c.Bool("no-stdin") { + if c.NoStdin { inputStream = nil } - if err := startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), c.BoolT("sig-proxy"), false); err != nil { + if err := startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false); err != nil { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } diff --git a/cmd/podman/build.go b/cmd/podman/build.go index 0cd1bfbe3..20f621e84 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -10,38 +10,58 @@ import ( "github.com/containers/buildah/imagebuildah" buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - layerFlags = []cli.Flag{ - cli.BoolTFlag{ - Name: "force-rm", - Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful. (default true)", - }, - cli.BoolTFlag{ - Name: "layers", - Usage: "Cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. ", - }, - } + buildCommand cliconfig.BuildValues buildDescription = "Builds an OCI or Docker image using instructions from one\n" + "or more Dockerfiles and a specified build context directory." - buildCommand = cli.Command{ - Name: "build", - Usage: "Build an image using instructions from Dockerfiles", - Description: buildDescription, - Flags: sortFlags(append(append(buildahcli.BudFlags, layerFlags...), buildahcli.FromAndBudFlags...)), - Action: buildCmd, - ArgsUsage: "CONTEXT-DIRECTORY | URL", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + layerValues buildahcli.LayerResults + budFlagsValues buildahcli.BudResults + fromAndBudValues buildahcli.FromAndBudResults + userNSValues buildahcli.UserNSResults + namespaceValues buildahcli.NameSpaceResults + + _buildCommand = &cobra.Command{ + Use: "build", + Short: "Build an image using instructions from Dockerfiles", + Long: buildDescription, + RunE: func(cmd *cobra.Command, args []string) error { + buildCommand.InputArgs = args + buildCommand.GlobalFlags = MainGlobalOpts + buildCommand.BudResults = &budFlagsValues + buildCommand.UserNSResults = &userNSValues + buildCommand.FromAndBudResults = &fromAndBudValues + buildCommand.LayerResults = &layerValues + buildCommand.NameSpaceResults = &namespaceValues + return buildCmd(&buildCommand) + }, + Example: "CONTEXT-DIRECTORY | URL", } ) +func init() { + buildCommand.Command = _buildCommand + flags := buildCommand.Flags() + flags.SetInterspersed(false) + + flags.BoolVar(&layerValues.ForceRm, "force-rm", true, "Always remove intermediate containers after a build, even if the build is unsuccessful. (default true)") + flags.BoolVar(&layerValues.Layers, "layers", true, "Cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override") + budFlags := buildahcli.GetBudFlags(&budFlagsValues) + fromAndBugFlags := buildahcli.GetFromAndBudFlags(&fromAndBudValues, &userNSValues, &namespaceValues) + + flags.AddFlagSet(&budFlags) + flags.AddFlagSet(&fromAndBugFlags) + + rootCmd.AddCommand(buildCommand.Command) +} + func getDockerfiles(files []string) []string { var dockerfiles []string for _, f := range files { @@ -54,30 +74,30 @@ func getDockerfiles(files []string) []string { return dockerfiles } -func buildCmd(c *cli.Context) error { +func buildCmd(c *cliconfig.BuildValues) error { // The following was taken directly from containers/buildah/cmd/bud.go // TODO Find a away to vendor more of this in rather than copy from bud output := "" tags := []string{} - if c.IsSet("tag") || c.IsSet("t") { - tags = c.StringSlice("tag") + if c.Flag("tag").Changed { + tags = c.Tag if len(tags) > 0 { output = tags[0] tags = tags[1:] } } pullPolicy := imagebuildah.PullNever - if c.BoolT("pull") { + if c.Pull { pullPolicy = imagebuildah.PullIfMissing } - if c.Bool("pull-always") { + if c.PullAlways { pullPolicy = imagebuildah.PullAlways } args := make(map[string]string) - if c.IsSet("build-arg") { - for _, arg := range c.StringSlice("build-arg") { + if c.Flag("build-arg").Changed { + for _, arg := range c.BuildArg { av := strings.SplitN(arg, "=", 2) if len(av) > 1 { args[av[0]] = av[1] @@ -87,15 +107,15 @@ func buildCmd(c *cli.Context) error { } } - dockerfiles := getDockerfiles(c.StringSlice("file")) - format, err := getFormat(c) + dockerfiles := getDockerfiles(c.File) + format, err := getFormat(&c.PodmanCommand) if err != nil { return nil } contextDir := "" - cliArgs := c.Args() + cliArgs := c.InputArgs - layers := c.BoolT("layers") // layers for podman defaults to true + layers := c.Layers // layers for podman defaults to true // Check to see if the BUILDAH_LAYERS environment variable is set and override command-line if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok { layers = buildahcli.UseLayers() @@ -124,7 +144,7 @@ func buildCmd(c *cli.Context) error { } contextDir = absDir } - cliArgs = cliArgs.Tail() + cliArgs = Tail(cliArgs) } else { // No context directory or URL was specified. Try to use the // home of the first locally-available Dockerfile. @@ -153,17 +173,14 @@ func buildCmd(c *cli.Context) error { if len(dockerfiles) == 0 { dockerfiles = append(dockerfiles, filepath.Join(contextDir, "Dockerfile")) } - if err := parse.ValidateFlags(c, buildahcli.BudFlags); err != nil { - return err - } runtimeFlags := []string{} - for _, arg := range c.StringSlice("runtime-flag") { + for _, arg := range c.RuntimeOpts { runtimeFlags = append(runtimeFlags, "--"+arg) } // end from buildah - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -173,10 +190,10 @@ func buildCmd(c *cli.Context) error { stdout = os.Stdout stderr = os.Stderr reporter = os.Stderr - if c.IsSet("logfile") { - f, err := os.OpenFile(c.String("logfile"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + if c.Flag("logfile").Changed { + f, err := os.OpenFile(c.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { - return errors.Errorf("error opening logfile %q: %v", c.String("logfile"), err) + return errors.Errorf("error opening logfile %q: %v", c.Logfile, err) } defer f.Close() logrus.SetOutput(f) @@ -185,36 +202,36 @@ func buildCmd(c *cli.Context) error { reporter = f } - systemContext, err := parse.SystemContextFromOptions(c) + systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command) if err != nil { return errors.Wrapf(err, "error building system context") } - systemContext.AuthFilePath = getAuthFile(c.String("authfile")) - commonOpts, err := parse.CommonBuildOptions(c) + systemContext.AuthFilePath = getAuthFile(c.Authfile) + commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command) if err != nil { return err } - namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c) + namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command) if err != nil { return errors.Wrapf(err, "error parsing namespace-related options") } - usernsOption, idmappingOptions, err := parse.IDMappingOptions(c) + usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command) if err != nil { return errors.Wrapf(err, "error parsing ID mapping options") } namespaceOptions.AddOrReplace(usernsOption...) ociruntime := runtime.GetOCIRuntimePath() - if c.IsSet("runtime") { - ociruntime = c.String("runtime") + if c.Flag("runtime").Changed { + ociruntime = c.Runtime } options := imagebuildah.BuildOptions{ ContextDirectory: contextDir, PullPolicy: pullPolicy, Compression: imagebuildah.Gzip, - Quiet: c.Bool("quiet"), - SignaturePolicyPath: c.String("signature-policy"), + Quiet: c.Quiet, + SignaturePolicyPath: c.SignaturePolicy, Args: args, Output: output, AdditionalTags: tags, @@ -227,22 +244,22 @@ func buildCmd(c *cli.Context) error { SystemContext: systemContext, NamespaceOptions: namespaceOptions, ConfigureNetwork: networkPolicy, - CNIPluginPath: c.String("cni-plugin-path"), - CNIConfigDir: c.String("cni-config-dir"), + CNIPluginPath: c.CNIPlugInPath, + CNIConfigDir: c.CNIConfigDir, IDMappingOptions: idmappingOptions, CommonBuildOpts: commonOpts, - DefaultMountsFilePath: c.GlobalString("default-mounts-file"), - IIDFile: c.String("iidfile"), - Squash: c.Bool("squash"), - Labels: c.StringSlice("label"), - Annotations: c.StringSlice("annotation"), + DefaultMountsFilePath: c.GlobalFlags.DefaultMountsFile, + IIDFile: c.Iidfile, + Squash: c.Squash, + Labels: c.Label, + Annotations: c.Annotation, Layers: layers, - NoCache: c.Bool("no-cache"), - RemoveIntermediateCtrs: c.BoolT("rm"), - ForceRmIntermediateCtrs: c.BoolT("force-rm"), + NoCache: c.NoCache, + RemoveIntermediateCtrs: c.Rm, + ForceRmIntermediateCtrs: c.ForceRm, } - if c.Bool("quiet") { + if c.Quiet { options.ReportWriter = ioutil.Discard } @@ -252,3 +269,13 @@ func buildCmd(c *cli.Context) error { return runtime.Build(getContext(), options, dockerfiles...) } + +// Tail returns a string slice after the first element unless there are +// not enough elements, then it returns an empty slice. This is to replace +// the urfavecli Tail method for args +func Tail(a []string) []string { + if len(a) >= 2 { + return []string(a)[1:] + } + return []string{} +} diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index 5c3363147..2a978bea8 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -5,70 +5,67 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + checkpointCommand cliconfig.CheckpointValues checkpointDescription = ` podman container checkpoint Checkpoints one or more running containers. The container name or ID can be used. ` - checkpointFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "keep, k", - Usage: "Keep all temporary checkpoint files", + _checkpointCommand = &cobra.Command{ + Use: "checkpoint", + Short: "Checkpoints one or more containers", + Long: checkpointDescription, + RunE: func(cmd *cobra.Command, args []string) error { + checkpointCommand.InputArgs = args + checkpointCommand.GlobalFlags = MainGlobalOpts + return checkpointCmd(&checkpointCommand) }, - cli.BoolFlag{ - Name: "leave-running, R", - Usage: "Leave the container running after writing checkpoint to disk", - }, - cli.BoolFlag{ - Name: "tcp-established", - Usage: "Checkpoint a container with established TCP connections", - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Checkpoint all running containers", - }, - LatestFlag, - } - checkpointCommand = cli.Command{ - Name: "checkpoint", - Usage: "Checkpoints one or more containers", - Description: checkpointDescription, - Flags: sortFlags(checkpointFlags), - Action: checkpointCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func checkpointCmd(c *cli.Context) error { +func init() { + checkpointCommand.Command = _checkpointCommand + + flags := checkpointCommand.Flags() + flags.BoolVarP(&checkpointCommand.Keep, "keep", "k", false, "Keep all temporary checkpoint files") + flags.BoolVarP(&checkpointCommand.LeaveRunning, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk") + flags.BoolVar(&checkpointCommand.TcpEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") + flags.BoolVarP(&checkpointCommand.All, "all", "a", false, "Checkpoint all running containers") + flags.BoolVarP(&checkpointCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") +} + +func checkpointCmd(c *cliconfig.CheckpointValues) error { if rootless.IsRootless() { return errors.New("checkpointing a container requires root") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) options := libpod.ContainerCheckpointOptions{ - Keep: c.Bool("keep"), - KeepRunning: c.Bool("leave-running"), - TCPEstablished: c.Bool("tcp-established"), + Keep: c.Keep, + KeepRunning: c.LeaveRunning, + TCPEstablished: c.TcpEstablished, } - if err := checkAllAndLatest(c); err != nil { + if err := checkAllAndLatest(&c.PodmanCommand); err != nil { return err } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, lastError := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") for _, ctr := range containers { if err = ctr.Checkpoint(context.TODO(), options); err != nil { diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go index bc4af9f50..1a7617d5c 100644 --- a/cmd/podman/cleanup.go +++ b/cmd/podman/cleanup.go @@ -4,50 +4,52 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - cleanupFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Cleans up all containers", - }, - LatestFlag, - } + cleanupCommand cliconfig.CleanupValues cleanupDescription = ` podman container cleanup Cleans up mount points and network stacks on one or more containers from the host. The container name or ID can be used. This command is used internally when running containers, but can also be used if container cleanup has failed when a container exits. ` - cleanupCommand = cli.Command{ - Name: "cleanup", - Usage: "Cleanup network and mountpoints of one or more containers", - Description: cleanupDescription, - Flags: sortFlags(cleanupFlags), - Action: cleanupCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _cleanupCommand = &cobra.Command{ + Use: "cleanup", + Short: "Cleanup network and mountpoints of one or more containers", + Long: cleanupDescription, + RunE: func(cmd *cobra.Command, args []string) error { + cleanupCommand.InputArgs = args + cleanupCommand.GlobalFlags = MainGlobalOpts + return cleanupCmd(&cleanupCommand) + }, + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func cleanupCmd(c *cli.Context) error { - if err := validateFlags(c, cleanupFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) +func init() { + cleanupCommand.Command = _cleanupCommand + flags := cleanupCommand.Flags() + + flags.BoolVarP(&cleanupCommand.All, "all", "a", false, "Cleans up all containers") + flags.BoolVarP(&cleanupCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") +} + +func cleanupCmd(c *cliconfig.CleanupValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if err := checkAllAndLatest(c); err != nil { + if err := checkAllAndLatest(&c.PodmanCommand); err != nil { return err } - cleanupContainers, lastError := getAllOrLatestContainers(c, runtime, -1, "all") + cleanupContainers, lastError := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") ctx := getContext() diff --git a/cmd/podman/cliconfig/commands.go b/cmd/podman/cliconfig/commands.go new file mode 100644 index 000000000..7d1e762d9 --- /dev/null +++ b/cmd/podman/cliconfig/commands.go @@ -0,0 +1,109 @@ +package cliconfig + +// GlobalIsSet is a compatibility method for urfave +func (p *PodmanCommand) GlobalIsSet(opt string) bool { + flag := p.PersistentFlags().Lookup(opt) + if flag == nil { + return false + } + return flag.Changed +} + +// IsSet is a compatibility method for urfave +func (p *PodmanCommand) IsSet(opt string) bool { + flag := p.Flags().Lookup(opt) + if flag == nil { + return false + } + return flag.Changed +} + +// Bool is a compatibility method for urfave +func (p *PodmanCommand) Bool(opt string) bool { + flag := p.Flags().Lookup(opt) + if flag == nil { + return false + } + val, _ := p.Flags().GetBool(opt) + return val +} + +// String is a compatibility method for urfave +func (p *PodmanCommand) String(opt string) string { + flag := p.Flags().Lookup(opt) + if flag == nil { + return "" + } + val, _ := p.Flags().GetString(opt) + return val +} + +// StringArray is a compatibility method for urfave +func (p *PodmanCommand) StringArray(opt string) []string { + flag := p.Flags().Lookup(opt) + if flag == nil { + return []string{} + } + val, _ := p.Flags().GetStringArray(opt) + return val +} + +// StringSlice is a compatibility method for urfave +func (p *PodmanCommand) StringSlice(opt string) []string { + flag := p.Flags().Lookup(opt) + if flag == nil { + return []string{} + } + val, _ := p.Flags().GetStringSlice(opt) + return val +} + +// Int is a compatibility method for urfave +func (p *PodmanCommand) Int(opt string) int { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetInt(opt) + return val +} + +// Unt is a compatibility method for urfave +func (p *PodmanCommand) Uint(opt string) uint { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetUint(opt) + return val +} + +// Int64 is a compatibility method for urfave +func (p *PodmanCommand) Int64(opt string) int64 { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetInt64(opt) + return val +} + +// Unt64 is a compatibility method for urfave +func (p *PodmanCommand) Uint64(opt string) uint64 { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetUint64(opt) + return val +} + +// Float64 is a compatibility method for urfave +func (p *PodmanCommand) Float64(opt string) float64 { + flag := p.Flags().Lookup(opt) + if flag == nil { + return 0 + } + val, _ := p.Flags().GetFloat64(opt) + return val +} diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go new file mode 100644 index 000000000..b925d29ff --- /dev/null +++ b/cmd/podman/cliconfig/config.go @@ -0,0 +1,541 @@ +package cliconfig + +import ( + "github.com/spf13/cobra" +) + +type PodmanCommand struct { + *cobra.Command + InputArgs []string + GlobalFlags MainFlags +} + +type MainFlags struct { + CGroupManager string + CniConfigDir string + ConmonPath string + DefaultMountsFile string + HooksDir []string + MaxWorks int + Namespace string + Root string + Runroot string + Runtime string + StorageDriver string + StorageOpts []string + Syslog bool + + Config string + CpuProfile string + LogLevel string + TmpDir string +} + +type AttachValues struct { + PodmanCommand + DetachKeys string + Latest bool + NoStdin bool + SigProxy bool +} + +type ImagesValues struct { + PodmanCommand + All bool + Digests bool + Filter []string + Format string + Noheading bool + NoTrunc bool + Quiet bool + Sort string +} + +type TagValues struct { + PodmanCommand +} + +type WaitValues struct { + PodmanCommand + Interval uint + Latest bool +} + +type CheckpointValues struct { + PodmanCommand + Keep bool + LeaveRunning bool + TcpEstablished bool + All bool + Latest bool +} + +type CommitValues struct { + PodmanCommand + Change []string + Format string + Message string + Author string + Pause bool + Quiet bool +} + +type ContainersPrune struct { + PodmanCommand +} + +type DiffValues struct { + PodmanCommand + Archive bool + Format string +} + +type ExecValues struct { + PodmanCommand + Env []string + Privileged bool + Interfactive bool + Tty bool + User string + Latest bool + Workdir string +} + +type ImageExistsValues struct { + PodmanCommand +} + +type ContainerExistsValues struct { + PodmanCommand +} + +type PodExistsValues struct { + PodmanCommand +} + +type ExportValues struct { + PodmanCommand + Output string +} + +type GenerateKubeValues struct { + PodmanCommand + Service bool +} + +type HistoryValues struct { + PodmanCommand + Human bool + NoTrunc bool + Quiet bool + Format string +} +type PruneImagesValues struct { + PodmanCommand + All bool +} + +type ImportValues struct { + PodmanCommand + Change []string + Message string + Quiet bool +} + +type InfoValues struct { + PodmanCommand + Debug bool + Format string +} + +type InspectValues struct { + PodmanCommand + TypeObject string + Format string + Size bool + Latest bool +} + +type KillValues struct { + PodmanCommand + All bool + Signal string + Latest bool +} + +type LoadValues struct { + PodmanCommand + Input string + Quiet bool + SignaturePolicy string +} + +type LoginValues struct { + PodmanCommand + Password string + Username string + Authfile string + CertDir string + GetLogin bool + TlsVerify bool +} + +type LogoutValues struct { + PodmanCommand + Authfile string + All bool +} + +type LogsValues struct { + PodmanCommand + Details bool + Follow bool + Since string + Tail uint64 + Timestamps bool + Latest bool +} + +type MountValues struct { + PodmanCommand + All bool + Format string + NoTrunc bool + Latest bool +} + +type PauseValues struct { + PodmanCommand + All bool +} + +type KubePlayValues struct { + PodmanCommand + Authfile string + CertDir string + Creds string + Quiet bool + SignaturePolicy string + TlsVerify bool +} + +type PodCreateValues struct { + PodmanCommand + CgroupParent string + Infra bool + InfraImage string + InfraCommand string + LabelFile []string + Labels []string + Name string + PodIDFile string + Publish []string + Share string +} + +type PodInspectValues struct { + PodmanCommand + Latest bool +} + +type PodKillValues struct { + PodmanCommand + All bool + Signal string + Latest bool +} + +type PodPauseValues struct { + PodmanCommand + All bool + Latest bool +} + +type PodPsValues struct { + PodmanCommand + CtrNames bool + CtrIDs bool + CtrStatus bool + Filter string + Format string + Latest bool + Namespace bool + NoTrunc bool + Quiet bool + Sort string +} + +type PodRestartValues struct { + PodmanCommand + All bool + Latest bool +} + +type PodRmValues struct { + PodmanCommand + All bool + Force bool + Latest bool +} + +type PodStartValues struct { + PodmanCommand + All bool + Latest bool +} +type PodStatsValues struct { + PodmanCommand + All bool + NoStream bool + NoReset bool + Format string + Latest bool +} + +type PodStopValues struct { + PodmanCommand + All bool + Latest bool + Timeout uint +} + +type PodTopValues struct { + PodmanCommand + Latest bool + ListDescriptors bool +} +type PodUnpauseValues struct { + PodmanCommand + All bool + Latest bool +} + +type PortValues struct { + PodmanCommand + All bool + Latest bool +} + +type PsValues struct { + PodmanCommand + All bool + Filter []string + Format string + Last int + Latest bool + Namespace bool + NoTrunct bool + Pod bool + Quiet bool + Size bool + Sort string + Sync bool +} + +type PullValues struct { + PodmanCommand + Authfile string + CertDir string + Creds string + Quiet bool + SignaturePolicy string + TlsVerify bool +} + +type PushValues struct { + PodmanCommand + Authfile string + CertDir string + Compress bool + Creds string + Format string + Quiet bool + RemoveSignatures bool + SignBy string + SignaturePolicy string + TlsVerify bool +} + +type RefreshValues struct { + PodmanCommand +} + +type RestartValues struct { + PodmanCommand + All bool + Latest bool + Running bool + Timeout uint +} + +type RestoreValues struct { + PodmanCommand + All bool + Keep bool + Latest bool + TcpEstablished bool +} + +type RmValues struct { + PodmanCommand + All bool + Force bool + Latest bool + Volumes bool +} + +type RmiValues struct { + PodmanCommand + All bool + Force bool +} + +type RunlabelValues struct { + PodmanCommand + Authfile string + Display bool + CertDir string + Creds string + Name string + Opt1 string + Opt2 string + Opt3 string + Quiet bool + Pull bool + SignaturePolicy string + TlsVerify bool +} +type SaveValues struct { + PodmanCommand + Compress bool + Format string + Output string + Quiet bool +} + +type SearchValues struct { + PodmanCommand + Authfile string + Filter []string + Format string + Limit int + NoTrunc bool + TlsVerify bool +} + +type SignValues struct { + PodmanCommand + Directory string + SignBy string +} + +type StartValues struct { + PodmanCommand + Attach bool + DetachKeys string + Interactive bool + Latest bool + SigProxy bool +} + +type StatsValues struct { + PodmanCommand + All bool + Format string + Latest bool + NoReset bool + NoStream bool +} + +type StopValues struct { + PodmanCommand + All bool + Latest bool + Timeout uint +} + +type TopValues struct { + PodmanCommand + Latest bool + ListDescriptors bool +} + +type UmountValues struct { + PodmanCommand + All bool + Force bool + Latest bool +} + +type UnpauseValues struct { + PodmanCommand + All bool +} + +type VarlinkValues struct { + PodmanCommand + Timeout int64 +} + +type SetTrustValues struct { + PodmanCommand + PolicyPath string + PubKeysFile []string + TrustType string +} + +type ShowTrustValues struct { + PodmanCommand + Json bool + PolicyPath string + Raw bool + RegistryPath string +} + +type VersionValues struct { + PodmanCommand + Format string +} + +type VolumeCreateValues struct { + PodmanCommand + Driver string + Label []string + Opt []string +} +type VolumeInspectValues struct { + PodmanCommand + All bool + Format string +} + +type VolumeLsValues struct { + PodmanCommand + Filter string + Format string + Quiet bool +} + +type VolumePruneValues struct { + PodmanCommand + Force bool +} + +type VolumeRmValues struct { + PodmanCommand + All bool + Force bool +} + +type CleanupValues struct { + PodmanCommand + All bool + Latest bool +} + +type SystemPruneValues struct { + PodmanCommand + All bool + Force bool + Volume bool +} diff --git a/cmd/podman/cliconfig/create.go b/cmd/podman/cliconfig/create.go new file mode 100644 index 000000000..68ba4d857 --- /dev/null +++ b/cmd/podman/cliconfig/create.go @@ -0,0 +1,22 @@ +package cliconfig + +import ( + buildahcli "github.com/containers/buildah/pkg/cli" +) + +type CreateValues struct { + PodmanCommand +} + +type RunValues struct { + PodmanCommand +} + +type BuildValues struct { + PodmanCommand + *buildahcli.BudResults + *buildahcli.UserNSResults + *buildahcli.FromAndBudResults + *buildahcli.NameSpaceResults + *buildahcli.LayerResults +} diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 57126da4a..e1eba1f31 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -2,151 +2,106 @@ package main -import "github.com/urfave/cli" +import ( + "github.com/spf13/cobra" +) -func getAppCommands() []cli.Command { - return []cli.Command{ - attachCommand, - commitCommand, - buildCommand, - createCommand, - diffCommand, - execCommand, - killCommand, - kubeCommand, - loadCommand, - loginCommand, - logoutCommand, - logsCommand, - mountCommand, - pauseCommand, - psCommand, - podCommand, - portCommand, - pushCommand, - playCommand, - restartCommand, - rmCommand, - runCommand, - saveCommand, - searchCommand, - startCommand, - statsCommand, - stopCommand, - topCommand, - umountCommand, - unpauseCommand, - volumeCommand, - waitCommand, +func getImageSubCommands() []*cobra.Command { + return []*cobra.Command{ + _buildCommand, + _importCommand, + _loadCommand, + _pullCommand, + _rmiCommand, + _saveCommand, + _signCommand, } } -func getImageSubCommands() []cli.Command { - return []cli.Command{ - buildCommand, - importCommand, - loadCommand, - pullCommand, - saveCommand, - trustCommand, - signCommand, +func getContainerSubCommands() []*cobra.Command { + return []*cobra.Command{ + _attachCommand, + _checkpointCommand, + _cleanupCommand, + _containerExistsCommand, + _commitCommand, + _createCommand, + _diffCommand, + _execCommand, + _exportCommand, + _killCommand, + _logsCommand, + _psCommand, + _mountCommand, + _pauseCommand, + _portCommand, + _pruneContainersCommand, + _refreshCommand, + _restartCommand, + _restoreCommand, + _rmCommand, + _runCommmand, + _runlabelCommand, + _startCommand, + _statsCommand, + _stopCommand, + _topCommand, + _umountCommand, + _unpauseCommand, + _waitCommand, } } -func getSystemSubCommands() []cli.Command { - return []cli.Command{infoCommand} +func getPodSubCommands() []*cobra.Command { + return []*cobra.Command{ + _podCreateCommand, + _podExistsCommand, + _podInspectCommand, + _podKillCommand, + _podPauseCommand, + _podPsCommand, + _podRestartCommand, + _podRmCommand, + _podStartCommand, + _podStatsCommand, + _podStopCommand, + _podTopCommand, + _podUnpauseCommand, + } +} + +func getVolumeSubCommands() []*cobra.Command { + return []*cobra.Command{ + _volumeCreateCommand, + _volumeLsCommand, + _volumeRmCommand, + _volumeInspectCommand, + _volumePruneCommand, + } } -func getContainerSubCommands() []cli.Command { - return []cli.Command{ - attachCommand, - checkpointCommand, - cleanupCommand, - containerExistsCommand, - commitCommand, - createCommand, - diffCommand, - execCommand, - exportCommand, - killCommand, - logsCommand, - psCommand, - mountCommand, - pauseCommand, - portCommand, - pruneContainersCommand, - refreshCommand, - restartCommand, - restoreCommand, - rmCommand, - runCommand, - runlabelCommand, - startCommand, - statsCommand, - stopCommand, - topCommand, - umountCommand, - unpauseCommand, - // updateCommand, - waitCommand, +func getGenerateSubCommands() []*cobra.Command { + return []*cobra.Command{ + _containerKubeCommand, } } -func getMainAppFlags() []cli.Flag { - return []cli.Flag{ - cli.StringFlag{ - Name: "cgroup-manager", - Usage: "Cgroup manager to use (cgroupfs or systemd, default systemd)", - }, - cli.StringFlag{ - Name: "cni-config-dir", - Usage: "Path of the configuration directory for CNI networks", - }, - cli.StringFlag{ - Name: "conmon", - Usage: "Path of the conmon binary", - }, - cli.StringFlag{ - Name: "default-mounts-file", - Usage: "Path to default mounts file", - Hidden: true, - }, - cli.StringSliceFlag{ - Name: "hooks-dir", - Usage: "Set the OCI hooks directory path (may be set multiple times)", - }, - cli.IntFlag{ - Name: "max-workers", - Usage: "The maximum number of workers for parallel operations", - Hidden: true, - }, - cli.StringFlag{ - Name: "namespace", - Usage: "Set the libpod namespace, used to create separate views of the containers and pods on the system", - Value: "", - }, - cli.StringFlag{ - Name: "root", - Usage: "Path to the root directory in which data, including images, is stored", - }, - cli.StringFlag{ - Name: "runroot", - Usage: "Path to the 'run directory' where all state information is stored", - }, - cli.StringFlag{ - Name: "runtime", - Usage: "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc", - }, - cli.StringFlag{ - Name: "storage-driver, s", - Usage: "Select which storage driver is used to manage storage of images and containers (default is overlay)", - }, - cli.StringSliceFlag{ - Name: "storage-opt", - Usage: "Used to pass an option to the storage driver", - }, - cli.BoolFlag{ - Name: "syslog", - Usage: "Output logging information to syslog as well as the console", - }, + +func getPlaySubCommands() []*cobra.Command { + return []*cobra.Command{ + _playKubeCommand, + } +} + +func getTrustSubCommands() []*cobra.Command { + return []*cobra.Command{ + _setTrustCommand, + _showTrustCommand, + } +} + +func getSystemSubCommands() []*cobra.Command { + return []*cobra.Command{ + _infoCommand, + _pruneSystemCommand, } } diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go index 0adbd7b4c..80083eab9 100644 --- a/cmd/podman/commands_remoteclient.go +++ b/cmd/podman/commands_remoteclient.go @@ -2,24 +2,47 @@ package main -import "github.com/urfave/cli" +import ( + "github.com/spf13/cobra" +) -func getAppCommands() []cli.Command { - return []cli.Command{} +//import "github.com/urfave/cli" +// +func getAppCommands() []*cobra.Command { + return []*cobra.Command{} } -func getImageSubCommands() []cli.Command { - return []cli.Command{} +func getImageSubCommands() []*cobra.Command { + return []*cobra.Command{} } -func getContainerSubCommands() []cli.Command { - return []cli.Command{} +func getContainerSubCommands() []*cobra.Command { + return []*cobra.Command{} } -func getSystemSubCommands() []cli.Command { - return []cli.Command{} +func getPodSubCommands() []*cobra.Command { + return []*cobra.Command{} } -func getMainAppFlags() []cli.Flag { - return []cli.Flag{} +func getVolumeSubCommands() []*cobra.Command { + return []*cobra.Command{} +} + +func getGenerateSubCommands() []*cobra.Command { + return []*cobra.Command{} +} + +func getPlaySubCommands() []*cobra.Command { + return []*cobra.Command{} +} + +func getTrustSubCommands() []*cobra.Command { + return []*cobra.Command{} +} + +//func getMainAppFlags() []cli.Flag { +// return []cli.Flag{} +//} +func getSystemSubCommands() []*cobra.Command { + return []*cobra.Command{} } diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index 02ede4f73..6fd6b9761 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" "io" "os" "strings" @@ -13,57 +15,43 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( - commitFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "change, c", - Usage: fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(libpod.ChangeCmds, " | ")), - }, - cli.StringFlag{ - Name: "format, f", - Usage: "`format` of the image manifest and metadata", - Value: "oci", - }, - cli.StringFlag{ - Name: "message, m", - Usage: "Set commit message for imported image", - }, - cli.StringFlag{ - Name: "author, a", - Usage: "Set the author for the image committed", - }, - cli.BoolTFlag{ - Name: "pause, p", - Usage: "Pause container during commit", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output", - }, - } + commitCommand cliconfig.CommitValues commitDescription = `Create an image from a container's changes. Optionally tag the image created, set the author with the --author flag, set the commit message with the --message flag, and make changes to the instructions with the --change flag.` - commitCommand = cli.Command{ - Name: "commit", - Usage: "Create new image based on the changed container", - Description: commitDescription, - Flags: sortFlags(commitFlags), - Action: commitCmd, - ArgsUsage: "CONTAINER [REPOSITORY[:TAG]]", - OnUsageError: usageErrorHandler, + + _commitCommand = &cobra.Command{ + Use: "commit", + Short: "Create new image based on the changed container", + Long: commitDescription, + RunE: func(cmd *cobra.Command, args []string) error { + commitCommand.InputArgs = args + commitCommand.GlobalFlags = MainGlobalOpts + return commitCmd(&commitCommand) + }, + Example: "CONTAINER [REPOSITORY[:TAG]]", } ) -func commitCmd(c *cli.Context) error { - if err := validateFlags(c, commitFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) +func init() { + commitCommand.Command = _commitCommand + flags := commitCommand.Flags() + flags.StringSliceVarP(&commitCommand.Change, "change", "c", []string{}, fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(libpod.ChangeCmds, " | "))) + flags.StringVarP(&commitCommand.Format, "format", "f", "oci", "`Format` of the image manifest and metadata") + flags.StringVarP(&commitCommand.Message, "message", "m", "", "Set commit message for imported image") + flags.StringVarP(&commitCommand.Author, "author", "a", "", "Set the author for the image committed") + flags.BoolVarP(&commitCommand.Pause, "pause", "p", false, "Pause container during commit") + flags.BoolVarP(&commitCommand.Quiet, "quiet", "q", false, "Suppress output") + + rootCmd.AddCommand(commitCommand.Command) +} + +func commitCmd(c *cliconfig.CommitValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -73,26 +61,26 @@ func commitCmd(c *cli.Context) error { writer io.Writer mimeType string ) - args := c.Args() + args := c.InputArgs if len(args) != 2 { return errors.Errorf("you must provide a container name or ID and a target image name") } - switch c.String("format") { + switch c.Format { case "oci": mimeType = buildah.OCIv1ImageManifest - if c.IsSet("message") || c.IsSet("m") { + if c.Flag("message").Changed { return errors.Errorf("messages are only compatible with the docker image format (-f docker)") } case "docker": mimeType = manifest.DockerV2Schema2MediaType default: - return errors.Errorf("unrecognized image format %q", c.String("format")) + return errors.Errorf("unrecognized image format %q", c.Format) } container := args[0] reference := args[1] - if c.IsSet("change") || c.IsSet("c") { - for _, change := range c.StringSlice("change") { + if c.Flag("change").Changed { + for _, change := range c.Change { splitChange := strings.Split(strings.ToUpper(change), "=") if !util.StringInSlice(splitChange[0], libpod.ChangeCmds) { return errors.Errorf("invalid syntax for --change: %s", change) @@ -100,7 +88,7 @@ func commitCmd(c *cli.Context) error { } } - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } ctr, err := runtime.LookupContainer(container) @@ -117,10 +105,10 @@ func commitCmd(c *cli.Context) error { } options := libpod.ContainerCommitOptions{ CommitOptions: coptions, - Pause: c.Bool("pause"), - Message: c.String("message"), - Changes: c.StringSlice("change"), - Author: c.String("author"), + Pause: c.Pause, + Message: c.Message, + Changes: c.Change, + Author: c.Author, } newImage, err := ctr.Commit(getContext(), reference, options) if err != nil { diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 82d60d62e..5fbdfce50 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -4,34 +4,19 @@ import ( "context" "fmt" "os" - "reflect" - "regexp" - "sort" "strings" "github.com/containers/buildah" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/fatih/camelcase" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( - stores = make(map[storage.Store]struct{}) - LatestFlag = cli.BoolFlag{ - Name: "latest, l", - Usage: "Act on the latest container podman is aware of", - } - LatestPodFlag = cli.BoolFlag{ - Name: "latest, l", - Usage: "Act on the latest pod podman is aware of", - } - WorkDirFlag = cli.StringFlag{ - Name: "workdir, w", - Usage: "Working directory inside the container", - } + stores = make(map[storage.Store]struct{}) ) const ( @@ -50,53 +35,9 @@ func shortID(id string) string { return id } -func usageErrorHandler(context *cli.Context, err error, _ bool) error { - cmd := context.App.Name - if len(context.Command.Name) > 0 { - cmd = cmd + " " + context.Command.Name - } - return fmt.Errorf("%s\nSee '%s --help'.", err, cmd) -} - -func commandNotFoundHandler(context *cli.Context, command string) { - fmt.Fprintf(os.Stderr, "Command %q not found.\nSee `%s --help`.\n", command, context.App.Name) - os.Exit(exitCode) -} - -// validateFlags searches for StringFlags or StringSlice flags that never had -// a value set. This commonly occurs when the CLI mistakenly takes the next -// option and uses it as a value. -func validateFlags(c *cli.Context, flags []cli.Flag) error { - for _, flag := range flags { - switch reflect.TypeOf(flag).String() { - case "cli.StringSliceFlag": - { - f := flag.(cli.StringSliceFlag) - name := strings.Split(f.Name, ",") - val := c.StringSlice(name[0]) - for _, v := range val { - if ok, _ := regexp.MatchString("^-.+", v); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - case "cli.StringFlag": - { - f := flag.(cli.StringFlag) - name := strings.Split(f.Name, ",") - val := c.String(name[0]) - if ok, _ := regexp.MatchString("^-.+", val); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - } - return nil -} - // checkAllAndLatest checks that --all and --latest are used correctly -func checkAllAndLatest(c *cli.Context) error { - argLen := len(c.Args()) +func checkAllAndLatest(c *cliconfig.PodmanCommand) error { + argLen := len(c.InputArgs) if (c.Bool("all") || c.Bool("latest")) && argLen > 0 { return errors.Errorf("no arguments are needed with --all or --latest") } @@ -117,7 +58,7 @@ func checkAllAndLatest(c *cli.Context) error { // is desired a -1 can be used to get all containers. For a better // error message, if the filter fails, a corresponding verb can be // specified which will then appear in the error message. -func getAllOrLatestContainers(c *cli.Context, runtime *libpod.Runtime, filterState libpod.ContainerStatus, verb string) ([]*libpod.Container, error) { +func getAllOrLatestContainers(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, filterState libpod.ContainerStatus, verb string) ([]*libpod.Container, error) { var containers []*libpod.Container var lastError error var err error @@ -142,7 +83,7 @@ func getAllOrLatestContainers(c *cli.Context, runtime *libpod.Runtime, filterSta } containers = append(containers, lastCtr) } else { - args := c.Args() + args := c.InputArgs for _, i := range args { container, err := runtime.LookupContainer(i) if err != nil { @@ -173,363 +114,367 @@ func getDefaultNetwork() string { return "bridge" } -// Common flags shared between commands -var createFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "add-host", - Usage: "Add a custom host-to-IP mapping (host:ip) (default [])", - }, - cli.StringSliceFlag{ - Name: "annotation", - Usage: "Add annotations to container (key:value) (default [])", - }, - cli.StringSliceFlag{ - Name: "attach, a", - Usage: "Attach to STDIN, STDOUT or STDERR (default [])", - }, - cli.StringFlag{ - Name: "blkio-weight", - Usage: "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", - }, - cli.StringSliceFlag{ - Name: "blkio-weight-device", - Usage: "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", - }, - cli.StringSliceFlag{ - Name: "cap-add", - Usage: "Add capabilities to the container", - }, - cli.StringSliceFlag{ - Name: "cap-drop", - Usage: "Drop capabilities from the container", - }, - cli.StringFlag{ - Name: "cgroup-parent", - Usage: "Optional parent cgroup for the container", - }, - cli.StringFlag{ - Name: "cidfile", - Usage: "Write the container ID to the file", - }, - cli.StringFlag{ - Name: "conmon-pidfile", - Usage: "Path to the file that will receive the PID of conmon", - }, - cli.Uint64Flag{ - Name: "cpu-period", - Usage: "Limit the CPU CFS (Completely Fair Scheduler) period", - }, - cli.Int64Flag{ - Name: "cpu-quota", - Usage: "Limit the CPU CFS (Completely Fair Scheduler) quota", - }, - cli.Uint64Flag{ - Name: "cpu-rt-period", - Usage: "Limit the CPU real-time period in microseconds", - }, - cli.Int64Flag{ - Name: "cpu-rt-runtime", - Usage: "Limit the CPU real-time runtime in microseconds", - }, - cli.Uint64Flag{ - Name: "cpu-shares", - Usage: "CPU shares (relative weight)", - }, - cli.Float64Flag{ - Name: "cpus", - Usage: "Number of CPUs. The default is 0.000 which means no limit", - }, - cli.StringFlag{ - Name: "cpuset-cpus", - Usage: "CPUs in which to allow execution (0-3, 0,1)", - }, - cli.StringFlag{ - Name: "cpuset-mems", - Usage: "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", - }, - cli.BoolFlag{ - Name: "detach, d", - Usage: "Run container in background and print container ID", - }, - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`", - }, - cli.StringSliceFlag{ - Name: "device", - Usage: "Add a host device to the container (default [])", - }, - cli.StringSliceFlag{ - Name: "device-read-bps", - Usage: "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", - }, - cli.StringSliceFlag{ - Name: "device-read-iops", - Usage: "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", - }, - cli.StringSliceFlag{ - Name: "device-write-bps", - Usage: "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", - }, - cli.StringSliceFlag{ - Name: "device-write-iops", - Usage: "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", - }, - cli.StringSliceFlag{ - Name: "dns", - Usage: "Set custom DNS servers", - }, - cli.StringSliceFlag{ - Name: "dns-opt", - Usage: "Set custom DNS options", - }, - cli.StringSliceFlag{ - Name: "dns-search", - Usage: "Set custom DNS search domains", - }, - cli.StringFlag{ - Name: "entrypoint", - Usage: "Overwrite the default ENTRYPOINT of the image", - }, - cli.StringSliceFlag{ - Name: "env, e", - Usage: "Set environment variables in container", - }, - cli.StringSliceFlag{ - Name: "env-file", - Usage: "Read in a file of environment variables", - }, - cli.StringSliceFlag{ - Name: "expose", - Usage: "Expose a port or a range of ports (default [])", - }, - cli.StringSliceFlag{ - Name: "gidmap", - Usage: "GID map to use for the user namespace", - }, - cli.StringSliceFlag{ - Name: "group-add", - Usage: "Add additional groups to join (default [])", - }, - cli.BoolFlag{ - Name: "help", - Hidden: true, - }, - cli.StringFlag{ - Name: "hostname, h", - Usage: "Set container hostname", - }, - cli.StringFlag{ - Name: "image-volume, builtin-volume", - Usage: "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", - Value: "bind", - }, - cli.BoolFlag{ - Name: "init", - Usage: "Run an init binary inside the container that forwards signals and reaps processes", - }, - cli.StringFlag{ - Name: "init-path", +func getCreateFlags(c *cliconfig.PodmanCommand) { + + createFlags := c.Flags() + + createFlags.StringSlice( + "add-host", []string{}, + "Add a custom host-to-IP mapping (host:ip) (default [])", + ) + createFlags.StringSlice( + "annotation", []string{}, + "Add annotations to container (key:value) (default [])", + ) + createFlags.StringSliceP( + "attach", "a", []string{}, + "Attach to STDIN, STDOUT or STDERR (default [])", + ) + createFlags.String( + "blkio-weight", "", + "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", + ) + createFlags.StringSlice( + "blkio-weight-device", []string{}, + "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", + ) + createFlags.StringSlice( + "cap-add", []string{}, + "Add capabilities to the container", + ) + createFlags.StringSlice( + "cap-drop", []string{}, + "Drop capabilities from the container", + ) + createFlags.String( + "cgroup-parent", "", + "Optional parent cgroup for the container", + ) + createFlags.String( + "cidfile", "", + "Write the container ID to the file", + ) + createFlags.String( + "conmon-pidfile", "", + "Path to the file that will receive the PID of conmon", + ) + createFlags.Uint64( + "cpu-period", 0, + "Limit the CPU CFS (Completely Fair Scheduler) period", + ) + createFlags.Int64( + "cpu-quota", 0, + "Limit the CPU CFS (Completely Fair Scheduler) quota", + ) + createFlags.Uint64( + "cpu-rt-period", 0, + "Limit the CPU real-time period in microseconds", + ) + createFlags.Int64( + "cpu-rt-runtime", 0, + "Limit the CPU real-time runtime in microseconds", + ) + createFlags.Uint64( + "cpu-shares", 0, + "CPU shares (relative weight)", + ) + createFlags.Float64( + "cpus", 0, + "Number of CPUs. The default is 0.000 which means no limit", + ) + createFlags.String( + "cpuset-cpus", "", + "CPUs in which to allow execution (0-3, 0,1)", + ) + createFlags.String( + "cpuset-mems", "", + "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", + ) + createFlags.BoolP( + "detach", "d", false, + "Run container in background and print container ID", + ) + createFlags.String( + "detach-keys", "", + "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`", + ) + createFlags.StringSlice( + "device", []string{}, + "Add a host device to the container (default [])", + ) + createFlags.StringSlice( + "device-read-bps", []string{}, + "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", + ) + createFlags.StringSlice( + "device-read-iops", []string{}, + "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", + ) + createFlags.StringSlice( + "device-write-bps", []string{}, + "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", + ) + createFlags.StringSlice( + "device-write-iops", []string{}, + "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", + ) + createFlags.StringSlice( + "dns", []string{}, + "Set custom DNS servers", + ) + createFlags.StringSlice( + "dns-opt", []string{}, + "Set custom DNS options", + ) + createFlags.StringSlice( + "dns-search", []string{}, + "Set custom DNS search domains", + ) + createFlags.String( + "entrypoint", "", + "Overwrite the default ENTRYPOINT of the image", + ) + createFlags.StringSliceP( + "env", "e", []string{}, + "Set environment variables in container", + ) + createFlags.StringSlice( + "env-file", []string{}, + "Read in a file of environment variables", + ) + createFlags.StringSlice( + "expose", []string{}, + "Expose a port or a range of ports (default [])", + ) + createFlags.StringSlice( + "gidmap", []string{}, + "GID map to use for the user namespace", + ) + createFlags.StringSlice( + "group-add", []string{}, + "Add additional groups to join (default [])", + ) + createFlags.Bool( + "help", false, "", + ) + + createFlags.StringP( + "hostname", "h", "", + "Set container hostname", + ) + createFlags.String( + "image-volume", "bind", + "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", + ) + createFlags.Bool( + "init", false, + "Run an init binary inside the container that forwards signals and reaps processes", + ) + createFlags.String( + "init-path", "", // Do not use the Value field for setting the default value to determine user input (i.e., non-empty string) - Usage: fmt.Sprintf("Path to the container-init binary (default: %q)", libpod.DefaultInitPath), - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Keep STDIN open even if not attached", - }, - cli.StringFlag{ - Name: "ip", - Usage: "Specify a static IPv4 address for the container", - }, - cli.StringFlag{ - Name: "ipc", - Usage: "IPC namespace to use", - }, - cli.StringFlag{ - Name: "kernel-memory", - Usage: "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)", - }, - cli.StringSliceFlag{ - Name: "label", - Usage: "Set metadata on container (default [])", - }, - cli.StringSliceFlag{ - Name: "label-file", - Usage: "Read in a line delimited file of labels (default [])", - }, - cli.StringFlag{ - Name: "log-driver", - Usage: "Logging driver for the container", - }, - cli.StringSliceFlag{ - Name: "log-opt", - Usage: "Logging driver options (default [])", - }, - cli.StringFlag{ - Name: "mac-address", - Usage: "Container MAC address (e.g. 92:d0:c6:0a:29:33), not currently supported", - }, - cli.StringFlag{ - Name: "memory, m", - Usage: "Memory limit (format: <number>[<unit>], where unit = b, k, m or g)", - }, - cli.StringFlag{ - Name: "memory-reservation", - Usage: "Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)", - }, - cli.StringFlag{ - Name: "memory-swap", - Usage: "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", - }, - cli.Int64Flag{ - Name: "memory-swappiness", - Usage: "Tune container memory swappiness (0 to 100) (default -1)", - Value: -1, - }, - cli.StringFlag{ - Name: "name", - Usage: "Assign a name to the container", - }, - cli.StringFlag{ - Name: "net, network", - Usage: "Connect a container to a network", - Value: getDefaultNetwork(), - }, - cli.BoolFlag{ - Name: "oom-kill-disable", - Usage: "Disable OOM Killer", - }, - cli.StringFlag{ - Name: "oom-score-adj", - Usage: "Tune the host's OOM preferences (-1000 to 1000)", - }, - cli.StringFlag{ - Name: "pid", - Usage: "PID namespace to use", - }, - cli.Int64Flag{ - Name: "pids-limit", - Usage: "Tune container pids limit (set -1 for unlimited)", - }, - cli.StringFlag{ - Name: "pod", - Usage: "Run container in an existing pod", - }, - cli.BoolFlag{ - Name: "privileged", - Usage: "Give extended privileges to container", - }, - cli.StringSliceFlag{ - Name: "publish, p", - Usage: "Publish a container's port, or a range of ports, to the host (default [])", - }, - cli.BoolFlag{ - Name: "publish-all, P", - Usage: "Publish all exposed ports to random ports on the host interface", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when pulling images", - }, - cli.BoolFlag{ - Name: "read-only", - Usage: "Make containers root filesystem read-only", - }, - cli.StringFlag{ - Name: "restart", - Usage: "Restart is not supported. Please use a systemd unit file for restart", - }, - cli.BoolFlag{ - Name: "rm", - Usage: "Remove container (and pod if created) after exit", - }, - cli.BoolFlag{ - Name: "rootfs", - Usage: "The first argument is not an image but the rootfs to the exploded container", - }, - cli.StringSliceFlag{ - Name: "security-opt", - Usage: "Security Options (default [])", - }, - cli.StringFlag{ - Name: "shm-size", - Usage: "Size of `/dev/shm`. The format is `<number><unit>`.", - Value: "65536k", - }, - cli.StringFlag{ - Name: "stop-signal", - Usage: "Signal to stop a container. Default is SIGTERM", - }, - cli.IntFlag{ - Name: "stop-timeout", - Usage: "Timeout (in seconds) to stop a container. Default is 10", - Value: libpod.CtrRemoveTimeout, - }, - cli.StringSliceFlag{ - Name: "storage-opt", - Usage: "Storage driver options per container (default [])", - }, - cli.StringFlag{ - Name: "subgidname", - Usage: "Name of range listed in /etc/subgid for use in user namespace", - }, - cli.StringFlag{ - Name: "subuidname", - Usage: "Name of range listed in /etc/subuid for use in user namespace", - }, + fmt.Sprintf("Path to the container-init binary (default: %q)", libpod.DefaultInitPath), + ) + createFlags.BoolP( + "interactive", "i", false, + "Keep STDIN open even if not attached", + ) + createFlags.String( + "ip", "", + "Specify a static IPv4 address for the container", + ) + createFlags.String( + "ipc", "", + "IPC namespace to use", + ) + createFlags.String( + "kernel-memory", "", + "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)", + ) + createFlags.StringSlice( + "label", []string{}, + "Set metadata on container (default [])", + ) + createFlags.StringSlice( + "label-file", []string{}, + "Read in a line delimited file of labels (default [])", + ) + createFlags.String( + "log-driver", "", + "Logging driver for the container", + ) + createFlags.StringSlice( + "log-opt", []string{}, + "Logging driver options (default [])", + ) + createFlags.String( + "mac-address", "", + "Container MAC address (e.g. 92:d0:c6:0a:29:33), not currently supported", + ) + createFlags.StringP( + "memory", "m", "", + "Memory limit (format: <number>[<unit>], where unit = b, k, m or g)", + ) + createFlags.String( + "memory-reservation", "", + "Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)", + ) + createFlags.String( + "memory-swap", "", + "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", + ) + createFlags.Int64( + "memory-swappiness", -1, + "Tune container memory swappiness (0 to 100) (default -1)", + ) + createFlags.String( + "name", "", + "Assign a name to the container", + ) + createFlags.String( + "net", getDefaultNetwork(), + "Connect a container to a network", + ) + createFlags.String( + "network", getDefaultNetwork(), + "Connect a container to a network", + ) + createFlags.Bool( + "oom-kill-disable", false, + "Disable OOM Killer", + ) + createFlags.Int( + "oom-score-adj", 0, + "Tune the host's OOM preferences (-1000 to 1000)", + ) + createFlags.String( + "pid", "", + "PID namespace to use", + ) + createFlags.Int64( + "pids-limit", 0, + "Tune container pids limit (set -1 for unlimited)", + ) + createFlags.String( + "pod", "", + "Run container in an existing pod", + ) + createFlags.Bool( + "privileged", false, + "Give extended privileges to container", + ) + createFlags.StringSliceP( + "publish", "p", []string{}, + "Publish a container's port, or a range of ports, to the host (default [])", + ) + createFlags.BoolP( + "publish-all", "P", false, + "Publish all exposed ports to random ports on the host interface", + ) + createFlags.BoolP( + "quiet", "q", false, + "Suppress output information when pulling images", + ) + createFlags.Bool( + "read-only", false, + "Make containers root filesystem read-only", + ) + createFlags.String( + "restart", "", + "Restart is not supported. Please use a systemd unit file for restart", + ) + createFlags.Bool( + "rm", false, + "Remove container (and pod if created) after exit", + ) + createFlags.Bool( + "rootfs", false, + "The first argument is not an image but the rootfs to the exploded container", + ) + createFlags.StringArray( + "security-opt", []string{}, + "Security Options (default [])", + ) + createFlags.String( + "shm-size", "65536k", + "Size of `/dev/shm`. The format is `<number><unit>`", + ) + createFlags.String( + "stop-signal", "", + "Signal to stop a container. Default is SIGTERM", + ) + createFlags.Int( + "stop-timeout", libpod.CtrRemoveTimeout, + "Timeout (in seconds) to stop a container. Default is 10", + ) + createFlags.StringSlice( + "storage-opt", []string{}, + "Storage driver options per container (default [])", + ) + createFlags.String( + "subgidname", "", + "Name of range listed in /etc/subgid for use in user namespace", + ) + createFlags.String( + "subuidname", "", + "Name of range listed in /etc/subuid for use in user namespace", + ) - cli.StringSliceFlag{ - Name: "sysctl", - Usage: "Sysctl options (default [])", - }, - cli.BoolTFlag{ - Name: "systemd", - Usage: "Run container in systemd mode if the command executable is systemd or init", - }, - cli.StringSliceFlag{ - Name: "tmpfs", - Usage: "Mount a temporary filesystem (`tmpfs`) into a container (default [])", - }, - cli.BoolFlag{ - Name: "tty, t", - Usage: "Allocate a pseudo-TTY for container", - }, - cli.StringSliceFlag{ - Name: "uidmap", - Usage: "UID map to use for the user namespace", - }, - cli.StringSliceFlag{ - Name: "ulimit", - Usage: "Ulimit options (default [])", - }, - cli.StringFlag{ - Name: "user, u", - Usage: "Username or UID (format: <name|uid>[:<group|gid>])", - }, - cli.StringFlag{ - Name: "userns", - Usage: "User namespace to use", - }, - cli.StringFlag{ - Name: "uts", - Usage: "UTS namespace to use", - }, - cli.StringSliceFlag{ - Name: "mount", - Usage: "Attach a filesystem mount to the container (default [])", - }, - cli.StringSliceFlag{ - Name: "volume, v", - Usage: "Bind mount a volume into the container (default [])", - }, - cli.StringSliceFlag{ - Name: "volumes-from", - Usage: "Mount volumes from the specified container(s) (default [])", - }, - WorkDirFlag, + createFlags.StringSlice( + "sysctl", []string{}, + "Sysctl options (default [])", + ) + createFlags.Bool( + "systemd", true, + "Run container in systemd mode if the command executable is systemd or init", + ) + createFlags.StringSlice( + "tmpfs", []string{}, + "Mount a temporary filesystem (`tmpfs`) into a container (default [])", + ) + createFlags.BoolP( + "tty", "t", false, + "Allocate a pseudo-TTY for container", + ) + createFlags.StringSlice( + "uidmap", []string{}, + "UID map to use for the user namespace", + ) + createFlags.StringSlice( + "ulimit", []string{}, + "Ulimit options (default [])", + ) + createFlags.StringP( + "user", "u", "", + "Username or UID (format: <name|uid>[:<group|gid>])", + ) + createFlags.String( + "userns", "", + "User namespace to use", + ) + createFlags.String( + "uts", "", + "UTS namespace to use", + ) + createFlags.StringArray( + "mount", []string{}, + "Attach a filesystem mount to the container (default [])", + ) + createFlags.StringArrayP( + "volume", "v", []string{}, + "Bind mount a volume into the container (default [])", + ) + createFlags.StringSlice( + "volumes-from", []string{}, + "Mount volumes from the specified container(s) (default [])", + ) + createFlags.StringP( + "workdir", "w", "", + "Working directory inside the container", + ) } -func getFormat(c *cli.Context) (string, error) { +func getFormat(c *cliconfig.PodmanCommand) (string, error) { format := strings.ToLower(c.String("format")) if strings.HasPrefix(format, buildah.OCI) { return buildah.OCIv1ImageManifest, nil @@ -541,13 +486,6 @@ func getFormat(c *cli.Context) (string, error) { return "", errors.Errorf("unrecognized image type %q", format) } -func sortFlags(flags []cli.Flag) []cli.Flag { - sort.Slice(flags, func(i, j int) bool { - return strings.Compare(flags[i].GetName(), flags[j].GetName()) < 0 - }) - return flags -} - func getAuthFile(authfile string) string { if authfile != "" { return authfile diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 29300a6a4..969cb2dc8 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -1,30 +1,21 @@ package main import ( - "sort" - - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) -var ( - containerSubCommands = []cli.Command{ - exportCommand, - inspectCommand, - } - containerDescription = "Manage containers" - containerCommand = cli.Command{ - Name: "container", - Usage: "Manage Containers", - Description: containerDescription, - ArgsUsage: "", - Subcommands: getContainerSubCommandsSorted(), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - } -) +var containerDescription = "Manage containers" +var containerCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "container", + Short: "Manage Containers", + Long: containerDescription, + TraverseChildren: true, + }, +} -func getContainerSubCommandsSorted() []cli.Command { - containerSubCommands = append(containerSubCommands, getContainerSubCommands()...) - sort.Sort(commandSortedAlpha{containerSubCommands}) - return containerSubCommands +func init() { + containerCommand.AddCommand(getContainerSubCommands()...) + rootCmd.AddCommand(containerCommand.Command) } diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go index 09141e9a3..3f9b46035 100644 --- a/cmd/podman/containers_prune.go +++ b/cmd/podman/containers_prune.go @@ -3,30 +3,39 @@ package main import ( "context" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + pruneContainersCommand cliconfig.ContainersPrune pruneContainersDescription = ` podman container prune Removes all exited containers ` - pruneContainersCommand = cli.Command{ - Name: "prune", - Usage: "Remove all stopped containers", - Description: pruneContainersDescription, - Action: pruneContainersCmd, - OnUsageError: usageErrorHandler, + _pruneContainersCommand = &cobra.Command{ + Use: "prune", + Short: "Remove all stopped containers", + Long: pruneContainersDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pruneContainersCommand.InputArgs = args + pruneContainersCommand.GlobalFlags = MainGlobalOpts + return pruneContainersCmd(&pruneContainersCommand) + }, } ) +func init() { + pruneContainersCommand.Command = _pruneContainersCommand +} + func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force bool) error { var deleteFuncs []shared.ParallelWorkerInput @@ -60,8 +69,8 @@ func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWork return printParallelOutput(deleteErrors, errCount) } -func pruneContainersCmd(c *cli.Context) error { - runtime, err := adapter.GetRuntime(c) +func pruneContainersCmd(c *cliconfig.ContainersPrune) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -69,7 +78,7 @@ func pruneContainersCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("rm") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 2d85abd35..2f8742052 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -12,6 +12,7 @@ import ( "strings" "syscall" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -29,37 +30,46 @@ import ( "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + createCommand cliconfig.CreateValues + createDescription = "Creates a new container from the given image or" + + " storage and prepares it for running the specified command. The" + + " container ID is then printed to stdout. You can then start it at" + + " any time with the podman start <container_id> command. The container" + + " will be created with the initial state 'created'." + _createCommand = &cobra.Command{ + Use: "create", + Short: "Create but do not start a container", + Long: createDescription, + RunE: func(cmd *cobra.Command, args []string) error { + createCommand.InputArgs = args + createCommand.GlobalFlags = MainGlobalOpts + return createCmd(&createCommand) + }, + Example: "IMAGE [COMMAND [ARG...]]", + } + defaultEnvVariables = map[string]string{ "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM": "xterm", } ) -var createDescription = "Creates a new container from the given image or" + - " storage and prepares it for running the specified command. The" + - " container ID is then printed to stdout. You can then start it at" + - " any time with the podman start <container_id> command. The container" + - " will be created with the initial state 'created'." - -var createCommand = cli.Command{ - Name: "create", - Usage: "Create but do not start a container", - Description: createDescription, - Flags: sortFlags(createFlags), - Action: createCmd, - ArgsUsage: "IMAGE [COMMAND [ARG...]]", - HideHelp: true, - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, +func init() { + createCommand.PodmanCommand.Command = _createCommand + + getCreateFlags(&createCommand.PodmanCommand) + flags := createCommand.Flags() + flags.SetInterspersed(true) + + rootCmd.AddCommand(createCommand.Command) } -func createCmd(c *cli.Context) error { - if err := createInit(c); err != nil { +func createCmd(c *cliconfig.CreateValues) error { + if err := createInit(&c.PodmanCommand); err != nil { return err } @@ -67,13 +77,13 @@ func createCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - ctr, _, err := createContainer(c, runtime) + ctr, _, err := createContainer(&c.PodmanCommand, runtime) if err != nil { return err } @@ -82,33 +92,24 @@ func createCmd(c *cli.Context) error { return nil } -func createInit(c *cli.Context) error { - // TODO should allow user to create based off a directory on the host not just image - // Need CLI support for this - +func createInit(c *cliconfig.PodmanCommand) error { // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). - if c.Bool("help") { - cli.ShowCommandHelpAndExit(c, "run", 0) - } - - if err := validateFlags(c, createFlags); err != nil { - return err - } - if len(c.Args()) < 1 { + if len(c.InputArgs) < 1 { return errors.Errorf("image name or ID is required") } return nil } -func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { +func createContainer(c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { + rtc := runtime.GetConfig() ctx := getContext() rootfs := "" if c.Bool("rootfs") { - rootfs = c.Args()[0] + rootfs = c.InputArgs[0] } var err error @@ -134,7 +135,7 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container writer = os.Stderr } - newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) + newImage, err := runtime.ImageRuntime().New(ctx, c.InputArgs[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) if err != nil { return nil, nil, err } @@ -264,7 +265,7 @@ func isPortInImagePorts(exposedPorts map[string]struct{}, port string) bool { return false } -func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { +func configureEntrypoint(c *cliconfig.PodmanCommand, data *inspect.ImageData) []string { entrypoint := []string{} if c.IsSet("entrypoint") { // Force entrypoint to "" @@ -284,7 +285,7 @@ func configureEntrypoint(c *cli.Context, data *inspect.ImageData) []string { return entrypoint } -func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { +func configurePod(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { pod, err := runtime.LookupPod(podName) if err != nil { return namespaces, err @@ -296,7 +297,7 @@ func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) { namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID) } - if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && pod.SharesNet()) { + if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) { namespaces["net"] = fmt.Sprintf("container:%s", podInfraID) } if (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) { @@ -313,7 +314,7 @@ func configurePod(c *cli.Context, runtime *libpod.Runtime, namespaces map[string // Parses CLI options related to container creation into a config which can be // parsed into an OCI runtime spec -func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { +func parseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *libpod.Runtime, imageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { var ( inputCommand, command []string memoryLimit, memoryReservation, memorySwap, memoryKernel int64 @@ -335,14 +336,14 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim imageID := "" - inputCommand = c.Args()[1:] + inputCommand = c.InputArgs[1:] if data != nil { imageID = data.ID } rootfs := "" if c.Bool("rootfs") { - rootfs = c.Args()[0] + rootfs = c.InputArgs[0] } sysctl, err := validateSysctl(c.StringSlice("sysctl")) @@ -382,15 +383,15 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim blkioWeight = uint16(u) } var mountList []spec.Mount - if mountList, err = parseMounts(c.StringSlice("mount")); err != nil { + if mountList, err = parseMounts(c.StringArray("mount")); err != nil { return nil, err } - if err = parseVolumes(c.StringSlice("volume")); err != nil { + if err = parseVolumes(c.StringArray("volume")); err != nil { return nil, err } - if err = parseVolumesFrom(c.StringSlice("volumes-from")); err != nil { + if err = parseVolumesFrom(c.StringArray("volumes-from")); err != nil { return nil, err } @@ -399,10 +400,10 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if c.Bool("detach") && c.Bool("rm") { return nil, errors.Errorf("--rm and --detach cannot be specified together") } - if c.Int64("cpu-period") != 0 && c.Float64("cpus") > 0 { + if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed { return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") } - if c.Int64("cpu-quota") != 0 && c.Float64("cpus") > 0 { + if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") } @@ -420,9 +421,13 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim // Instead of integrating here, should be done in libpod // However, that also involves setting up security opts // when the pod's namespace is integrated + namespaceNet := c.String("network") + if c.Flag("net").Changed { + namespaceNet = c.String("net") + } namespaces = map[string]string{ "pid": c.String("pid"), - "net": c.String("net"), + "net": namespaceNet, "ipc": c.String("ipc"), "user": c.String("userns"), "uts": c.String("uts"), @@ -654,7 +659,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim } var systemd bool - if command != nil && c.BoolT("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { + if command != nil && c.Bool("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { systemd = true if signalString == "" { stopSignal, err = signal.ParseSignal("RTMIN+3") @@ -663,7 +668,17 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim } } } + // This is done because cobra cannot have two aliased flags. So we have to check + // both + network := c.String("network") + if c.Flag("net").Changed { + network = c.String("net") + } + var memorySwappiness int64 + if c.Flags().Lookup("memory-swappiness") != nil { + memorySwappiness, _ = c.Flags().GetInt64("memory-swappiness") + } config := &cc.CreateConfig{ Runtime: runtime, Annotations: annotations, @@ -697,7 +712,7 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim LogDriverOpt: c.StringSlice("log-opt"), MacAddress: c.String("mac-address"), Name: c.String("name"), - Network: c.String("network"), + Network: network, NetworkAlias: c.StringSlice("network-alias"), IpcMode: ipcMode, NetMode: netMode, @@ -730,12 +745,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim Memory: memoryLimit, MemoryReservation: memoryReservation, MemorySwap: memorySwap, - MemorySwappiness: c.Int("memory-swappiness"), + MemorySwappiness: int(memorySwappiness), KernelMemory: memoryKernel, OomScoreAdj: c.Int("oom-score-adj"), - - PidsLimit: c.Int64("pids-limit"), - Ulimit: c.StringSlice("ulimit"), + PidsLimit: c.Int64("pids-limit"), + Ulimit: c.StringSlice("ulimit"), }, Rm: c.Bool("rm"), StopSignal: stopSignal, @@ -747,13 +761,12 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim User: user, UsernsMode: usernsMode, Mounts: mountList, - Volumes: c.StringSlice("volume"), + Volumes: c.StringArray("volume"), WorkDir: workDir, Rootfs: rootfs, VolumesFrom: c.StringSlice("volumes-from"), - Syslog: c.GlobalBool("syslog"), + Syslog: c.GlobalFlags.Syslog, } - if c.Bool("init") { initPath := c.String("init-path") if initPath == "" { @@ -767,11 +780,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim if config.Privileged { config.LabelOpts = label.DisableSecOpt() } else { - if err := parseSecurityOpt(config, c.StringSlice("security-opt")); err != nil { + if err := parseSecurityOpt(config, c.StringArray("security-opt")); err != nil { return nil, err } } - config.SecurityOpts = c.StringSlice("security-opt") + config.SecurityOpts = c.StringArray("security-opt") warnings, err := verifyContainerResources(config, false) if err != nil { return nil, err diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go index 95b9321fd..ae0549687 100644 --- a/cmd/podman/create_cli.go +++ b/cmd/podman/create_cli.go @@ -80,19 +80,22 @@ func addWarning(warnings []string, msg string) []string { // podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... // podman run --mount type=tmpfs,target=/dev/shm .. func parseMounts(mounts []string) ([]spec.Mount, error) { + // TODO(vrothberg): the manual parsing can be replaced with a regular expression + // to allow a more robust parsing of the mount format and to give + // precise errors regarding supported format versus suppored options. var mountList []spec.Mount - errInvalidSyntax := errors.Errorf("incorrect mount format : should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>,[options]") + errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs>,[src=<host-dir>,]target=<ctr-dir>[,options]") for _, mount := range mounts { var tokenCount int var mountInfo spec.Mount arr := strings.SplitN(mount, ",", 2) if len(arr) < 2 { - return nil, errInvalidSyntax + return nil, errors.Wrapf(errInvalidSyntax, "%q", mount) } kv := strings.Split(arr[0], "=") if kv[0] != "type" { - return nil, errInvalidSyntax + return nil, errors.Wrapf(errInvalidSyntax, "%q", mount) } switch kv[1] { case "bind": @@ -168,7 +171,7 @@ func parseVolumes(volumes []string) error { for _, volume := range volumes { arr := strings.SplitN(volume, ":", 3) if len(arr) < 2 { - return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir:[option]", volume) + return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume) } if err := validateVolumeHostDir(arr[0]); err != nil { return err diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index 5f813699f..04a659ab6 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -2,12 +2,12 @@ package main import ( "fmt" - + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) type diffJSONOutput struct { @@ -33,31 +33,35 @@ func (so stdoutStruct) Out() error { } var ( - diffFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "archive", - Usage: "Save the diff as a tar archive", - Hidden: true, - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format.", - }, - } + diffCommand cliconfig.DiffValues diffDescription = fmt.Sprint(`Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer`) - diffCommand = cli.Command{ - Name: "diff", - Usage: "Inspect changes on container's file systems", - Description: diffDescription, - Flags: sortFlags(diffFlags), - Action: diffCmd, - ArgsUsage: "ID-NAME", - OnUsageError: usageErrorHandler, + _diffCommand = &cobra.Command{ + Use: "diff", + Short: "Inspect changes on container's file systems", + Long: diffDescription, + RunE: func(cmd *cobra.Command, args []string) error { + diffCommand.InputArgs = args + diffCommand.GlobalFlags = MainGlobalOpts + return diffCmd(&diffCommand) + }, + Example: "ID-NAME", } ) +func init() { + diffCommand.Command = _diffCommand + flags := diffCommand.Flags() + + flags.BoolVar(&diffCommand.Archive, "archive", true, "Save the diff as a tar archive") + flags.StringVar(&diffCommand.Format, "format", "", "Change the output format") + + flags.MarkHidden("archive") + + rootCmd.AddCommand(diffCommand.Command) + +} func formatJSON(output []diffOutputParams) (diffJSONOutput, error) { jsonStruct := diffJSONOutput{} for _, output := range output { @@ -75,29 +79,25 @@ func formatJSON(output []diffOutputParams) (diffJSONOutput, error) { return jsonStruct, nil } -func diffCmd(c *cli.Context) error { - if err := validateFlags(c, diffFlags); err != nil { - return err - } - - if len(c.Args()) != 1 { +func diffCmd(c *cliconfig.DiffValues) error { + if len(c.InputArgs) != 1 { return errors.Errorf("container, image, or layer name must be specified: podman diff [options [...]] ID-NAME") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - to := c.Args().Get(0) + to := c.InputArgs[0] changes, err := runtime.GetDiff("", to) if err != nil { return errors.Wrapf(err, "could not get changes for %q", to) } diffOutput := []diffOutputParams{} - outputFormat := c.String("format") + outputFormat := c.Format for _, change := range changes { diff --git a/cmd/podman/docker/types.go b/cmd/podman/docker/types.go deleted file mode 100644 index eda618e40..000000000 --- a/cmd/podman/docker/types.go +++ /dev/null @@ -1,272 +0,0 @@ -package docker - -// -// Types extracted from Docker -// - -import ( - "time" - - "github.com/containers/image/pkg/strslice" - "github.com/opencontainers/go-digest" -) - -// TypeLayers github.com/docker/docker/image/rootfs.go -const TypeLayers = "layers" - -// V2S2MediaTypeManifest github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeManifest = "application/vnd.docker.distribution.manifest.v2+json" - -// V2S2MediaTypeImageConfig github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeImageConfig = "application/vnd.docker.container.image.v1+json" - -// V2S2MediaTypeLayer github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip" - -// V2S2MediaTypeUncompressedLayer github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar" - -// V2S2RootFS describes images root filesystem -// This is currently a placeholder that only supports layers. In the future -// this can be made into an interface that supports different implementations. -// github.com/docker/docker/image/rootfs.go -type V2S2RootFS struct { - Type string `json:"type"` - DiffIDs []digest.Digest `json:"diff_ids,omitempty"` -} - -// V2S2History stores build commands that were used to create an image -// github.com/docker/docker/image/image.go -type V2S2History struct { - // Created is the timestamp at which the image was created - Created time.Time `json:"created"` - // Author is the name of the author that was specified when committing the image - Author string `json:"author,omitempty"` - // CreatedBy keeps the Dockerfile command used while building the image - CreatedBy string `json:"created_by,omitempty"` - // Comment is the commit message that was set when committing the image - Comment string `json:"comment,omitempty"` - // EmptyLayer is set to true if this history item did not generate a - // layer. Otherwise, the history item is associated with the next - // layer in the RootFS section. - EmptyLayer bool `json:"empty_layer,omitempty"` -} - -// ID is the content-addressable ID of an image. -// github.com/docker/docker/image/image.go -type ID digest.Digest - -// HealthConfig holds configuration settings for the HEALTHCHECK feature. -// github.com/docker/docker/api/types/container/config.go -type HealthConfig struct { - // Test is the test to perform to check that the container is healthy. - // An empty slice means to inherit the default. - // The options are: - // {} : inherit healthcheck - // {"NONE"} : disable healthcheck - // {"CMD", args...} : exec arguments directly - // {"CMD-SHELL", command} : run command with system's default shell - Test []string `json:",omitempty"` - - // Zero means to inherit. Durations are expressed as integer nanoseconds. - Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks. - Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung. - StartPeriod time.Duration `json:",omitempty"` // Time to wait after the container starts before running the first check. - - // Retries is the number of consecutive failures needed to consider a container as unhealthy. - // Zero means inherit. - Retries int `json:",omitempty"` -} - -// PortSet is a collection of structs indexed by Port -// github.com/docker/go-connections/nat/nat.go -type PortSet map[Port]struct{} - -// Port is a string containing port number and protocol in the format "80/tcp" -// github.com/docker/go-connections/nat/nat.go -type Port string - -// Config contains the configuration data about a container. -// It should hold only portable information about the container. -// Here, "portable" means "independent from the host we are running on". -// Non-portable information *should* appear in HostConfig. -// All fields added to this struct must be marked `omitempty` to keep getting -// predictable hashes from the old `v1Compatibility` configuration. -// github.com/docker/docker/api/types/container/config.go -type Config struct { - Hostname string // Hostname - Domainname string // Domainname - User string // User that will run the command(s) inside the container, also support user:group - AttachStdin bool // Attach the standard input, makes possible user interaction - AttachStdout bool // Attach the standard output - AttachStderr bool // Attach the standard error - ExposedPorts PortSet `json:",omitempty"` // List of exposed ports - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string // List of environment variable to set in the container - Cmd strslice.StrSlice // Command to run when starting the container - Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy - ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) - Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) - Volumes map[string]struct{} // List of volumes (mounts) used for the container - WorkingDir string // Current directory (PWD) in the command will be launched - Entrypoint strslice.StrSlice // Entrypoint to run when starting the container - NetworkDisabled bool `json:",omitempty"` // Is network disabled - MacAddress string `json:",omitempty"` // Mac Address of the container - OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile - Labels map[string]string // List of labels set to this container - StopSignal string `json:",omitempty"` // Signal to stop a container - StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container - Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT -} - -// V1Compatibility - For non-top-level layers, create fake V1Compatibility -// strings that fit the format and don't collide with anything else, but -// don't result in runnable images on their own. -// github.com/docker/distribution/manifest/schema1/config_builder.go -type V1Compatibility struct { - ID string `json:"id"` - Parent string `json:"parent,omitempty"` - Comment string `json:"comment,omitempty"` - Created time.Time `json:"created"` - Config struct { - Cmd []string - } `json:"container_config,omitempty"` - Author string `json:"author,omitempty"` - ThrowAway bool `json:"throwaway,omitempty"` -} - -// V1Image stores the V1 image configuration. -// github.com/docker/docker/image/image.go -type V1Image struct { - // ID is a unique 64 character identifier of the image - ID string `json:"id,omitempty"` - // Parent is the ID of the parent image - Parent string `json:"parent,omitempty"` - // Comment is the commit message that was set when committing the image - Comment string `json:"comment,omitempty"` - // Created is the timestamp at which the image was created - Created time.Time `json:"created"` - // Container is the id of the container used to commit - Container string `json:"container,omitempty"` - // ContainerConfig is the configuration of the container that is committed into the image - ContainerConfig Config `json:"container_config,omitempty"` - // DockerVersion specifies the version of Docker that was used to build the image - DockerVersion string `json:"docker_version,omitempty"` - // Author is the name of the author that was specified when committing the image - Author string `json:"author,omitempty"` - // Config is the configuration of the container received from the client - Config *Config `json:"config,omitempty"` - // Architecture is the hardware that the image is build and runs on - Architecture string `json:"architecture,omitempty"` - // OS is the operating system used to build and run the image - OS string `json:"os,omitempty"` - // Size is the total size of the image including all layers it is composed of - Size int64 `json:",omitempty"` -} - -// V2Image stores the image configuration -// github.com/docker/docker/image/image.go -type V2Image struct { - V1Image - Parent ID `json:"parent,omitempty"` - RootFS *V2S2RootFS `json:"rootfs,omitempty"` - History []V2S2History `json:"history,omitempty"` - OSVersion string `json:"os.version,omitempty"` - OSFeatures []string `json:"os.features,omitempty"` - - // rawJSON caches the immutable JSON associated with this image. - //rawJSON []byte - - // computedID is the ID computed from the hash of the image config. - // Not to be confused with the legacy V1 ID in V1Image. - //computedID ID -} - -// V2Versioned provides a struct with the manifest schemaVersion and mediaType. -// Incoming content with unknown schema version can be decoded against this -// struct to check the version. -// github.com/docker/distribution/manifest/versioned.go -type V2Versioned struct { - // SchemaVersion is the image manifest schema that this image follows - SchemaVersion int `json:"schemaVersion"` - - // MediaType is the media type of this schema. - MediaType string `json:"mediaType,omitempty"` -} - -// V2S1FSLayer is a container struct for BlobSums defined in an image manifest -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1FSLayer struct { - // BlobSum is the tarsum of the referenced filesystem image layer - BlobSum digest.Digest `json:"blobSum"` -} - -// V2S1History stores unstructured v1 compatibility information -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1History struct { - // V1Compatibility is the raw v1 compatibility information - V1Compatibility string `json:"v1Compatibility"` -} - -// V2S1Manifest provides the base accessible fields for working with V2 image -// format in the registry. -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1Manifest struct { - V2Versioned - - // Name is the name of the image's repository - Name string `json:"name"` - - // Tag is the tag of the image specified by this manifest - Tag string `json:"tag"` - - // Architecture is the host architecture on which this image is intended to - // run - Architecture string `json:"architecture"` - - // FSLayers is a list of filesystem layer blobSums contained in this image - FSLayers []V2S1FSLayer `json:"fsLayers"` - - // History is a list of unstructured historical data for v1 compatibility - History []V2S1History `json:"history"` -} - -// V2S2Descriptor describes targeted content. Used in conjunction with a blob -// store, a descriptor can be used to fetch, store and target any kind of -// blob. The struct also describes the wire protocol format. Fields should -// only be added but never changed. -// github.com/docker/distribution/blobs.go -type V2S2Descriptor struct { - // MediaType describe the type of the content. All text based formats are - // encoded as utf-8. - MediaType string `json:"mediaType,omitempty"` - - // Size in bytes of content. - Size int64 `json:"size,omitempty"` - - // Digest uniquely identifies the content. A byte stream can be verified - // against against this digest. - Digest digest.Digest `json:"digest,omitempty"` - - // URLs contains the source URLs of this content. - URLs []string `json:"urls,omitempty"` - - // NOTE: Before adding a field here, please ensure that all - // other options have been exhausted. Much of the type relationships - // depend on the simplicity of this type. -} - -// V2S2Manifest defines a schema2 manifest. -// github.com/docker/distribution/manifest/schema2/manifest.go -type V2S2Manifest struct { - V2Versioned - - // Config references the image configuration as a blob. - Config V2S2Descriptor `json:"config"` - - // Layers lists descriptors for the layers referenced by the - // configuration. - Layers []V2S2Descriptor `json:"layers"` -} diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index 073e72e64..b4f66bc03 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -2,82 +2,76 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( - execFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "env, e", - Usage: "Set environment variables", - }, - cli.BoolFlag{ - Name: "privileged", - Usage: "Give the process extended Linux capabilities inside the container. The default is false", - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Not supported. All exec commands are interactive by default.", - }, - cli.BoolFlag{ - Name: "tty, t", - Usage: "Allocate a pseudo-TTY. The default is false", - }, - cli.StringFlag{ - Name: "user, u", - Usage: "Sets the username or UID used and optionally the groupname or GID for the specified command", - }, - LatestFlag, - WorkDirFlag, - } + execCommand cliconfig.ExecValues + execDescription = ` podman exec Run a command in a running container ` - - execCommand = cli.Command{ - Name: "exec", - Usage: "Run a process in a running container", - Description: execDescription, - Flags: sortFlags(execFlags), - Action: execCmd, - ArgsUsage: "CONTAINER-NAME", - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _execCommand = &cobra.Command{ + Use: "exec", + Short: "Run a process in a running container", + Long: execDescription, + RunE: func(cmd *cobra.Command, args []string) error { + execCommand.InputArgs = args + execCommand.GlobalFlags = MainGlobalOpts + return execCmd(&execCommand) + }, + Example: "CONTAINER-NAME", } ) -func execCmd(c *cli.Context) error { - args := c.Args() +func init() { + execCommand.Command = _execCommand + flags := execCommand.Flags() + flags.SetInterspersed(false) + flags.StringSliceVarP(&execCommand.Env, "env", "e", []string{}, "Set environment variables") + flags.BoolVarP(&execCommand.Interfactive, "interactive", "i", false, "Not supported. All exec commands are interactive by default") + flags.BoolVarP(&execCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&execCommand.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") + flags.BoolVarP(&execCommand.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") + flags.StringVarP(&execCommand.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") + + flags.StringVarP(&execCommand.Workdir, "workdir", "w", "", "Working directory inside the container") + + rootCmd.AddCommand(execCommand.Command) +} + +func execCmd(c *cliconfig.ExecValues) error { + args := c.InputArgs var ctr *libpod.Container var err error argStart := 1 - if len(args) < 1 && !c.Bool("latest") { + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide one container name or id") } - if len(args) < 2 && !c.Bool("latest") { + if len(args) < 2 && !c.Latest { return errors.Errorf("you must provide a command to exec") } - if c.Bool("latest") { + if c.Latest { argStart = 0 } rootless.SetSkipStorageSetup(true) cmd := args[argStart:] - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { ctr, err = runtime.GetLatestContainer() } else { ctr, err = runtime.LookupContainer(args[0]) @@ -101,7 +95,7 @@ func execCmd(c *cli.Context) error { // ENVIRONMENT VARIABLES env := map[string]string{} - if err := readKVStrings(env, []string{}, c.StringSlice("env")); err != nil { + if err := readKVStrings(env, []string{}, c.Env); err != nil { return errors.Wrapf(err, "unable to process environment variables") } envs := []string{} @@ -109,5 +103,5 @@ func execCmd(c *cli.Context) error { envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } - return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user"), c.String("workdir")) + return ctr.Exec(c.Tty, c.Privileged, envs, cmd, c.User, c.Workdir) } diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go index a7601aaa2..a21e8fcf8 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -1,6 +1,8 @@ package main import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" "os" "github.com/containers/libpod/cmd/podman/libpodruntime" @@ -8,66 +10,78 @@ import ( "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( + imageExistsCommand cliconfig.ImageExistsValues + containerExistsCommand cliconfig.ContainerExistsValues + podExistsCommand cliconfig.PodExistsValues + imageExistsDescription = ` podman image exists Check if an image exists in local storage ` - - imageExistsCommand = cli.Command{ - Name: "exists", - Usage: "Check if an image exists in local storage", - Description: imageExistsDescription, - Action: imageExistsCmd, - ArgsUsage: "IMAGE-NAME", - OnUsageError: usageErrorHandler, - } -) - -var ( containerExistsDescription = ` podman container exists Check if a container exists in local storage ` - - containerExistsCommand = cli.Command{ - Name: "exists", - Usage: "Check if a container exists in local storage", - Description: containerExistsDescription, - Action: containerExistsCmd, - ArgsUsage: "CONTAINER-NAME", - OnUsageError: usageErrorHandler, - } -) - -var ( podExistsDescription = ` podman pod exists Check if a pod exists in local storage ` + _imageExistsCommand = &cobra.Command{ + Use: "exists", + Short: "Check if an image exists in local storage", + Long: imageExistsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + imageExistsCommand.InputArgs = args + imageExistsCommand.GlobalFlags = MainGlobalOpts + return imageExistsCmd(&imageExistsCommand) + }, + Example: "IMAGE-NAME", + } + + _containerExistsCommand = &cobra.Command{ + Use: "exists", + Short: "Check if a container exists in local storage", + Long: containerExistsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + containerExistsCommand.InputArgs = args + containerExistsCommand.GlobalFlags = MainGlobalOpts + return containerExistsCmd(&containerExistsCommand) - podExistsCommand = cli.Command{ - Name: "exists", - Usage: "Check if a pod exists in local storage", - Description: podExistsDescription, - Action: podExistsCmd, - ArgsUsage: "POD-NAME", - OnUsageError: usageErrorHandler, + }, + Example: "CONTAINER-NAME", + } + + _podExistsCommand = &cobra.Command{ + Use: "exists", + Short: "Check if a pod exists in local storage", + Long: podExistsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podExistsCommand.InputArgs = args + podExistsCommand.GlobalFlags = MainGlobalOpts + return podExistsCmd(&podExistsCommand) + }, + Example: "POD-NAME", } ) -func imageExistsCmd(c *cli.Context) error { - args := c.Args() +func init() { + imageExistsCommand.Command = _imageExistsCommand + containerExistsCommand.Command = _containerExistsCommand + podExistsCommand.Command = _podExistsCommand +} + +func imageExistsCmd(c *cliconfig.ImageExistsValues) error { + args := c.InputArgs if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one image at a time") } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -83,12 +97,12 @@ func imageExistsCmd(c *cli.Context) error { return nil } -func containerExistsCmd(c *cli.Context) error { - args := c.Args() +func containerExistsCmd(c *cliconfig.ContainerExistsValues) error { + args := c.InputArgs if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one container at a time") } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -102,12 +116,12 @@ func containerExistsCmd(c *cli.Context) error { return nil } -func podExistsCmd(c *cli.Context) error { - args := c.Args() +func podExistsCmd(c *cliconfig.PodExistsValues) error { + args := c.InputArgs if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one pod at a time") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } diff --git a/cmd/podman/export.go b/cmd/podman/export.go index eaa4e38a2..bd9e38b0c 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -3,50 +3,52 @@ package main import ( "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - exportFlags = []cli.Flag{ - cli.StringFlag{ - Name: "output, o", - Usage: "Write to a file, default is STDOUT", - Value: "/dev/stdout", - }, - } + exportCommand cliconfig.ExportValues exportDescription = "Exports container's filesystem contents as a tar archive" + " and saves it on the local machine." - exportCommand = cli.Command{ - Name: "export", - Usage: "Export container's filesystem contents as a tar archive", - Description: exportDescription, - Flags: sortFlags(exportFlags), - Action: exportCmd, - ArgsUsage: "CONTAINER", - OnUsageError: usageErrorHandler, + + _exportCommand = &cobra.Command{ + Use: "export", + Short: "Export container's filesystem contents as a tar archive", + Long: exportDescription, + RunE: func(cmd *cobra.Command, args []string) error { + exportCommand.InputArgs = args + exportCommand.GlobalFlags = MainGlobalOpts + return exportCmd(&exportCommand) + }, + Example: "CONTAINER", } ) +func init() { + exportCommand.Command = _exportCommand + flags := exportCommand.Flags() + flags.StringVarP(&exportCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") + rootCmd.AddCommand(exportCommand.Command) +} + // exportCmd saves a container to a tarball on disk -func exportCmd(c *cli.Context) error { - if err := validateFlags(c, exportFlags); err != nil { - return err - } +func exportCmd(c *cliconfig.ExportValues) error { if os.Geteuid() != 0 { rootless.SetSkipStorageSetup(true) } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) == 0 { return errors.Errorf("container id must be specified") } @@ -54,10 +56,11 @@ func exportCmd(c *cli.Context) error { return errors.Errorf("too many arguments given, need 1 at most.") } - output := c.String("output") + output := c.Output if runtime.Remote && (output == "/dev/stdout" || len(output) == 0) { return errors.New("remote client usage must specify an output file (-o)") } + if output == "/dev/stdout" { file := os.Stdout if logrus.IsTerminal(file) { @@ -68,5 +71,5 @@ func exportCmd(c *cli.Context) error { if err := validateFileName(output); err != nil { return err } - return runtime.Export(args[0], c.String("output")) + return runtime.Export(args[0], output) } diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go index 20a4a61ab..a5e15fdd6 100644 --- a/cmd/podman/generate.go +++ b/cmd/podman/generate.go @@ -1,23 +1,21 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) -var ( - generateSubCommands = []cli.Command{ - containerKubeCommand, - } +var generateDescription = "Generate structured data based for a containers and pods" +var generateCommand = cliconfig.PodmanCommand{ - generateDescription = "Generate structured data based for a containers and pods" - kubeCommand = cli.Command{ - Name: "generate", - Usage: "Generate structured data", - Description: generateDescription, - ArgsUsage: "", - Subcommands: generateSubCommands, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - Hidden: true, - } -) + Command: &cobra.Command{ + Use: "generate", + Short: "Generated structured data", + Long: generateDescription, + }, +} + +func init() { + generateCommand.AddCommand(getGenerateSubCommands()...) + rootCmd.AddCommand(generateCommand.Command) +} diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index fc667fb5b..8e3432d12 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -2,38 +2,40 @@ package main import ( "fmt" - + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" podmanVersion "github.com/containers/libpod/version" "github.com/ghodss/yaml" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" "k8s.io/api/core/v1" ) var ( - containerKubeFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "service, s", - Usage: "Generate YAML for kubernetes service object", - }, - } + containerKubeCommand cliconfig.GenerateKubeValues containerKubeDescription = "Generate Kubernetes Pod YAML" - containerKubeCommand = cli.Command{ - Name: "kube", - Usage: "Generate Kubernetes pod YAML for a container or pod", - Description: containerKubeDescription, - Flags: sortFlags(containerKubeFlags), - Action: generateKubeYAMLCmd, - ArgsUsage: "CONTAINER|POD-NAME", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _containerKubeCommand = &cobra.Command{ + Use: "kube", + Short: "Generate Kubernetes pod YAML for a container or pod", + Long: containerKubeDescription, + RunE: func(cmd *cobra.Command, args []string) error { + containerKubeCommand.InputArgs = args + containerKubeCommand.GlobalFlags = MainGlobalOpts + return generateKubeYAMLCmd(&containerKubeCommand) + }, + Example: "CONTAINER|POD-NAME", } ) -func generateKubeYAMLCmd(c *cli.Context) error { +func init() { + containerKubeCommand.Command = _containerKubeCommand + flags := containerKubeCommand.Flags() + flags.BoolVarP(&containerKubeCommand.Service, "service", "s", false, "Generate YAML for kubernetes service object") +} + +func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { var ( podYAML *v1.Pod container *libpod.Container @@ -48,12 +50,12 @@ func generateKubeYAMLCmd(c *cli.Context) error { if rootless.IsRootless() { return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") } - args := c.Args() + args := c.InputArgs if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { return errors.Errorf("you must provide one container|pod ID or name or --latest") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -77,7 +79,7 @@ func generateKubeYAMLCmd(c *cli.Context) error { return err } - if c.Bool("service") { + if c.Service { serviceYAML := libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts) marshalledService, err = yaml.Marshal(serviceYAML) if err != nil { diff --git a/cmd/podman/history.go b/cmd/podman/history.go index 8a9b6cd94..97e501947 100644 --- a/cmd/podman/history.go +++ b/cmd/podman/history.go @@ -6,12 +6,13 @@ import ( "strings" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" "github.com/docker/go-units" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const createdByTruncLength = 45 @@ -34,53 +35,44 @@ type historyOptions struct { } var ( - historyFlags = []cli.Flag{ - cli.BoolTFlag{ - Name: "human, H", - Usage: "Display sizes and dates in human readable format", - }, - cli.BoolFlag{ - Name: "no-trunc, notruncate", - Usage: "Do not truncate the output", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Display the numeric IDs only", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output to JSON or a Go template", - }, - } + historyCommand cliconfig.HistoryValues historyDescription = "Displays the history of an image. The information can be printed out in an easy to read, " + "or user specified format, and can be truncated." - historyCommand = cli.Command{ - Name: "history", - Usage: "Show history of a specified image", - Description: historyDescription, - Flags: sortFlags(historyFlags), - Action: historyCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _historyCommand = &cobra.Command{ + Use: "history", + Short: "Show history of a specified image", + Long: historyDescription, + RunE: func(cmd *cobra.Command, args []string) error { + historyCommand.InputArgs = args + historyCommand.GlobalFlags = MainGlobalOpts + return historyCmd(&historyCommand) + }, } ) -func historyCmd(c *cli.Context) error { - if err := validateFlags(c, historyFlags); err != nil { - return err - } +func init() { + historyCommand.Command = _historyCommand + flags := historyCommand.Flags() + flags.StringVar(&historyCommand.Format, "format", "", "Change the output to JSON or a Go template") + flags.BoolVarP(&historyCommand.Human, "human", "H", true, "Display sizes and dates in human readable format") + // notrucate needs to be added + flags.BoolVar(&historyCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") + flags.BoolVarP(&historyCommand.Quiet, "quiet", "q", false, "Display the numeric IDs only") - runtime, err := adapter.GetRuntime(c) + rootCmd.AddCommand(historyCommand.Command) + +} +func historyCmd(c *cliconfig.HistoryValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - format := genHistoryFormat(c.String("format"), c.Bool("quiet")) + format := genHistoryFormat(c.Format, c.Quiet) - args := c.Args() + args := c.InputArgs if len(args) == 0 { return errors.Errorf("an image name must be specified") } @@ -93,9 +85,9 @@ func historyCmd(c *cli.Context) error { return err } opts := historyOptions{ - human: c.BoolT("human"), - noTrunc: c.Bool("no-trunc"), - quiet: c.Bool("quiet"), + human: c.Human, + noTrunc: c.NoTrunc, + quiet: c.Quiet, format: format, } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index 6b451a1ca..ac7ff4944 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -1,37 +1,35 @@ package main import ( - "sort" - - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - imageSubCommands = []cli.Command{ - importCommand, - historyCommand, - imageExistsCommand, - inspectCommand, - lsImagesCommand, - pruneImagesCommand, - pullCommand, - rmImageCommand, - tagCommand, - } imageDescription = "Manage images" - imageCommand = cli.Command{ - Name: "image", - Usage: "Manage images", - Description: imageDescription, - ArgsUsage: "", - Subcommands: getImageSubCommandsSorted(), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + imageCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "image", + Short: "Manage images", + Long: imageDescription, + }, } ) -func getImageSubCommandsSorted() []cli.Command { - imageSubCommands = append(imageSubCommands, getImageSubCommands()...) - sort.Sort(commandSortedAlpha{imageSubCommands}) - return imageSubCommands +//imageSubCommands are implemented both in local and remote clients +var imageSubCommands = []*cobra.Command{ + _historyCommand, + _imageExistsCommand, + _inspectCommand, + _imagesCommand, + _pruneImagesCommand, + _pushCommand, + _rmiCommand, + _tagCommand, +} + +func init() { + imageCommand.AddCommand(imageSubCommands...) + imageCommand.AddCommand(getImageSubCommands()...) + rootCmd.AddCommand(imageCommand.Command) } diff --git a/cmd/podman/images.go b/cmd/podman/images.go index 9fdf0a780..6f5a3e9f1 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -8,6 +8,7 @@ import ( "time" "unicode" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/imagefilters" "github.com/containers/libpod/libpod/adapter" @@ -16,7 +17,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) type imagesTemplateParams struct { @@ -83,108 +84,79 @@ func (a imagesSortedSize) Less(i, j int) bool { } var ( - imagesFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show all images (default hides intermediate images)", - }, - cli.BoolFlag{ - Name: "digests", - Usage: "Show digests", - }, - cli.StringSliceFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions provided (default [])", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to JSON or a Go template", - }, - cli.BoolFlag{ - Name: "noheading, n", - Usage: "Do not print column headings", - }, - cli.BoolFlag{ - Name: "no-trunc, notruncate", - Usage: "Do not truncate output", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Display only image IDs", - }, - cli.StringFlag{ - Name: "sort", - Usage: "Sort by created, id, repository, size, or tag", - Value: "Created", - }, - } - + imagesCommand cliconfig.ImagesValues imagesDescription = "lists locally stored images." - imagesCommand = cli.Command{ - Name: "images", - Usage: "List images in local storage", - Description: imagesDescription, - Flags: sortFlags(imagesFlags), - Action: imagesCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - } - lsImagesCommand = cli.Command{ - Name: "list", - Aliases: []string{"ls"}, - Usage: "List images in local storage", - Description: imagesDescription, - Flags: imagesFlags, - Action: imagesCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + + _imagesCommand = &cobra.Command{ + Use: "images", + Short: "List images in local storage", + Long: imagesDescription, + RunE: func(cmd *cobra.Command, args []string) error { + imagesCommand.InputArgs = args + imagesCommand.GlobalFlags = MainGlobalOpts + return imagesCmd(&imagesCommand) + }, + Example: "", } ) -func imagesCmd(c *cli.Context) error { +func init() { + imagesCommand.Command = _imagesCommand + flags := imagesCommand.Flags() + flags.BoolVarP(&imagesCommand.All, "all", "a", false, "Show all images (default hides intermediate images)") + flags.BoolVar(&imagesCommand.Digests, "digests", false, "Show digests") + flags.StringSliceVarP(&imagesCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + flags.StringVar(&imagesCommand.Format, "format", "", "Change the output format to JSON or a Go template") + flags.BoolVarP(&imagesCommand.Noheading, "noheading", "n", false, "Do not print column headings") + // TODO Need to learn how to deal with second name being a string instead of a char. + // This needs to be "no-trunc, notruncate" + flags.BoolVar(&imagesCommand.NoTrunc, "no-trunc", false, "Do not truncate output") + flags.BoolVarP(&imagesCommand.Quiet, "quiet", "q", false, "Display only image IDs") + flags.StringVar(&imagesCommand.Sort, "sort", "created", "Sort by created, id, repository, size, or tag") + + rootCmd.AddCommand(imagesCommand.Command) + +} + +func imagesCmd(c *cliconfig.ImagesValues) error { var ( filterFuncs []imagefilters.ResultFilter newImage *adapter.ContainerImage ) - if err := validateFlags(c, imagesFlags); err != nil { - return err - } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "Could not get runtime") } defer runtime.Shutdown(false) - if len(c.Args()) == 1 { - newImage, err = runtime.NewImageFromLocal(c.Args().Get(0)) + if len(c.InputArgs) == 1 { + newImage, err = runtime.NewImageFromLocal(c.InputArgs[0]) if err != nil { return err } } - if len(c.Args()) > 1 { + if len(c.InputArgs) > 1 { return errors.New("'podman images' requires at most 1 argument") } ctx := getContext() - if len(c.StringSlice("filter")) > 0 || newImage != nil { - filterFuncs, err = CreateFilterFuncs(ctx, runtime, c, newImage) + if len(c.Filter) > 0 || newImage != nil { + filterFuncs, err = CreateFilterFuncs(ctx, runtime, c.Filter, newImage) if err != nil { return err } } opts := imagesOptions{ - quiet: c.Bool("quiet"), - noHeading: c.Bool("noheading"), - noTrunc: c.Bool("no-trunc"), - digests: c.Bool("digests"), - format: c.String("format"), - sort: c.String("sort"), - all: c.Bool("all"), + quiet: c.Quiet, + noHeading: c.Noheading, + noTrunc: c.NoTrunc, + digests: c.Digests, + format: c.Format, + sort: c.Sort, + all: c.All, } opts.outputformat = opts.setOutputFormat() @@ -195,7 +167,7 @@ func imagesCmd(c *cli.Context) error { var filteredImages []*adapter.ContainerImage //filter the images - if len(c.StringSlice("filter")) > 0 || newImage != nil { + if len(c.Filter) > 0 || newImage != nil { filteredImages = imagefilters.FilterImages(images, filterFuncs) } else { filteredImages = images @@ -333,6 +305,7 @@ func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) // generateImagesOutput generates the images based on the format provided func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, opts imagesOptions) error { + templateMap := GenImageOutputMap() if len(images) == 0 { return nil } @@ -344,15 +317,17 @@ func generateImagesOutput(ctx context.Context, images []*adapter.ContainerImage, out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)} default: imagesOutput := getImagesTemplateOutput(ctx, images, opts) - out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: imagesOutput[0].HeaderMap()} + out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.outputformat, Fields: templateMap} } return formats.Writer(out).Out() } -// HeaderMap produces a generic map of "headers" based on a line -// of output -func (i *imagesTemplateParams) HeaderMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(i)) +// GenImageOutputMap generates the map used for outputting the images header +// without requiring a populated image. This replaces the previous HeaderMap +// call. +func GenImageOutputMap() map[string]string { + io := imagesTemplateParams{} + v := reflect.Indirect(reflect.ValueOf(io)) values := make(map[string]string) for i := 0; i < v.NumField(); i++ { @@ -368,9 +343,9 @@ func (i *imagesTemplateParams) HeaderMap() map[string]string { // CreateFilterFuncs returns an array of filter functions based on the user inputs // and is later used to filter images for output -func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, c *cli.Context, img *adapter.ContainerImage) ([]imagefilters.ResultFilter, error) { +func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, filters []string, img *adapter.ContainerImage) ([]imagefilters.ResultFilter, error) { var filterFuncs []imagefilters.ResultFilter - for _, filter := range c.StringSlice("filter") { + for _, filter := range filters { splitFilter := strings.Split(filter, "=") switch splitFilter[0] { case "before": diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 844984bb9..ba99c5bde 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -3,35 +3,39 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + pruneImagesCommand cliconfig.PruneImagesValues pruneImagesDescription = ` podman image prune Removes all unnamed images from local storage ` - pruneImageFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all unused images, not just dangling ones", + _pruneImagesCommand = &cobra.Command{ + Use: "prune", + Short: "Remove unused images", + Long: pruneImagesDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pruneImagesCommand.InputArgs = args + pruneImagesCommand.GlobalFlags = MainGlobalOpts + return pruneImagesCmd(&pruneImagesCommand) }, } - pruneImagesCommand = cli.Command{ - Name: "prune", - Usage: "Remove unused images", - Description: pruneImagesDescription, - Action: pruneImagesCmd, - OnUsageError: usageErrorHandler, - Flags: pruneImageFlags, - } ) -func pruneImagesCmd(c *cli.Context) error { - runtime, err := adapter.GetRuntime(c) +func init() { + pruneImagesCommand.Command = _pruneImagesCommand + flags := pruneImagesCommand.Flags() + flags.BoolVarP(&pruneImagesCommand.All, "all", "a", false, "Remove all unused images, not just dangling ones") +} + +func pruneImagesCmd(c *cliconfig.PruneImagesValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -39,7 +43,7 @@ func pruneImagesCmd(c *cli.Context) error { // Call prune; if any cids are returned, print them and then // return err in case an error also came up - pruneCids, err := runtime.PruneImages(c.Bool("all")) + pruneCids, err := runtime.PruneImages(c.All) if len(pruneCids) > 0 { for _, cid := range pruneCids { fmt.Println(cid) diff --git a/cmd/podman/import.go b/cmd/podman/import.go index 661bd5a65..52d144eb3 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -3,47 +3,44 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - importFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "change, c", - Usage: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR", - }, - cli.StringFlag{ - Name: "message, m", - Usage: "Set commit message for imported image", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output", - }, - } + importCommand cliconfig.ImportValues + importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz). Note remote tar balls can be specified, via web address. Optionally tag the image. You can specify the instructions using the --change option. ` - importCommand = cli.Command{ - Name: "import", - Usage: "Import a tarball to create a filesystem image", - Description: importDescription, - Flags: sortFlags(importFlags), - Action: importCmd, - ArgsUsage: "TARBALL [REFERENCE]", - OnUsageError: usageErrorHandler, + _importCommand = &cobra.Command{ + Use: "import", + Short: "Import a tarball to create a filesystem image", + Long: importDescription, + RunE: func(cmd *cobra.Command, args []string) error { + importCommand.InputArgs = args + importCommand.GlobalFlags = MainGlobalOpts + return importCmd(&importCommand) + }, + Example: "TARBALL [REFERENCE]", } ) -func importCmd(c *cli.Context) error { - if err := validateFlags(c, importFlags); err != nil { - return err - } +func init() { + importCommand.Command = _importCommand + flags := importCommand.Flags() + flags.StringSliceVarP(&importCommand.Change, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") + flags.StringVarP(&importCommand.Message, "message", "m", "", "Set commit message for imported image") + flags.BoolVarP(&importCommand.Quiet, "quiet", "q", false, "Suppress output") + + rootCmd.AddCommand(importCommand.Command) +} - runtime, err := adapter.GetRuntime(c) +func importCmd(c *cliconfig.ImportValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -54,7 +51,7 @@ func importCmd(c *cli.Context) error { reference string ) - args := c.Args() + args := c.InputArgs switch len(args) { case 0: return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin") @@ -71,7 +68,7 @@ func importCmd(c *cli.Context) error { return err } - quiet := c.Bool("quiet") + quiet := c.Quiet if runtime.Remote { quiet = false } diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 19078cea5..0896e16a4 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -4,45 +4,47 @@ import ( "fmt" rt "runtime" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/version" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + infoCommand cliconfig.InfoValues + infoDescription = "Display podman system information" - infoCommand = cli.Command{ - Name: "info", - Usage: infoDescription, - Description: `Information display here pertain to the host, current storage stats, and build of podman. Useful for the user and when reporting issues.`, - Flags: sortFlags(infoFlags), - Action: infoCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, - } - infoFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "debug, D", - Usage: "Display additional debug information", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Change the output format to JSON or a Go template", + _infoCommand = &cobra.Command{ + Use: "info", + Long: infoDescription, + Short: `Display information pertaining to the host, current storage stats, and build of podman. Useful for the user and when reporting issues.`, + RunE: func(cmd *cobra.Command, args []string) error { + infoCommand.InputArgs = args + infoCommand.GlobalFlags = MainGlobalOpts + return infoCmd(&infoCommand) }, + Example: "", } ) -func infoCmd(c *cli.Context) error { - if err := validateFlags(c, infoFlags); err != nil { - return err - } +func init() { + infoCommand.Command = _infoCommand + flags := infoCommand.Flags() + + flags.BoolVarP(&infoCommand.Debug, "debug", "D", false, "Display additional debug information") + flags.StringVarP(&infoCommand.Format, "format", "f", "", "Change the output format to JSON or a Go template") + + rootCmd.AddCommand(infoCommand.Command) +} + +func infoCmd(c *cliconfig.InfoValues) error { info := map[string]interface{}{} remoteClientInfo := map[string]interface{}{} - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -59,7 +61,7 @@ func infoCmd(c *cli.Context) error { infoArr = append(infoArr, libpod.InfoData{Type: "client", Data: remoteClientInfo}) } - if !runtime.Remote && c.Bool("debug") { + if !runtime.Remote && c.Debug { debugInfo := debugInfo(c) infoArr = append(infoArr, libpod.InfoData{Type: "debug", Data: debugInfo}) } @@ -69,7 +71,7 @@ func infoCmd(c *cli.Context) error { } var out formats.Writer - infoOutputFormat := c.String("format") + infoOutputFormat := c.Format switch infoOutputFormat { case formats.JSONString: out = formats.JSONStruct{Output: info} @@ -85,11 +87,11 @@ func infoCmd(c *cli.Context) error { } // top-level "debug" info -func debugInfo(c *cli.Context) map[string]interface{} { +func debugInfo(c *cliconfig.InfoValues) map[string]interface{} { info := map[string]interface{}{} info["compiler"] = rt.Compiler info["go version"] = rt.Version() - info["podman version"] = c.App.Version + info["podman version"] = version.Version version, _ := libpod.GetVersion() info["git commit"] = version.GitCommit return info diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 1346da9fb..b2979c6ae 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -5,13 +5,14 @@ import ( "encoding/json" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/adapter" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( @@ -21,38 +22,37 @@ const ( ) var ( - inspectFlags = []cli.Flag{ - cli.StringFlag{ - Name: "type, t", - Value: inspectAll, - Usage: "Return JSON for specified type, (e.g image, container or task)", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Change the output format to a Go template", - }, - cli.BoolFlag{ - Name: "size, s", - Usage: "Display total file size if the type is container", - }, - LatestFlag, - } + inspectCommand cliconfig.InspectValues + inspectDescription = "This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type." - inspectCommand = cli.Command{ - Name: "inspect", - Usage: "Display the configuration of a container or image", - Description: inspectDescription, - Flags: sortFlags(inspectFlags), - Action: inspectCmd, - ArgsUsage: "CONTAINER-OR-IMAGE [CONTAINER-OR-IMAGE]...", - OnUsageError: usageErrorHandler, + _inspectCommand = &cobra.Command{ + Use: "inspect", + Short: "Display the configuration of a container or image", + Long: inspectDescription, + RunE: func(cmd *cobra.Command, args []string) error { + inspectCommand.InputArgs = args + inspectCommand.GlobalFlags = MainGlobalOpts + return inspectCmd(&inspectCommand) + }, + Example: "CONTAINER-OR-IMAGE [CONTAINER-OR-IMAGE]...", } ) -func inspectCmd(c *cli.Context) error { - args := c.Args() - inspectType := c.String("type") - latestContainer := c.Bool("latest") +func init() { + inspectCommand.Command = _inspectCommand + flags := inspectCommand.Flags() + flags.StringVarP(&inspectCommand.TypeObject, "type", "t", inspectAll, "Return JSON for specified type, (e.g image, container or task)") + flags.StringVarP(&inspectCommand.Format, "format", "f", "", "Change the output format to a Go template") + flags.BoolVarP(&inspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of if the type is a container") + flags.BoolVarP(&inspectCommand.Size, "size", "s", false, "Display total file size if the type is container") + + rootCmd.AddCommand(inspectCommand.Command) +} + +func inspectCmd(c *cliconfig.InspectValues) error { + args := c.InputArgs + inspectType := c.TypeObject + latestContainer := c.Latest if len(args) == 0 && !latestContainer { return errors.Errorf("container or image name must be specified: podman inspect [options [...]] name") } @@ -60,11 +60,8 @@ func inspectCmd(c *cli.Context) error { if len(args) > 0 && latestContainer { return errors.Errorf("you cannot provide additional arguments with --latest") } - if err := validateFlags(c, inspectFlags); err != nil { - return err - } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -74,7 +71,7 @@ func inspectCmd(c *cli.Context) error { return errors.Errorf("the only recognized types are %q, %q, and %q", inspectTypeContainer, inspectTypeImage, inspectAll) } - outputFormat := c.String("format") + outputFormat := c.Format if strings.Contains(outputFormat, "{{.Id}}") { outputFormat = strings.Replace(outputFormat, "{{.Id}}", formats.IDString, -1) } @@ -87,7 +84,7 @@ func inspectCmd(c *cli.Context) error { inspectType = inspectTypeContainer } - inspectedObjects, iterateErr := iterateInput(getContext(), c, args, runtime, inspectType) + inspectedObjects, iterateErr := iterateInput(getContext(), c.Size, args, runtime, inspectType) if iterateErr != nil { return iterateErr } @@ -105,7 +102,7 @@ func inspectCmd(c *cli.Context) error { } // func iterateInput iterates the images|containers the user has requested and returns the inspect data and error -func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *adapter.LocalRuntime, inspectType string) ([]interface{}, error) { +func iterateInput(ctx context.Context, size bool, args []string, runtime *adapter.LocalRuntime, inspectType string) ([]interface{}, error) { var ( data interface{} inspectedItems []interface{} @@ -120,7 +117,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *a inspectError = errors.Wrapf(err, "error looking up container %q", input) break } - libpodInspectData, err := ctr.Inspect(c.Bool("size")) + libpodInspectData, err := ctr.Inspect(size) if err != nil { inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break @@ -160,7 +157,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *a break } } else { - libpodInspectData, err := ctr.Inspect(c.Bool("size")) + libpodInspectData, err := ctr.Inspect(size) if err != nil { inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index cfe4b4218..04c0fd531 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -4,6 +4,7 @@ import ( "fmt" "syscall" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -11,68 +12,66 @@ import ( "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - killFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Signal all running containers", - }, - cli.StringFlag{ - Name: "signal, s", - Usage: "Signal to send to the container", - Value: "KILL", - }, - LatestFlag, - } + killCommand cliconfig.KillValues + killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal." - killCommand = cli.Command{ - Name: "kill", - Usage: "Kill one or more running containers with a specific signal", - Description: killDescription, - Flags: sortFlags(killFlags), - Action: killCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _killCommand = &cobra.Command{ + Use: "kill", + Short: "Kill one or more running containers with a specific signal", + Long: killDescription, + RunE: func(cmd *cobra.Command, args []string) error { + killCommand.InputArgs = args + killCommand.GlobalFlags = MainGlobalOpts + return killCmd(&killCommand) + }, + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) +func init() { + killCommand.Command = _killCommand + flags := killCommand.Flags() + + flags.BoolVarP(&killCommand.All, "all", "a", false, "Signal all running containers") + flags.StringVarP(&killCommand.Signal, "signal", "s", "KILL", "Signal to send to the container") + flags.BoolVarP(&killCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + rootCmd.AddCommand(killCommand.Command) +} + // killCmd kills one or more containers with a signal -func killCmd(c *cli.Context) error { +func killCmd(c *cliconfig.KillValues) error { var ( killFuncs []shared.ParallelWorkerInput killSignal uint = uint(syscall.SIGTERM) ) - if err := checkAllAndLatest(c); err != nil { - return err - } - - if err := validateFlags(c, killFlags); err != nil { + if err := checkAllAndLatest(&c.PodmanCommand); err != nil { return err } rootless.SetSkipStorageSetup(true) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if c.String("signal") != "" { + if c.Signal != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err - sysSignal, err := signal.ParseSignal(c.String("signal")) + sysSignal, err := signal.ParseSignal(c.Signal) if err != nil { return err } killSignal = uint(sysSignal) } - containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { if len(containers) == 0 { return err @@ -94,7 +93,7 @@ func killCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("kill") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index dca2f5022..60acd1f5b 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -1,15 +1,15 @@ package libpodruntime import ( + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/urfave/cli" ) // GetRuntime generates a new libpod runtime configured by command line options -func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { +func GetRuntime(c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} storageOpts, volumePath, err := util.GetDefaultStoreOptions() @@ -17,29 +17,39 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { return nil, err } - if c.IsSet("uidmap") || c.IsSet("gidmap") || c.IsSet("subuidmap") || c.IsSet("subgidmap") { - mappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidmap"), c.String("subgidmap")) + uidmapFlag := c.Flags().Lookup("uidmap") + gidmapFlag := c.Flags().Lookup("gidmap") + subuidname := c.Flags().Lookup("subuidname") + subgidname := c.Flags().Lookup("subgidname") + if (uidmapFlag != nil && gidmapFlag != nil && subuidname != nil && subgidname != nil) && + (uidmapFlag.Changed || gidmapFlag.Changed || subuidname.Changed || subgidname.Changed) { + uidmapVal, _ := c.Flags().GetStringSlice("uidmap") + gidmapVal, _ := c.Flags().GetStringSlice("gidmap") + subuidVal, _ := c.Flags().GetString("subuidname") + subgidVal, _ := c.Flags().GetString("subgidname") + mappings, err := util.ParseIDMapping(uidmapVal, gidmapVal, subuidVal, subgidVal) if err != nil { return nil, err } storageOpts.UIDMap = mappings.UIDMap storageOpts.GIDMap = mappings.GIDMap + } - if c.GlobalIsSet("root") { - storageOpts.GraphRoot = c.GlobalString("root") + if c.Flags().Changed("root") { + storageOpts.GraphRoot = c.GlobalFlags.Root } - if c.GlobalIsSet("runroot") { - storageOpts.RunRoot = c.GlobalString("runroot") + if c.Flags().Changed("runroot") { + storageOpts.RunRoot = c.GlobalFlags.Runroot } if len(storageOpts.RunRoot) > 50 { return nil, errors.New("the specified runroot is longer than 50 characters") } - if c.GlobalIsSet("storage-driver") { - storageOpts.GraphDriverName = c.GlobalString("storage-driver") + if c.Flags().Changed("storage-driver") { + storageOpts.GraphDriverName = c.GlobalFlags.StorageDriver } - if c.GlobalIsSet("storage-opt") { - storageOpts.GraphDriverOptions = c.GlobalStringSlice("storage-opt") + if c.Flags().Changed("storage-opt") { + storageOpts.GraphDriverOptions = c.GlobalFlags.StorageOpts } options = append(options, libpod.WithStorageConfig(storageOpts)) @@ -47,23 +57,23 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { // TODO CLI flags for image config? // TODO CLI flag for signature policy? - if c.GlobalIsSet("namespace") { - options = append(options, libpod.WithNamespace(c.GlobalString("namespace"))) + if len(c.GlobalFlags.Namespace) > 0 { + options = append(options, libpod.WithNamespace(c.GlobalFlags.Namespace)) } - if c.GlobalIsSet("runtime") { - options = append(options, libpod.WithOCIRuntime(c.GlobalString("runtime"))) + if c.Flags().Changed("runtime") { + options = append(options, libpod.WithOCIRuntime(c.GlobalFlags.Runtime)) } - if c.GlobalIsSet("conmon") { - options = append(options, libpod.WithConmonPath(c.GlobalString("conmon"))) + if c.Flags().Changed("conmon") { + options = append(options, libpod.WithConmonPath(c.GlobalFlags.ConmonPath)) } - if c.GlobalIsSet("tmpdir") { - options = append(options, libpod.WithTmpDir(c.GlobalString("tmpdir"))) + if c.Flags().Changed("tmpdir") { + options = append(options, libpod.WithTmpDir(c.GlobalFlags.TmpDir)) } - if c.GlobalIsSet("cgroup-manager") { - options = append(options, libpod.WithCgroupManager(c.GlobalString("cgroup-manager"))) + if c.Flags().Changed("cgroup-manager") { + options = append(options, libpod.WithCgroupManager(c.GlobalFlags.CGroupManager)) } else { if rootless.IsRootless() { options = append(options, libpod.WithCgroupManager("cgroupfs")) @@ -73,29 +83,37 @@ func GetRuntime(c *cli.Context) (*libpod.Runtime, error) { // TODO flag to set libpod static dir? // TODO flag to set libpod tmp dir? - if c.GlobalIsSet("cni-config-dir") { - options = append(options, libpod.WithCNIConfigDir(c.GlobalString("cni-config-dir"))) + if c.Flags().Changed("cni-config-dir") { + options = append(options, libpod.WithCNIConfigDir(c.GlobalFlags.CniConfigDir)) } - if c.GlobalIsSet("default-mounts-file") { - options = append(options, libpod.WithDefaultMountsFile(c.GlobalString("default-mounts-file"))) + if c.Flags().Changed("default-mounts-file") { + options = append(options, libpod.WithDefaultMountsFile(c.GlobalFlags.DefaultMountsFile)) } - if c.GlobalIsSet("hooks-dir") { - options = append(options, libpod.WithHooksDir(c.GlobalStringSlice("hooks-dir")...)) + if c.Flags().Changed("hooks-dir") { + options = append(options, libpod.WithHooksDir(c.GlobalFlags.HooksDir...)) } // TODO flag to set CNI plugins dir? + // TODO I dont think these belong here? + // Will follow up with a different PR to address + // // Pod create options - if c.IsSet("infra-image") { - options = append(options, libpod.WithDefaultInfraImage(c.String("infra-image"))) + + infraImageFlag := c.Flags().Lookup("infra-image") + if infraImageFlag != nil && infraImageFlag.Changed { + infraImage, _ := c.Flags().GetString("infra-image") + options = append(options, libpod.WithDefaultInfraImage(infraImage)) } - if c.IsSet("infra-command") { - options = append(options, libpod.WithDefaultInfraCommand(c.String("infra-command"))) + infraCommandFlag := c.Flags().Lookup("infra-command") + if infraCommandFlag != nil && infraImageFlag.Changed { + infraCommand, _ := c.Flags().GetString("infra-command") + options = append(options, libpod.WithDefaultInfraCommand(infraCommand)) } options = append(options, libpod.WithVolumePath(volumePath)) - if c.IsSet("config") { - return libpod.NewRuntimeFromConfig(c.String("config"), options...) + if c.Flags().Changed("config") { + return libpod.NewRuntimeFromConfig(c.GlobalFlags.Config, options...) } return libpod.NewRuntime(options...) } diff --git a/cmd/podman/load.go b/cmd/podman/load.go index f39ee4487..90268ca8f 100644 --- a/cmd/podman/load.go +++ b/cmd/podman/load.go @@ -9,45 +9,44 @@ import ( "github.com/containers/image/directory" dockerarchive "github.com/containers/image/docker/archive" ociarchive "github.com/containers/image/oci/archive" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - loadFlags = []cli.Flag{ - cli.StringFlag{ - Name: "input, i", - Usage: "Read from archive file, default is STDIN", - Value: "/dev/stdin", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress the output", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - }, - } + loadCommand cliconfig.LoadValues + loadDescription = "Loads the image from docker-archive stored on the local machine." - loadCommand = cli.Command{ - Name: "load", - Usage: "Load an image from docker archive", - Description: loadDescription, - Flags: sortFlags(loadFlags), - Action: loadCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + _loadCommand = &cobra.Command{ + Use: "load", + Short: "Load an image from docker archive", + Long: loadDescription, + RunE: func(cmd *cobra.Command, args []string) error { + loadCommand.InputArgs = args + loadCommand.GlobalFlags = MainGlobalOpts + return loadCmd(&loadCommand) + }, } ) +func init() { + loadCommand.Command = _loadCommand + flags := loadCommand.Flags() + flags.StringVarP(&loadCommand.Input, "input", "i", "/dev/stdin", "Read from archive file, default is STDIN") + flags.BoolVarP(&loadCommand.Quiet, "quiet", "q", false, "Suppress the output") + flags.StringVar(&loadCommand.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file (not usually used)") + + rootCmd.AddCommand(loadCommand.Command) +} + // 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 { +func loadCmd(c *cliconfig.LoadValues) error { - args := c.Args() + args := c.InputArgs var imageName string if len(args) == 1 { @@ -56,17 +55,14 @@ func loadCmd(c *cli.Context) error { if len(args) > 1 { return errors.New("too many arguments. Requires exactly 1") } - if err := validateFlags(c, loadFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - input := c.String("input") + input := c.Input if input == "/dev/stdin" { fi, err := os.Stdin.Stat() @@ -101,7 +97,7 @@ func loadCmd(c *cli.Context) error { } var writer io.Writer - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } @@ -110,18 +106,18 @@ func loadCmd(c *cli.Context) error { var newImages []*image.Image src, err := dockerarchive.ParseReference(input) // FIXME? We should add dockerarchive.NewReference() if err == nil { - newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer) + newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.SignaturePolicy, writer) } if err != nil { // generate full src name with specified image:tag src, err := ociarchive.NewReference(input, imageName) // imageName may be "" if err == nil { - newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer) + newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.SignaturePolicy, writer) } if err != nil { src, err := directory.NewReference(input) if err == nil { - newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.String("signature-policy"), writer) + newImages, err = runtime.ImageRuntime().LoadFromArchiveReference(ctx, src, c.SignaturePolicy, writer) } if err != nil { return errors.Wrapf(err, "error pulling %q", input) diff --git a/cmd/podman/login.go b/cmd/podman/login.go index fc7b39ed8..5ab0713e5 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -9,55 +9,48 @@ import ( "github.com/containers/image/docker" "github.com/containers/image/pkg/docker/config" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/common" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" ) var ( - loginFlags = []cli.Flag{ - cli.StringFlag{ - Name: "password, p", - Usage: "Password for registry", - }, - cli.StringFlag{ - Name: "username, u", - Usage: "Username for registry", - }, - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "Pathname of a directory containing TLS certificates and keys used to connect to the registry", - }, - cli.BoolTFlag{ - Name: "get-login", - Usage: "Return the current login user for the registry", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } + loginCommand cliconfig.LoginValues + loginDescription = "Login to a container registry on a specified server." - loginCommand = cli.Command{ - Name: "login", - Usage: "Login to a container registry", - Description: loginDescription, - Flags: sortFlags(loginFlags), - Action: loginCmd, - ArgsUsage: "REGISTRY", - OnUsageError: usageErrorHandler, + _loginCommand = &cobra.Command{ + Use: "login", + Short: "Login to a container registry", + Long: loginDescription, + RunE: func(cmd *cobra.Command, args []string) error { + loginCommand.InputArgs = args + loginCommand.GlobalFlags = MainGlobalOpts + return loginCmd(&loginCommand) + }, + Example: "REGISTRY", } ) +func init() { + loginCommand.Command = _loginCommand + flags := loginCommand.Flags() + + flags.StringVar(&loginCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&loginCommand.CertDir, "cert-dir", "", "Pathname of a directory containing TLS certificates and keys used to connect to the registry") + flags.BoolVar(&loginCommand.GetLogin, "get-login", true, "Return the current login user for the registry") + flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry") + flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry") + + rootCmd.AddCommand(loginCommand.Command) +} + // loginCmd uses the authentication package to store a user's authenticated credentials // in an auth.json file for future use -func loginCmd(c *cli.Context) error { - args := c.Args() +func loginCmd(c *cliconfig.LoginValues) error { + args := c.InputArgs if len(args) > 1 { return errors.Errorf("too many arguments, login takes only 1 argument") } @@ -65,17 +58,17 @@ func loginCmd(c *cli.Context) error { return errors.Errorf("please specify a registry to login to") } server := registryFromFullName(scrubServer(args[0])) - authfile := getAuthFile(c.String("authfile")) + authfile := getAuthFile(c.Authfile) sc := common.GetSystemContext("", authfile, false) - if c.IsSet("tls-verify") { - sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + sc.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } - if c.String("cert-dir") != "" { - sc.DockerCertPath = c.String("cert-dir") + if c.CertDir != "" { + sc.DockerCertPath = c.CertDir } - if c.IsSet("get-login") { + if c.Flag("get-login").Changed { user, err := config.GetUserLoggedIn(sc, server) if err != nil { @@ -98,7 +91,7 @@ func loginCmd(c *cli.Context) error { ctx := getContext() // If no username and no password is specified, try to use existing ones. - if c.String("username") == "" && c.String("password") == "" { + if c.Username == "" && c.Password == "" { fmt.Println("Authenticating with existing credentials...") if err := docker.CheckAuth(ctx, sc, userFromAuthFile, passFromAuthFile, server); err == nil { fmt.Println("Existing credentials are valid. Already logged in to", server) @@ -107,7 +100,7 @@ func loginCmd(c *cli.Context) error { fmt.Println("Existing credentials are invalid, please enter valid username and password") } - username, password, err := getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile) + username, password, err := getUserAndPass(c.Username, c.Password, userFromAuthFile) if err != nil { return errors.Wrapf(err, "error getting username and password") } diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 3cdb606b5..02bde7857 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -4,53 +4,56 @@ import ( "fmt" "github.com/containers/image/pkg/docker/config" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/common" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - logoutFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove the cached credentials for all registries in the auth file", - }, - } + logoutCommand cliconfig.LogoutValues logoutDescription = "Remove the cached username and password for the registry." - logoutCommand = cli.Command{ - Name: "logout", - Usage: "Logout of a container registry", - Description: logoutDescription, - Flags: sortFlags(logoutFlags), - Action: logoutCmd, - ArgsUsage: "REGISTRY", - OnUsageError: usageErrorHandler, + _logoutCommand = &cobra.Command{ + Use: "logout", + Short: "Logout of a container registry", + Long: logoutDescription, + RunE: func(cmd *cobra.Command, args []string) error { + logoutCommand.InputArgs = args + logoutCommand.GlobalFlags = MainGlobalOpts + return logoutCmd(&logoutCommand) + }, + Example: "REGISTRY", } ) +func init() { + logoutCommand.Command = _logoutCommand + flags := logoutCommand.Flags() + flags.BoolVarP(&logoutCommand.All, "all", "a", false, "Remove the cached credentials for all registries in the auth file") + flags.StringVar(&logoutCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + + rootCmd.AddCommand(logoutCommand.Command) +} + // logoutCmd uses the authentication package to remove the authenticated of a registry // stored in the auth.json file -func logoutCmd(c *cli.Context) error { - args := c.Args() +func logoutCmd(c *cliconfig.LogoutValues) error { + args := c.InputArgs if len(args) > 1 { return errors.Errorf("too many arguments, logout takes at most 1 argument") } - if len(args) == 0 && !c.IsSet("all") { + if len(args) == 0 && !c.All { return errors.Errorf("registry must be given") } var server string if len(args) == 1 { server = scrubServer(args[0]) } - authfile := getAuthFile(c.String("authfile")) + authfile := getAuthFile(c.Authfile) sc := common.GetSystemContext("", authfile, false) - if c.Bool("all") { + if c.All { if err := config.RemoveAllAuthentication(sc); err != nil { return err } diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index 75947c34e..83f900e63 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -4,91 +4,82 @@ import ( "os" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/logs" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - logsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "details", - Usage: "Show extra details provided to the logs", - Hidden: true, - }, - cli.BoolFlag{ - Name: "follow, f", - Usage: "Follow log output. The default is false", - }, - cli.StringFlag{ - Name: "since", - Usage: "Show logs since TIMESTAMP", - }, - cli.Uint64Flag{ - Name: "tail", - Usage: "Output the specified number of LINES at the end of the logs. Defaults to 0, which prints all lines", - }, - cli.BoolFlag{ - Name: "timestamps, t", - Usage: "Output the timestamps in the log", - }, - LatestFlag, - } + logsCommand cliconfig.LogsValues logsDescription = "The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution" + "order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs" - logsCommand = cli.Command{ - Name: "logs", - Usage: "Fetch the logs of a container", - Description: logsDescription, - Flags: sortFlags(logsFlags), - Action: logsCmd, - ArgsUsage: "CONTAINER", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, - UseShortOptionHandling: true, + _logsCommand = &cobra.Command{ + Use: "logs", + Short: "Fetch the logs of a container", + Long: logsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + logsCommand.InputArgs = args + logsCommand.GlobalFlags = MainGlobalOpts + return logsCmd(&logsCommand) + }, + Example: "CONTAINER", } ) -func logsCmd(c *cli.Context) error { +func init() { + logsCommand.Command = _logsCommand + flags := logsCommand.Flags() + flags.BoolVar(&logsCommand.Details, "details", false, "Show extra details provided to the logs") + flags.BoolVarP(&logsCommand.Follow, "follow", "f", false, "Follow log output. The default is false") + flags.BoolVarP(&waitCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVar(&logsCommand.Since, "since", "", "Show logs since TIMESTAMP") + flags.Uint64Var(&logsCommand.Tail, "tail", 0, "Output the specified number of LINES at the end of the logs. Defaults to 0, which prints all lines") + flags.BoolVarP(&logsCommand.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") + flags.MarkHidden("details") + + flags.SetInterspersed(false) + + rootCmd.AddCommand(logsCommand.Command) +} + +func logsCmd(c *cliconfig.LogsValues) error { var ctr *libpod.Container var err error - if err := validateFlags(c, logsFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() - if len(args) != 1 && !c.Bool("latest") { + args := c.InputArgs + if len(args) != 1 && !c.Latest { return errors.Errorf("'podman logs' requires exactly one container name/ID") } sinceTime := time.Time{} - if c.IsSet("since") { + if c.Flag("since").Changed { // parse time, error out if something is wrong - since, err := parseInputTime(c.String("since")) + since, err := parseInputTime(c.Since) if err != nil { - return errors.Wrapf(err, "could not parse time: %q", c.String("since")) + return errors.Wrapf(err, "could not parse time: %q", c.Since) } sinceTime = since } opts := &logs.LogOptions{ - Details: c.Bool("details"), - Follow: c.Bool("follow"), + Details: c.Details, + Follow: c.Follow, Since: sinceTime, - Tail: c.Uint64("tail"), - Timestamps: c.Bool("timestamps"), + Tail: c.Tail, + Timestamps: c.Timestamps, } - if c.Bool("latest") { + if c.Latest { ctr, err = runtime.GetLatestContainer() } else { ctr, err = runtime.LookupContainer(args[0]) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 8c08b2bfb..32ecaede7 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -6,9 +6,10 @@ import ( "os" "os/exec" "runtime/pprof" - "sort" + "strings" "syscall" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" _ "github.com/containers/libpod/pkg/hooks/0.1.0" "github.com/containers/libpod/pkg/rootless" @@ -17,7 +18,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" lsyslog "github.com/sirupsen/logrus/hooks/syslog" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) // This is populated by the Makefile from the VERSION file @@ -26,198 +27,159 @@ var ( exitCode = 125 ) -var cmdsNotRequiringRootless = map[string]bool{ - "help": true, - "version": true, - "create": true, - "exec": true, - "export": true, - // `info` must be executed in an user namespace. - // If this change, please also update libpod.refreshRootless() - "login": true, - "logout": true, - "mount": true, - "kill": true, - "pause": true, - "restart": true, - "run": true, - "unpause": true, - "search": true, - "stats": true, - "stop": true, - "top": true, +var cmdsNotRequiringRootless = map[*cobra.Command]bool{ + _versionCommand: true, + _createCommand: true, + _execCommand: true, + _exportCommand: true, + //// `info` must be executed in an user namespace. + //// If this change, please also update libpod.refreshRootless() + _loginCommand: true, + _logoutCommand: true, + _mountCommand: true, + _killCommand: true, + _pauseCommand: true, + _restartCommand: true, + _runCommmand: true, + _unpauseCommand: true, + _searchCommand: true, + _statsCommand: true, + _stopCommand: true, + _topCommand: true, } -type commandSorted []cli.Command - -func (a commandSorted) Len() int { return len(a) } -func (a commandSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type commandSortedAlpha struct{ commandSorted } - -func (a commandSortedAlpha) Less(i, j int) bool { - return a.commandSorted[i].Name < a.commandSorted[j].Name +var rootCmd = &cobra.Command{ + Use: "podman", + Long: "manage pods and images", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return before(cmd, args) + }, + PersistentPostRunE: func(cmd *cobra.Command, args []string) error { + return after(cmd, args) + }, + SilenceUsage: true, + SilenceErrors: true, } -type flagSorted []cli.Flag - -func (a flagSorted) Len() int { return len(a) } -func (a flagSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +var MainGlobalOpts cliconfig.MainFlags + +func init() { + cobra.OnInitialize(initConfig) + rootCmd.TraverseChildren = true + rootCmd.Version = version.Version + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CGroupManager, "cgroup-manager", "", "Cgroup manager to use (cgroupfs or systemd, default systemd)") + // -c is deprecated due to conflict with -c on subcommands + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Config, "config", "", "Path of a libpod config file detailing container server configuration options") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.ConmonPath, "conmon", "", "Path of the conmon binary") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CniConfigDir, "cni-config-dir", "", "Path of the configuration directory for CNI networks") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.DefaultMountsFile, "default-mounts-file", "", "Path to default mounts file") + rootCmd.PersistentFlags().MarkHidden("defaults-mount-file") + rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.HooksDir, "hooks-dir", []string{}, "Set the OCI hooks directory path (may be set multiple times)") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.LogLevel, "log-level", "error", "Log messages above specified level: debug, info, warn, error (default), fatal or panic") + rootCmd.PersistentFlags().IntVar(&MainGlobalOpts.MaxWorks, "max-workers", 0, "The maximum number of workers for parallel operations") + rootCmd.PersistentFlags().MarkHidden("max-workers") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Namespace, "namespace", "", "Set the libpod namespace, used to create separate views of the containers and pods on the system") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Root, "root", "", "Path to the root directory in which data, including images, is stored") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Runroot, "runroot", "", "Path to the 'run directory' where all state information is stored") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Runtime, "runtime", "", "Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc") + // -s is depracated due to conflict with -s on subcommands + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.StorageDriver, "storage-driver", "", "Select which storage driver is used to manage storage of images and containers (default is overlay)") + rootCmd.PersistentFlags().StringSliceVar(&MainGlobalOpts.StorageOpts, "storage-opt", []string{}, "Used to pass an option to the storage driver") + rootCmd.PersistentFlags().BoolVar(&MainGlobalOpts.Syslog, "syslog", false, "Output logging information to syslog as well as the console") + + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.TmpDir, "tmpdir", "", "Path to the tmp directory") -type flagSortedAlpha struct{ flagSorted } - -func (a flagSortedAlpha) Less(i, j int) bool { - return a.flagSorted[i].GetName() < a.flagSorted[j].GetName() +} +func initConfig() { + // we can do more stuff in here. } -func main() { - debug := false - cpuProfile := false - - if reexec.Init() { - return - } - - app := cli.NewApp() - app.Name = "podman" - app.Usage = "manage pods and images" - app.OnUsageError = usageErrorHandler - app.CommandNotFound = commandNotFoundHandler - - app.Version = version.Version - - app.Commands = []cli.Command{ - containerCommand, - exportCommand, - historyCommand, - imageCommand, - imagesCommand, - importCommand, - infoCommand, - inspectCommand, - pullCommand, - rmiCommand, - systemCommand, - tagCommand, - versionCommand, - } - - app.Commands = append(app.Commands, getAppCommands()...) - sort.Sort(commandSortedAlpha{app.Commands}) - - if varlinkCommand != nil { - app.Commands = append(app.Commands, *varlinkCommand) +func before(cmd *cobra.Command, args []string) error { + if err := libpod.SetXdgRuntimeDir(""); err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) } - - app.Before = func(c *cli.Context) error { - if err := libpod.SetXdgRuntimeDir(""); err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - args := c.Args() - if args.Present() && rootless.IsRootless() { - if _, notRequireRootless := cmdsNotRequiringRootless[args.First()]; !notRequireRootless { - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - logrus.Errorf(err.Error()) - os.Exit(1) - } - if became { - os.Exit(ret) - } + if rootless.IsRootless() { + notRequireRootless := cmdsNotRequiringRootless[cmd] + if !notRequireRootless && !strings.HasPrefix(cmd.Use, "help") { + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + logrus.Errorf(err.Error()) + os.Exit(1) } - } - if c.GlobalBool("syslog") { - hook, err := lsyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") - if err == nil { - logrus.AddHook(hook) + if became { + os.Exit(ret) } } - logLevel := c.GlobalString("log-level") - if logLevel != "" { - level, err := logrus.ParseLevel(logLevel) - if err != nil { - return err - } + } - logrus.SetLevel(level) + if MainGlobalOpts.Syslog { + hook, err := lsyslog.NewSyslogHook("", "", syslog.LOG_INFO, "") + if err == nil { + logrus.AddHook(hook) } + } - rlimits := new(syscall.Rlimit) - rlimits.Cur = 1048576 - rlimits.Max = 1048576 - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error getting rlimits") - } - rlimits.Cur = rlimits.Max - if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { - return errors.Wrapf(err, "error setting new rlimits") - } + // Set log level + level, err := logrus.ParseLevel(MainGlobalOpts.LogLevel) + if err != nil { + return err + } + logrus.SetLevel(level) + + rlimits := new(syscall.Rlimit) + rlimits.Cur = 1048576 + rlimits.Max = 1048576 + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error getting rlimits") } - - if rootless.IsRootless() { - logrus.Info("running as rootless") + rlimits.Cur = rlimits.Max + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, rlimits); err != nil { + return errors.Wrapf(err, "error setting new rlimits") } + } - // Be sure we can create directories with 0755 mode. - syscall.Umask(0022) + if rootless.IsRootless() { + logrus.Info("running as rootless") + } - if logLevel == "debug" { - debug = true + // Be sure we can create directories with 0755 mode. + syscall.Umask(0022) + if cmd.Flag("cpu-profile").Changed { + f, err := os.Create(MainGlobalOpts.CpuProfile) + if err != nil { + return errors.Wrapf(err, "unable to create cpu profiling file %s", + MainGlobalOpts.CpuProfile) } - if c.GlobalIsSet("cpu-profile") { - f, err := os.Create(c.GlobalString("cpu-profile")) - if err != nil { - return errors.Wrapf(err, "unable to create cpu profiling file %s", - c.GlobalString("cpu-profile")) - } - cpuProfile = true - pprof.StartCPUProfile(f) - } - return nil - } - app.After = func(*cli.Context) error { - // called by Run() when the command handler succeeds - if cpuProfile { - pprof.StopCPUProfile() - } - return nil - } - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Path of a libpod config file detailing container server configuration options", - Hidden: true, - }, - cli.StringFlag{ - Name: "cpu-profile", - Usage: "Path for the cpu profiling results", - }, - cli.StringFlag{ - Name: "log-level", - Usage: "Log messages above specified level: debug, info, warn, error (default), fatal or panic", - Value: "error", - }, - cli.StringFlag{ - Name: "tmpdir", - Usage: "Path to the tmp directory", - }, + pprof.StartCPUProfile(f) } + return nil +} - app.Flags = append(app.Flags, getMainAppFlags()...) - sort.Sort(flagSortedAlpha{app.Flags}) +func after(cmd *cobra.Command, args []string) error { + if cmd.Flag("cpu-profile").Changed { + pprof.StopCPUProfile() + } + return nil +} - // Check if /etc/containers/registries.conf exists when running in - // in a local environment. - CheckForRegistries() +func main() { + //debug := false + //cpuProfile := false - if err := app.Run(os.Args); err != nil { - if debug { + if reexec.Init() { + return + } + if err := rootCmd.Execute(); err != nil { + if MainGlobalOpts.LogLevel == "debug" { logrus.Errorf(err.Error()) } else { - // Retrieve the exit error from the exec call, if it exists if ee, ok := err.(*exec.ExitError); ok { if status, ok := ee.Sys().(syscall.WaitStatus); ok { exitCode = status.ExitStatus() @@ -233,6 +195,11 @@ func main() { if exitCode == 125 { exitCode = 0 } + } + + // Check if /etc/containers/registries.conf exists when running in + // in a local environment. + CheckForRegistries() os.Exit(exitCode) } diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index f71d47434..36ca8bcfb 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -5,15 +5,18 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" of "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + mountCommand cliconfig.MountValues + mountDescription = ` podman mount Lists all mounted containers mount points @@ -22,32 +25,29 @@ var ( Mounts the specified container and outputs the mountpoint ` - mountFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Mount all containers", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to Go template", + _mountCommand = &cobra.Command{ + Use: "mount", + Short: "Mount a working container's root filesystem", + Long: mountDescription, + RunE: func(cmd *cobra.Command, args []string) error { + mountCommand.InputArgs = args + mountCommand.GlobalFlags = MainGlobalOpts + return mountCmd(&mountCommand) }, - cli.BoolFlag{ - Name: "notruncate", - Usage: "Do not truncate output", - }, - LatestFlag, - } - mountCommand = cli.Command{ - Name: "mount", - Usage: "Mount a working container's root filesystem", - Description: mountDescription, - Action: mountCmd, - ArgsUsage: "[CONTAINER-NAME-OR-ID [...]]", - Flags: sortFlags(mountFlags), - OnUsageError: usageErrorHandler, } ) +func init() { + mountCommand.Command = _mountCommand + flags := mountCommand.Flags() + flags.BoolVarP(&mountCommand.All, "all", "a", false, "Mount all containers") + flags.StringVar(&mountCommand.Format, "format", "", "Change the output format to Go template") + flags.BoolVarP(&mountCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&mountCommand.NoTrunc, "notruncate", false, "Do not truncate output") + + rootCmd.AddCommand(mountCommand.Command) +} + // jsonMountPoint stores info about each container type jsonMountPoint struct { ID string `json:"id"` @@ -55,15 +55,12 @@ type jsonMountPoint struct { MountPoint string `json:"mountpoint"` } -func mountCmd(c *cli.Context) error { - if err := validateFlags(c, mountFlags); err != nil { - return err - } +func mountCmd(c *cliconfig.MountValues) error { if os.Geteuid() != 0 { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -85,11 +82,11 @@ func mountCmd(c *cli.Context) error { } } - if c.Bool("all") && c.Bool("latest") { + if c.All && c.Latest { return errors.Errorf("--all and --latest cannot be used together") } - mountContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + mountContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { if len(mountContainers) == 0 { return err @@ -102,9 +99,9 @@ func mountCmd(c *cli.Context) error { of.JSONString: true, } - json := c.String("format") == of.JSONString - if !formats[c.String("format")] { - return errors.Errorf("%q is not a supported format", c.String("format")) + json := c.Format == of.JSONString + if !formats[c.Format] { + return errors.Errorf("%q is not a supported format", c.Format) } var lastError error @@ -149,7 +146,7 @@ func mountCmd(c *cli.Context) error { continue } - if c.Bool("notruncate") { + if c.NoTrunc { fmt.Printf("%-64s %s\n", container.ID(), mountPoint) } else { fmt.Printf("%-12.12s %s\n", container.ID(), mountPoint) diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 2e7182871..466a3b136 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -3,38 +3,44 @@ package main import ( "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - pauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Pause all running containers", - }, - } + pauseCommand cliconfig.PauseValues pauseDescription = ` podman pause Pauses one or more running containers. The container name or ID can be used. ` - pauseCommand = cli.Command{ - Name: "pause", - Usage: "Pause all the processes in one or more containers", - Description: pauseDescription, - Flags: pauseFlags, - Action: pauseCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _pauseCommand = &cobra.Command{ + Use: "pause", + Short: "Pause all the processes in one or more containers", + Long: pauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pauseCommand.InputArgs = args + pauseCommand.GlobalFlags = MainGlobalOpts + return pauseCmd(&pauseCommand) + }, + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func pauseCmd(c *cli.Context) error { +func init() { + pauseCommand.Command = _pauseCommand + flags := pauseCommand.Flags() + flags.BoolVarP(&pauseCommand.All, "all", "a", false, "Pause all running containers") + + rootCmd.AddCommand(pauseCommand.Command) +} + +func pauseCmd(c *cliconfig.PauseValues) error { var ( pauseContainers []*libpod.Container pauseFuncs []shared.ParallelWorkerInput @@ -43,18 +49,18 @@ func pauseCmd(c *cli.Context) error { return errors.New("pause is not supported for rootless containers") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() - if len(args) < 1 && !c.Bool("all") { + args := c.InputArgs + if len(args) < 1 && !c.All { return errors.Errorf("you must provide at least one container name or id") } - if c.Bool("all") { - containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + if c.All { + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { return err } @@ -84,7 +90,7 @@ func pauseCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("pause") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/play.go b/cmd/podman/play.go index 4e09b2689..ff8320a6b 100644 --- a/cmd/podman/play.go +++ b/cmd/podman/play.go @@ -1,23 +1,23 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) -var ( - playSubCommands = []cli.Command{ - playKubeCommand, - } +var playCommand cliconfig.PodmanCommand - playDescription = "Play a pod and its containers from a structured file." - playCommand = cli.Command{ - Name: "play", - Usage: "Play a container or pod", - Description: playDescription, - ArgsUsage: "", - Subcommands: playSubCommands, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - Hidden: true, +func init() { + var playDescription = "Play a pod and its containers from a structured file." + playCommand.Command = &cobra.Command{ + Use: "play", + Short: "Play a pod", + Long: playDescription, } -) + +} + +func init() { + playCommand.AddCommand(getPlaySubCommands()...) + rootCmd.AddCommand(playCommand.Command) +} diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index 2d97e0e95..b314c1278 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -20,51 +21,40 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" "k8s.io/api/core/v1" ) var ( - playKubeFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`Pathname` of a directory containing TLS certificates and keys", - }, - cli.StringFlag{ - Name: "creds", - Usage: "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when pulling images", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`Pathname` of signature policy file (not usually used)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } + playKubeCommand cliconfig.KubePlayValues playKubeDescription = "Play a Pod and its containers based on a Kubrernetes YAML" - playKubeCommand = cli.Command{ - Name: "kube", - Usage: "Play a pod based on Kubernetes YAML", - Description: playKubeDescription, - Action: playKubeYAMLCmd, - Flags: sortFlags(playKubeFlags), - ArgsUsage: "Kubernetes YAML file", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _playKubeCommand = &cobra.Command{ + Use: "kube", + Short: "Play a pod based on Kubernetes YAML", + Long: playKubeDescription, + RunE: func(cmd *cobra.Command, args []string) error { + playKubeCommand.InputArgs = args + playKubeCommand.GlobalFlags = MainGlobalOpts + return playKubeYAMLCmd(&playKubeCommand) + }, + Example: "Kubernetes YAML file", } ) -func playKubeYAMLCmd(c *cli.Context) error { +func init() { + playKubeCommand.Command = _playKubeCommand + flags := playKubeCommand.Flags() + flags.StringVar(&playKubeCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&playKubeCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.StringVar(&playKubeCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.BoolVarP(&playKubeCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.StringVar(&playKubeCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + + rootCmd.AddCommand(playKubeCommand.Command) +} + +func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { var ( podOptions []libpod.PodCreateOption podYAML v1.Pod @@ -77,7 +67,7 @@ func playKubeYAMLCmd(c *cli.Context) error { if rootless.IsRootless() { return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") } - args := c.Args() + args := c.InputArgs if len(args) > 1 { return errors.New("you can only play one kubernetes file at a time") } @@ -85,7 +75,7 @@ func playKubeYAMLCmd(c *cli.Context) error { return errors.New("you must supply at least one file") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -133,20 +123,20 @@ func playKubeYAMLCmd(c *cli.Context) error { "ipc": fmt.Sprintf("container:%s", podInfraID), "uts": fmt.Sprintf("container:%s", podInfraID), } - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } dockerRegistryOptions := image2.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), + DockerCertPath: c.CertDir, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } for _, container := range podYAML.Spec.Containers { - newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.String("signature-policy"), c.String("authfile"), writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) + newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) if err != nil { return err } diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index a30361134..4104c39c7 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -1,35 +1,24 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( podDescription = `Manage container pods. -Pods are a group of one or more containers sharing the same network, pid and ipc namespaces. -` - podSubCommands = []cli.Command{ - podCreateCommand, - podExistsCommand, - podInspectCommand, - podKillCommand, - podPauseCommand, - podPsCommand, - podRestartCommand, - podRmCommand, - podStartCommand, - podStatsCommand, - podStopCommand, - podTopCommand, - podUnpauseCommand, - } - podCommand = cli.Command{ - Name: "pod", - Usage: "Manage pods", - Description: podDescription, - UseShortOptionHandling: true, - Subcommands: podSubCommands, - OnUsageError: usageErrorHandler, - } +Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.` ) +var podCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "pod", + Short: "Manage pods", + Long: podDescription, + }, +} + +func init() { + podCommand.AddCommand(getPodSubCommands()...) + rootCmd.AddCommand(podCommand.Command) +} diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 967ce7610..9ac5d94a9 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -5,111 +5,81 @@ import ( "os" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( // Kernel namespaces shared by default within a pod DefaultKernelNamespaces = "cgroup,ipc,net,uts" + podCreateCommand cliconfig.PodCreateValues + + podCreateDescription = "Creates a new empty pod. The pod ID is then" + + " printed to stdout. You can then start it at any time with the" + + " podman pod start <pod_id> command. The pod will be created with the" + + " initial state 'created'." + + _podCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new empty pod", + Long: podCreateDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podCreateCommand.InputArgs = args + podCreateCommand.GlobalFlags = MainGlobalOpts + return podCreateCmd(&podCreateCommand) + }, + } ) -var podCreateDescription = "Creates a new empty pod. The pod ID is then" + - " printed to stdout. You can then start it at any time with the" + - " podman pod start <pod_id> command. The pod will be created with the" + - " initial state 'created'." - -var podCreateFlags = []cli.Flag{ - cli.StringFlag{ - Name: "cgroup-parent", - Usage: "Set parent cgroup for the pod", - }, - cli.BoolTFlag{ - Name: "infra", - Usage: "Create an infra container associated with the pod to share namespaces with", - }, - cli.StringFlag{ - Name: "infra-image", - Usage: "The image of the infra container to associate with the pod", - Value: libpod.DefaultInfraImage, - }, - cli.StringFlag{ - Name: "infra-command", - Usage: "The command to run on the infra container when the pod is started", - Value: libpod.DefaultInfraCommand, - }, - cli.StringSliceFlag{ - Name: "label-file", - Usage: "Read in a line delimited file of labels (default [])", - }, - cli.StringSliceFlag{ - Name: "label, l", - Usage: "Set metadata on pod (default [])", - }, - cli.StringFlag{ - Name: "name, n", - Usage: "Assign a name to the pod", - }, - cli.StringFlag{ - Name: "pod-id-file", - Usage: "Write the pod ID to the file", - }, - cli.StringSliceFlag{ - Name: "publish, p", - Usage: "Publish a container's port, or a range of ports, to the host (default [])", - }, - cli.StringFlag{ - Name: "share", - Usage: "A comma delimited list of kernel namespaces the pod will share", - Value: DefaultKernelNamespaces, - }, -} +func init() { + podCreateCommand.Command = _podCreateCommand + flags := podCreateCommand.Flags() + flags.SetInterspersed(false) + + flags.StringVar(&podCreateCommand.CgroupParent, "cgroup-parent", "", "Set parent cgroup for the pod") + flags.BoolVar(&podCreateCommand.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") + flags.StringVar(&podCreateCommand.InfraImage, "infra-image", libpod.DefaultInfraImage, "The image of the infra container to associate with the pod") + flags.StringVar(&podCreateCommand.InfraCommand, "infra-command", libpod.DefaultInfraCommand, "The command to run on the infra container when the pod is started") + flags.StringSliceVar(&podCreateCommand.LabelFile, "label-file", []string{}, "Read in a line delimited file of labels") + flags.StringSliceVarP(&podCreateCommand.Labels, "label", "l", []string{}, "Set metadata on pod (default [])") + flags.StringVarP(&podCreateCommand.Name, "name", "n", "", "Assign a name to the pod") + flags.StringVar(&podCreateCommand.PodIDFile, "pod-id-file", "", "Write the pod ID to the file") + flags.StringSliceVarP(&podCreateCommand.Publish, "publish", "p", []string{}, "Publish a container's port, or a range of ports, to the host (default [])") + flags.StringVar(&podCreateCommand.Share, "share", DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") -var podCreateCommand = cli.Command{ - Name: "create", - Usage: "Create a new empty pod", - Description: podCreateDescription, - Flags: sortFlags(podCreateFlags), - Action: podCreateCmd, - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, } -func podCreateCmd(c *cli.Context) error { +func podCreateCmd(c *cliconfig.PodCreateValues) error { var options []libpod.PodCreateOption var err error - if err = validateFlags(c, createFlags); err != nil { - return err - } - - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) var podIdFile *os.File - if c.IsSet("pod-id-file") && os.Geteuid() == 0 { - podIdFile, err = libpod.OpenExclusiveFile(c.String("pod-id-file")) + if c.Flag("pod-id-file").Changed && os.Geteuid() == 0 { + podIdFile, err = libpod.OpenExclusiveFile(c.PodIDFile) if err != nil && os.IsExist(err) { - return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", c.String("pod-id-file")) + return errors.Errorf("pod id file exists. Ensure another pod is not using it or delete %s", c.PodIDFile) } if err != nil { - return errors.Errorf("error opening pod-id-file %s", c.String("pod-id-file")) + return errors.Errorf("error opening pod-id-file %s", c.PodIDFile) } defer podIdFile.Close() defer podIdFile.Sync() } - if len(c.StringSlice("publish")) > 0 { - if !c.BoolT("infra") { + if len(c.Publish) > 0 { + if !c.Infra { return errors.Errorf("you must have an infra container to publish port bindings to the host") } if rootless.IsRootless() { @@ -117,15 +87,15 @@ func podCreateCmd(c *cli.Context) error { } } - if !c.BoolT("infra") && c.IsSet("share") && c.String("share") != "none" && c.String("share") != "" { + if !c.Infra && c.Flag("share").Changed && c.Share != "none" && c.Share != "" { return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") } - if c.IsSet("cgroup-parent") { - options = append(options, libpod.WithPodCgroupParent(c.String("cgroup-parent"))) + if c.Flag("cgroup-parent").Changed { + options = append(options, libpod.WithPodCgroupParent(c.CgroupParent)) } - labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("label")) + labels, err := getAllLabels(c.LabelFile, c.Labels) if err != nil { return errors.Wrapf(err, "unable to process labels") } @@ -133,21 +103,21 @@ func podCreateCmd(c *cli.Context) error { options = append(options, libpod.WithPodLabels(labels)) } - if c.IsSet("name") { - options = append(options, libpod.WithPodName(c.String("name"))) + if c.Flag("name").Changed { + options = append(options, libpod.WithPodName(c.Name)) } - if c.BoolT("infra") { + if c.Infra { options = append(options, libpod.WithInfraContainer()) - nsOptions, err := shared.GetNamespaceOptions(strings.Split(c.String("share"), ",")) + nsOptions, err := shared.GetNamespaceOptions(strings.Split(c.Share, ",")) if err != nil { return err } options = append(options, nsOptions...) } - if len(c.StringSlice("publish")) > 0 { - portBindings, err := shared.CreatePortBindings(c.StringSlice("publish")) + if len(c.Publish) > 0 { + portBindings, err := shared.CreatePortBindings(c.Publish) if err != nil { return err } diff --git a/cmd/podman/pod_inspect.go b/cmd/podman/pod_inspect.go index c7bbf31cd..f4c357e96 100644 --- a/cmd/podman/pod_inspect.go +++ b/cmd/podman/pod_inspect.go @@ -2,46 +2,53 @@ package main import ( "encoding/json" - "fmt" + + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podInspectFlags = []cli.Flag{ - LatestPodFlag, - } + podInspectCommand cliconfig.PodInspectValues podInspectDescription = "Display the configuration for a pod by name or id" - podInspectCommand = cli.Command{ - Name: "inspect", - Usage: "Displays a pod configuration", - Description: podInspectDescription, - Flags: sortFlags(podInspectFlags), - Action: podInspectCmd, - UseShortOptionHandling: true, - ArgsUsage: "[POD_NAME_OR_ID]", - OnUsageError: usageErrorHandler, + _podInspectCommand = &cobra.Command{ + Use: "inspect", + Short: "Displays a pod configuration", + Long: podInspectDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podInspectCommand.InputArgs = args + podInspectCommand.GlobalFlags = MainGlobalOpts + return podInspectCmd(&podInspectCommand) + }, + Example: "[POD_NAME_OR_ID]", } ) -func podInspectCmd(c *cli.Context) error { +func init() { + podInspectCommand.Command = _podInspectCommand + flags := podInspectCommand.Flags() + flags.BoolVarP(&podInspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + +} + +func podInspectCmd(c *cliconfig.PodInspectValues) error { var ( pod *libpod.Pod ) - if err := checkMutuallyExclusiveFlags(c); err != nil { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - args := c.Args() - runtime, err := libpodruntime.GetRuntime(c) + args := c.InputArgs + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { pod, err = runtime.GetLatestPod() if err != nil { return errors.Wrapf(err, "unable to get latest pod") diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go index c8029eb46..43411a988 100644 --- a/cmd/podman/pod_kill.go +++ b/cmd/podman/pod_kill.go @@ -4,46 +4,45 @@ import ( "fmt" "syscall" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/docker/docker/pkg/signal" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podKillFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Kill all containers in all pods", - }, - cli.StringFlag{ - Name: "signal, s", - Usage: "Signal to send to the containers in the pod", - Value: "KILL", - }, - LatestPodFlag, - } + podKillCommand cliconfig.PodKillValues podKillDescription = "The main process of each container inside the specified pod will be sent SIGKILL, or any signal specified with option --signal." - podKillCommand = cli.Command{ - Name: "kill", - Usage: "Send the specified signal or SIGKILL to containers in pod", - Description: podKillDescription, - Flags: sortFlags(podKillFlags), - Action: podKillCmd, - ArgsUsage: "[POD_NAME_OR_ID]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podKillCommand = &cobra.Command{ + Use: "kill", + Short: "Send the specified signal or SIGKILL to containers in pod", + Long: podKillDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podKillCommand.InputArgs = args + podKillCommand.GlobalFlags = MainGlobalOpts + return podKillCmd(&podKillCommand) + }, + Example: "[POD_NAME_OR_ID]", } ) +func init() { + podKillCommand.Command = _podKillCommand + flags := podKillCommand.Flags() + flags.BoolVarP(&podKillCommand.All, "all", "a", false, "Kill all containers in all pods") + flags.BoolVarP(&podKillCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + flags.StringVarP(&podKillCommand.Signal, "signal", "s", "KILL", "Signal to send to the containers in the pod") +} + // podKillCmd kills one or more pods with a signal -func podKillCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { +func podKillCmd(c *cliconfig.PodKillValues) error { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -51,10 +50,10 @@ func podKillCmd(c *cli.Context) error { var killSignal uint = uint(syscall.SIGTERM) - if c.String("signal") != "" { + if c.Signal != "" { // Check if the signalString provided by the user is valid // Invalid signals will return err - sysSignal, err := signal.ParseSignal(c.String("signal")) + sysSignal, err := signal.ParseSignal(c.Signal) if err != nil { return err } @@ -64,7 +63,7 @@ func podKillCmd(c *cli.Context) error { // getPodsFromContext returns an error when a requested pod // isn't found. The only fatal error scenerio is when there are no pods // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) for _, pod := range pods { ctr_errs, err := pod.Kill(killSignal) diff --git a/cmd/podman/pod_pause.go b/cmd/podman/pod_pause.go index f29a0b8d1..a54783d88 100644 --- a/cmd/podman/pod_pause.go +++ b/cmd/podman/pod_pause.go @@ -2,43 +2,42 @@ package main import ( "fmt" - + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podPauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Pause all running pods", + podPauseCommand cliconfig.PodPauseValues + podPauseDescription = `Pauses one or more pods. The pod name or ID can be used.` + _podPauseCommand = &cobra.Command{ + Use: "pause", + Short: "Pause one or more pods", + Long: podPauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podPauseCommand.InputArgs = args + podPauseCommand.GlobalFlags = MainGlobalOpts + return podPauseCmd(&podPauseCommand) }, - LatestPodFlag, - } - podPauseDescription = ` - Pauses one or more pods. The pod name or ID can be used. -` - - podPauseCommand = cli.Command{ - Name: "pause", - Usage: "Pause one or more pods", - Description: podPauseDescription, - Flags: sortFlags(podPauseFlags), - Action: podPauseCmd, - ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Example: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", } ) -func podPauseCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { +func init() { + podPauseCommand.Command = _podPauseCommand + flags := podPauseCommand.Flags() + flags.BoolVarP(&podPauseCommand.All, "all", "a", false, "Pause all running pods") + flags.BoolVarP(&podPauseCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") +} + +func podPauseCmd(c *cliconfig.PodPauseValues) error { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -47,7 +46,7 @@ func podPauseCmd(c *cli.Context) error { // getPodsFromContext returns an error when a requested pod // isn't found. The only fatal error scenerio is when there are no pods // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) for _, pod := range pods { ctr_errs, err := pod.Pause() diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go index 2030b9b04..991e294bf 100644 --- a/cmd/podman/pod_ps.go +++ b/cmd/podman/pod_ps.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" @@ -15,7 +16,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/docker/go-units" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( @@ -112,95 +113,68 @@ func (a podPsSortedStatus) Less(i, j int) bool { } var ( - podPsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "ctr-names", - Usage: "Display the container names", - }, - cli.BoolFlag{ - Name: "ctr-ids", - Usage: "Display the container UUIDs. If no-trunc is not set they will be truncated", - }, - cli.BoolFlag{ - Name: "ctr-status", - Usage: "Display the container status", - }, - cli.StringFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions given", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print pods to JSON or using a Go template", - }, - cli.BoolFlag{ - Name: "latest, l", - Usage: "Show the latest pod created", - }, - cli.BoolFlag{ - Name: "namespace, ns", - Usage: "Display namespace information of the pod", - }, - cli.BoolFlag{ - Name: "no-trunc", - Usage: "Do not truncate pod and container IDs", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Print the numeric IDs of the pods only", - }, - cli.StringFlag{ - Name: "sort", - Usage: "Sort output by created, id, name, or number", - Value: "created", - }, - } + podPsCommand cliconfig.PodPsValues + podPsDescription = "List all pods on system including their names, ids and current state." - podPsCommand = cli.Command{ - Name: "ps", - Aliases: []string{"ls", "list"}, - Usage: "List pods", - Description: podPsDescription, - Flags: sortFlags(podPsFlags), - Action: podPsCmd, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podPsCommand = &cobra.Command{ + Use: "ps", + Aliases: []string{"ls", "list"}, + Short: "List pods", + Long: podPsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podPsCommand.InputArgs = args + podPsCommand.GlobalFlags = MainGlobalOpts + return podPsCmd(&podPsCommand) + }, } ) -func podPsCmd(c *cli.Context) error { - if err := validateFlags(c, podPsFlags); err != nil { - return err - } +func init() { + podPsCommand.Command = _podPsCommand + flags := podPsCommand.Flags() + flags.BoolVar(&podPsCommand.CtrNames, "ctr-names", false, "Display the container names") + flags.BoolVar(&podPsCommand.CtrIDs, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated") + flags.BoolVar(&podPsCommand.CtrStatus, "ctr-status", false, "Display the container status") + flags.StringVarP(&podPsCommand.Filter, "filter", "f", "", "Filter output based on conditions given") + flags.StringVar(&podPsCommand.Format, "format", "", "Pretty-print pods to JSON or using a Go template") + flags.BoolVarP(&podPsCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") + flags.BoolVar(&podPsCommand.Namespace, "namespace", false, "Display namespace information of the pod") + flags.BoolVar(&podPsCommand.Namespace, "ns", false, "Display namespace information of the pod") + flags.BoolVar(&podPsCommand.NoTrunc, "no-trunc", false, "Do not truncate pod and container IDs") + flags.BoolVarP(&podPsCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the pods only") + flags.StringVar(&podPsCommand.Sort, "sort", "created", "Sort output by created, id, name, or number") + +} +func podPsCmd(c *cliconfig.PodPsValues) error { if err := podPsCheckFlagsPassed(c); err != nil { return errors.Wrapf(err, "error with flags passed") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { return errors.Errorf("too many arguments, ps takes no arguments") } opts := podPsOptions{ - NoTrunc: c.Bool("no-trunc"), - Quiet: c.Bool("quiet"), - Sort: c.String("sort"), - IdsOfContainers: c.Bool("ctr-ids"), - NamesOfContainers: c.Bool("ctr-names"), - StatusOfContainers: c.Bool("ctr-status"), + NoTrunc: c.NoTrunc, + Quiet: c.Quiet, + Sort: c.Sort, + IdsOfContainers: c.CtrIDs, + NamesOfContainers: c.CtrNames, + StatusOfContainers: c.CtrStatus, } opts.Format = genPodPsFormat(c) var filterFuncs []libpod.PodFilter - if c.String("filter") != "" { - filters := strings.Split(c.String("filter"), ",") + if c.Filter != "" { + filters := strings.Split(c.Filter, ",") for _, f := range filters { filterSplit := strings.Split(f, "=") if len(filterSplit) < 2 { @@ -215,7 +189,7 @@ func podPsCmd(c *cli.Context) error { } var pods []*libpod.Pod - if c.IsSet("latest") { + if c.Latest { pod, err := runtime.GetLatestPod() if err != nil { return err @@ -244,13 +218,13 @@ func podPsCmd(c *cli.Context) error { } // podPsCheckFlagsPassed checks if mutually exclusive flags are passed together -func podPsCheckFlagsPassed(c *cli.Context) error { +func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error { // quiet, and format with Go template are mutually exclusive flags := 0 - if c.Bool("quiet") { + if c.Quiet { flags++ } - if c.IsSet("format") && c.String("format") != formats.JSONString { + if c.Flag("format").Changed && c.Format != formats.JSONString { flags++ } if flags > 1 { @@ -342,20 +316,20 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) } // generate the template based on conditions given -func genPodPsFormat(c *cli.Context) string { +func genPodPsFormat(c *cliconfig.PodPsValues) string { format := "" - if c.String("format") != "" { + if c.Format != "" { // "\t" from the command line is not being recognized as a tab // replacing the string "\t" to a tab character if the user passes in "\t" - format = strings.Replace(c.String("format"), `\t`, "\t", -1) - } else if c.Bool("quiet") { + format = strings.Replace(c.Format, `\t`, "\t", -1) + } else if c.Quiet { format = formats.IDString } else { format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}" if c.Bool("namespace") { format += "\t{{.Cgroup}}\t{{.Namespaces}}" } - if c.Bool("ctr-names") || c.Bool("ctr-ids") || c.Bool("ctr-status") { + if c.CtrNames || c.CtrIDs || c.CtrStatus { format += "\t{{.ContainerInfo}}" } else { format += "\t{{.NumberOfContainers}}" diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index d9800cbf7..4d05205ea 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -3,40 +3,43 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podRestartFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Restart all pods", - }, - LatestPodFlag, - } + podRestartCommand cliconfig.PodRestartValues podRestartDescription = `Restarts one or more pods. The pod ID or name can be used.` - - podRestartCommand = cli.Command{ - Name: "restart", - Usage: "Restart one or more pods", - Description: podRestartDescription, - Flags: sortFlags(podRestartFlags), - Action: podRestartCmd, - ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podRestartCommand = &cobra.Command{ + Use: "restart", + Short: "Restart one or more pods", + Long: podRestartDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podRestartCommand.InputArgs = args + podRestartCommand.GlobalFlags = MainGlobalOpts + return podRestartCmd(&podRestartCommand) + }, + Example: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", } ) -func podRestartCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { +func init() { + podRestartCommand.Command = _podRestartCommand + flags := podRestartCommand.Flags() + flags.BoolVarP(&podRestartCommand.All, "all", "a", false, "Restart all running pods") + flags.BoolVarP(&podRestartCommand.Latest, "latest", "l", false, "Restart the latest pod podman is aware of") + +} + +func podRestartCmd(c *cliconfig.PodRestartValues) error { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -45,7 +48,7 @@ func podRestartCmd(c *cli.Context) error { // getPodsFromContext returns an error when a requested pod // isn't found. The only fatal error scenerio is when there are no pods // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) ctx := getContext() for _, pod := range pods { diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go index 49f2104cf..445a641df 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -3,60 +3,61 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podRmFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all pods", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false", - }, - LatestPodFlag, - } + podRmCommand cliconfig.PodRmValues podRmDescription = fmt.Sprintf(` podman rm will remove one or more pods from the host. The pod name or ID can be used. A pod with containers will not be removed without --force. If --force is specified, all containers will be stopped, then removed. `) - podRmCommand = cli.Command{ - Name: "rm", - Usage: "Remove one or more pods", - Description: podRmDescription, - Flags: sortFlags(podRmFlags), - Action: podRmCmd, - ArgsUsage: "[POD ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podRmCommand = &cobra.Command{ + Use: "rm", + Short: "Remove one or more pods", + Long: podRmDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podRmCommand.InputArgs = args + podRmCommand.GlobalFlags = MainGlobalOpts + return podRmCmd(&podRmCommand) + }, + Example: "[POD ...]", } ) +func init() { + podRmCommand.Command = _podRmCommand + flags := podRmCommand.Flags() + flags.BoolVarP(&podRmCommand.All, "all", "a", false, "Remove all running pods") + flags.BoolVarP(&podRmCommand.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false") + flags.BoolVarP(&podRmCommand.Latest, "latest", "l", false, "Remove the latest pod podman is aware of") + +} + // saveCmd saves the image to either docker-archive or oci -func podRmCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { +func podRmCmd(c *cliconfig.PodRmValues) error { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) ctx := getContext() - force := c.Bool("force") + force := c.Force // getPodsFromContext returns an error when a requested pod // isn't found. The only fatal error scenerio is when there are no pods // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) for _, pod := range pods { err = runtime.RemovePod(ctx, pod, force, force) diff --git a/cmd/podman/pod_start.go b/cmd/podman/pod_start.go index 2178340a4..8b35d3e04 100644 --- a/cmd/podman/pod_start.go +++ b/cmd/podman/pod_start.go @@ -3,44 +3,46 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podStartFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Start all running pods", - }, - LatestPodFlag, - } + podStartCommand cliconfig.PodStartValues podStartDescription = ` podman pod start Starts one or more pods. The pod name or ID can be used. ` - - podStartCommand = cli.Command{ - Name: "start", - Usage: "Start one or more pods", - Description: podStartDescription, - Flags: sortFlags(podStartFlags), - Action: podStartCmd, - ArgsUsage: "POD-NAME [POD-NAME ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podStartCommand = &cobra.Command{ + Use: "start", + Short: "Start one or more pods", + Long: podStartDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podStartCommand.InputArgs = args + podStartCommand.GlobalFlags = MainGlobalOpts + return podStartCmd(&podStartCommand) + }, + Example: "POD-NAME [POD-NAME ...]", } ) -func podStartCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { +func init() { + podStartCommand.Command = _podStartCommand + flags := podStartCommand.Flags() + flags.BoolVarP(&podStartCommand.All, "all", "a", false, "Start all pods") + flags.BoolVarP(&podStartCommand.Latest, "latest", "l", false, "Start the latest pod podman is aware of") +} + +func podStartCmd(c *cliconfig.PodStartValues) error { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -49,7 +51,7 @@ func podStartCmd(c *cli.Context) error { // getPodsFromContext returns an error when a requested pod // isn't found. The only fatal error scenerio is when there are no pods // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) ctx := getContext() for _, pod := range pods { diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go index 0f0e215e6..ccbcf9f7c 100644 --- a/cmd/podman/pod_stats.go +++ b/cmd/podman/pod_stats.go @@ -7,54 +7,49 @@ import ( "encoding/json" tm "github.com/buger/goterm" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/ulule/deepcopier" - "github.com/urfave/cli" ) var ( - podStatsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show stats for all pods. Only running pods are shown by default.", - }, - cli.BoolFlag{ - Name: "no-stream", - Usage: "Disable streaming stats and only pull the first result, default setting is false", - }, - cli.BoolFlag{ - Name: "no-reset", - Usage: "Disable resetting the screen between intervals", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print container statistics to JSON or using a Go template", - }, LatestPodFlag, - } + podStatsCommand cliconfig.PodStatsValues podStatsDescription = "Display a live stream of resource usage statistics for the containers in or more pods" - podStatsCommand = cli.Command{ - Name: "stats", - Usage: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one or more pods", - Description: podStatsDescription, - Flags: sortFlags(podStatsFlags), - Action: podStatsCmd, - ArgsUsage: "[POD_NAME_OR_ID]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _podStatsCommand = &cobra.Command{ + Use: "stats", + Short: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one or more pods", + Long: podStatsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podStatsCommand.InputArgs = args + podStatsCommand.GlobalFlags = MainGlobalOpts + return podStatsCmd(&podStatsCommand) + }, + Example: "[POD_NAME_OR_ID]", } ) -func podStatsCmd(c *cli.Context) error { +func init() { + podStatsCommand.Command = _podStatsCommand + flags := podStatsCommand.Flags() + flags.BoolVarP(&podStatsCommand.All, "all", "a", false, "Provide stats for all running pods") + flags.StringVar(&podStatsCommand.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + flags.BoolVarP(&podStatsCommand.Latest, "latest", "l", false, "Provide stats on the latest pod podman is aware of") + flags.BoolVar(&podStatsCommand.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") + flags.BoolVar(&podStatsCommand.NoReset, "no-reset", false, "Disable resetting the screen between intervals") +} + +func podStatsCmd(c *cliconfig.PodStatsValues) error { var ( podFunc func() ([]*libpod.Pod, error) ) - format := c.String("format") - all := c.Bool("all") - latest := c.Bool("latest") + format := c.Format + all := c.All + latest := c.Latest ctr := 0 if all { ctr += 1 @@ -62,7 +57,7 @@ func podStatsCmd(c *cli.Context) error { if latest { ctr += 1 } - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { ctr += 1 } @@ -73,19 +68,19 @@ func podStatsCmd(c *cli.Context) error { all = true } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) times := -1 - if c.Bool("no-stream") { + if c.NoStream { times = 1 } - if len(c.Args()) > 0 { - podFunc = func() ([]*libpod.Pod, error) { return getPodsByList(c.Args(), runtime) } + if len(c.InputArgs) > 0 { + podFunc = func() ([]*libpod.Pod, error) { return getPodsByList(c.InputArgs, runtime) } } else if latest { podFunc = func() ([]*libpod.Pod, error) { latestPod, err := runtime.GetLatestPod() @@ -159,7 +154,7 @@ func podStatsCmd(c *cli.Context) error { newStats = append(newStats, &newPod) } //Output - if strings.ToLower(format) != formats.JSONString && !c.Bool("no-reset") { + if strings.ToLower(format) != formats.JSONString && !c.NoReset { tm.Clear() tm.MoveCursor(1, 1) tm.Flush() diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go index 148b4d518..63bd0f5cb 100644 --- a/cmd/podman/pod_stop.go +++ b/cmd/podman/pod_stop.go @@ -2,48 +2,50 @@ package main import ( "fmt" + + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podStopFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Stop all running pods", - }, - LatestPodFlag, - cli.UintFlag{ - Name: "timeout, time, t", - Usage: "Seconds to wait for pod stop before killing the container", - }, - } + podStopCommand cliconfig.PodStopValues podStopDescription = ` podman pod stop Stops one or more running pods. The pod name or ID can be used. ` - podStopCommand = cli.Command{ - Name: "stop", - Usage: "Stop one or more pods", - Description: podStopDescription, - Flags: sortFlags(podStopFlags), - Action: podStopCmd, - ArgsUsage: "POD-NAME [POD-NAME ...]", - OnUsageError: usageErrorHandler, + _podStopCommand = &cobra.Command{ + Use: "stop", + Short: "Stop one or more pods", + Long: podStopDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podStopCommand.InputArgs = args + podStopCommand.GlobalFlags = MainGlobalOpts + return podStopCmd(&podStopCommand) + }, + Example: "POD-NAME [POD-NAME ...]", } ) -func podStopCmd(c *cli.Context) error { +func init() { + podStopCommand.Command = _podStopCommand + flags := podStopCommand.Flags() + flags.BoolVarP(&podStopCommand.All, "all", "a", false, "Stop all running pods") + flags.BoolVarP(&podStopCommand.Latest, "latest", "l", false, "Stop the latest pod podman is aware of") + flags.UintVarP(&podStopCommand.Timeout, "timeout", "t", 0, "Seconds to wait for pod stop before killing the container") +} + +func podStopCmd(c *cliconfig.PodStopValues) error { timeout := -1 - if err := checkMutuallyExclusiveFlags(c); err != nil { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -52,12 +54,12 @@ func podStopCmd(c *cli.Context) error { // getPodsFromContext returns an error when a requested pod // isn't found. The only fatal error scenerio is when there are no pods // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) ctx := getContext() - if c.IsSet("timeout") { - timeout = int(c.Uint("timeout")) + if c.Flag("timeout").Changed { + timeout = int(c.Timeout) } for _, pod := range pods { // set cleanup to true to clean mounts and namespaces diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go index 1bd1287db..f1db2fac5 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -6,21 +6,17 @@ import ( "strings" "text/tabwriter" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podTopFlags = []cli.Flag{ - LatestFlag, - cli.BoolFlag{ - Name: "list-descriptors", - Hidden: true, - }, - } + podTopCommand cliconfig.PodTopValues + podTopDescription = fmt.Sprintf(`Display the running processes containers in a pod. Specify format descriptors to alter the output. You may run "podman pod top -l pid pcpu seccomp" to print the process ID, the CPU percentage and the seccomp mode of each process of @@ -28,24 +24,34 @@ the latest pod. %s `, getDescriptorString()) - podTopCommand = cli.Command{ - Name: "top", - Usage: "Display the running processes of containers in a pod", - Description: podTopDescription, - Flags: sortFlags(podTopFlags), - Action: podTopCmd, - ArgsUsage: "POD-NAME [format descriptors]", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _podTopCommand = &cobra.Command{ + Use: "top", + Short: "Display the running processes of containers in a pod", + Long: podTopDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podTopCommand.InputArgs = args + podTopCommand.GlobalFlags = MainGlobalOpts + return podTopCmd(&podTopCommand) + }, + Example: "POD-NAME [format descriptors]", } ) -func podTopCmd(c *cli.Context) error { +func init() { + podTopCommand.Command = _podTopCommand + flags := podTopCommand.Flags() + flags.BoolVarP(&podTopCommand.Latest, "latest,", "l", false, "Act on the latest pod podman is aware of") + flags.BoolVar(&podTopCommand.ListDescriptors, "list-descriptors", false, "") + flags.MarkHidden("list-descriptors") + +} + +func podTopCmd(c *cliconfig.PodTopValues) error { var pod *libpod.Pod var err error - args := c.Args() + args := c.InputArgs - if c.Bool("list-descriptors") { + if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { return err @@ -54,21 +60,18 @@ func podTopCmd(c *cli.Context) error { return nil } - if len(args) < 1 && !c.Bool("latest") { + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide the name or id of a running pod") } - if err := validateFlags(c, podTopFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) var descriptors []string - if c.Bool("latest") { + if c.Latest { descriptors = args pod, err = runtime.GetLatestPod() } else { diff --git a/cmd/podman/pod_unpause.go b/cmd/podman/pod_unpause.go index ed1a00cf8..17b771d4a 100644 --- a/cmd/podman/pod_unpause.go +++ b/cmd/podman/pod_unpause.go @@ -3,42 +3,42 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - podUnpauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Unpause all paused pods", + podUnpauseCommand cliconfig.PodUnpauseValues + podUnpauseDescription = `Unpauses one or more pods. The pod name or ID can be used.` + _podUnpauseCommand = &cobra.Command{ + Use: "unpause", + Short: "Unpause one or more pods", + Long: podUnpauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podUnpauseCommand.InputArgs = args + podUnpauseCommand.GlobalFlags = MainGlobalOpts + return podUnpauseCmd(&podUnpauseCommand) }, - LatestPodFlag, - } - podUnpauseDescription = ` - Unpauses one or more pods. The pod name or ID can be used. -` - - podUnpauseCommand = cli.Command{ - Name: "unpause", - Usage: "Unpause one or more pods", - Description: podUnpauseDescription, - Flags: sortFlags(podUnpauseFlags), - Action: podUnpauseCmd, - ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Example: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", } ) -func podUnpauseCmd(c *cli.Context) error { - if err := checkMutuallyExclusiveFlags(c); err != nil { +func init() { + podUnpauseCommand.Command = _podUnpauseCommand + flags := podUnpauseCommand.Flags() + flags.BoolVarP(&podUnpauseCommand.All, "all", "a", false, "Unpause all running pods") + flags.BoolVarP(&podUnpauseCommand.Latest, "latest", "l", false, "Unpause the latest pod podman is aware of") +} + +func podUnpauseCmd(c *cliconfig.PodUnpauseValues) error { + if err := checkMutuallyExclusiveFlags(&c.PodmanCommand); err != nil { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -47,7 +47,7 @@ func podUnpauseCmd(c *cli.Context) error { // getPodsFromContext returns an error when a requested pod // isn't found. The only fatal error scenerio is when there are no pods // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(c, runtime) + pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) for _, pod := range pods { ctr_errs, err := pod.Unpause() diff --git a/cmd/podman/port.go b/cmd/podman/port.go index 6875c648a..488ef2ffe 100644 --- a/cmd/podman/port.go +++ b/cmd/podman/port.go @@ -5,38 +5,44 @@ import ( "strconv" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - portFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Display port information for all containers", - }, - LatestFlag, - } + portCommand cliconfig.PortValues portDescription = ` podman port List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT ` - - portCommand = cli.Command{ - Name: "port", - Usage: "List port mappings or a specific mapping for the container", - Description: portDescription, - Flags: sortFlags(portFlags), - Action: portCmd, - ArgsUsage: "CONTAINER-NAME [mapping]", - OnUsageError: usageErrorHandler, + _portCommand = &cobra.Command{ + Use: "port", + Short: "List port mappings or a specific mapping for the container", + Long: portDescription, + RunE: func(cmd *cobra.Command, args []string) error { + portCommand.InputArgs = args + portCommand.GlobalFlags = MainGlobalOpts + return portCmd(&portCommand) + }, + Example: "CONTAINER-NAME [mapping]", } ) -func portCmd(c *cli.Context) error { +func init() { + portCommand.Command = _portCommand + flags := portCommand.Flags() + + flags.BoolVarP(&portCommand.All, "all", "a", false, "Display port information for all containers") + flags.BoolVarP(&portCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + rootCmd.AddCommand(portCommand.Command) +} + +func portCmd(c *cliconfig.PortValues) error { var ( userProto, containerName string userPort int @@ -44,29 +50,26 @@ func portCmd(c *cli.Context) error { containers []*libpod.Container ) - args := c.Args() - if err := validateFlags(c, portFlags); err != nil { - return err - } + args := c.InputArgs - if c.Bool("latest") && c.Bool("all") { + if c.Latest && c.All { return errors.Errorf("the 'all' and 'latest' options cannot be used together") } - if c.Bool("all") && len(args) > 0 { + if c.All && len(args) > 0 { return errors.Errorf("no additional arguments can be used with 'all'") } - if len(args) == 0 && !c.Bool("latest") && !c.Bool("all") { + if len(args) == 0 && !c.Latest && !c.All { return errors.Errorf("you must supply a running container name or id") } - if !c.Bool("latest") && !c.Bool("all") { + if !c.Latest && !c.All { containerName = args[0] } port := "" - if len(args) > 1 && !c.Bool("latest") { + if len(args) > 1 && !c.Latest { port = args[1] } - if len(args) == 1 && c.Bool("latest") { + if len(args) == 1 && c.Latest { port = args[0] } if port != "" { @@ -90,19 +93,19 @@ func portCmd(c *cli.Context) error { } } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if !c.Bool("latest") && !c.Bool("all") { + if !c.Latest && !c.All { container, err = runtime.LookupContainer(containerName) if err != nil { return errors.Wrapf(err, "unable to find container %s", containerName) } containers = append(containers, container) - } else if c.Bool("latest") { + } else if c.Latest { container, err = runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to get last created container") @@ -119,7 +122,7 @@ func portCmd(c *cli.Context) error { if state, _ := con.State(); state != libpod.ContainerStateRunning { continue } - if c.Bool("all") { + if c.All { fmt.Println(con.ID()) } // Iterate mappings diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 1708c671c..002975b87 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -12,6 +12,7 @@ import ( "text/tabwriter" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" @@ -21,7 +22,7 @@ import ( "github.com/docker/go-units" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/fields" ) @@ -153,112 +154,78 @@ func (a psSortedSize) Less(i, j int) bool { } var ( - psFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show all the containers, default is only running containers", - }, - cli.StringSliceFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions given", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print containers to JSON or using a Go template", - }, - cli.IntFlag{ - Name: "last, n", - Usage: "Print the n last created containers (all states)", - Value: -1, - }, - cli.BoolFlag{ - Name: "latest, l", - Usage: "Show the latest container created (all states)", - }, - cli.BoolFlag{ - Name: "namespace, ns", - Usage: "Display namespace information", - }, - cli.BoolFlag{ - Name: "no-trunc", - Usage: "Display the extended information", - }, - cli.BoolFlag{ - Name: "pod, p", - Usage: "Print the ID and name of the pod the containers are associated with", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Print the numeric IDs of the containers only", - }, - cli.BoolFlag{ - Name: "size, s", - Usage: "Display the total file sizes", - }, - cli.StringFlag{ - Name: "sort", - Usage: "Sort output by command, created, id, image, names, runningfor, size, or status", - Value: "created", - }, - cli.BoolFlag{ - Name: "sync", - Usage: "Sync container state with OCI runtime", - }, - } + psCommand cliconfig.PsValues psDescription = "Prints out information about the containers" - psCommand = cli.Command{ - Name: "list", - Aliases: []string{"ls", "ps"}, - Usage: "List containers", - Description: psDescription, - Flags: sortFlags(psFlags), - Action: psCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _psCommand = &cobra.Command{ + Use: "list", + Aliases: []string{"ls", "ps"}, + Short: "List containers", + Long: psDescription, + RunE: func(cmd *cobra.Command, args []string) error { + psCommand.InputArgs = args + psCommand.GlobalFlags = MainGlobalOpts + return psCmd(&psCommand) + }, + Example: "", } ) -func psCmd(c *cli.Context) error { +func init() { + psCommand.Command = _psCommand + flags := psCommand.Flags() + flags.BoolVarP(&psCommand.All, "all", "a", false, "Show all the containers, default is only running containers") + flags.StringSliceVarP(&psCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions given") + flags.StringVar(&psCommand.Format, "format", "", "Pretty-print containers to JSON or using a Go template") + flags.IntVarP(&psCommand.Last, "last", "n", -1, "Print the n last created containers (all states)") + flags.BoolVarP(&psCommand.Latest, "latest", "l", false, "Show the latest container created (all states)") + flags.BoolVar(&psCommand.Namespace, "namespace", false, "Display namespace information") + flags.BoolVar(&psCommand.Namespace, "ns", false, "Display namespace information") + flags.BoolVar(&psCommand.NoTrunct, "no-trunc", false, "Display the extended information") + flags.BoolVarP(&psCommand.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") + flags.BoolVarP(&psCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") + flags.BoolVarP(&psCommand.Size, "size", "s", false, "Display the total file sizes") + flags.StringVar(&psCommand.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") + flags.BoolVar(&psCommand.Sync, "sync", false, "Sync container state with OCI runtime") + + rootCmd.AddCommand(psCommand.Command) + +} +func psCmd(c *cliconfig.PsValues) error { var ( filterFuncs []libpod.ContainerFilter outputContainers []*libpod.Container ) - if err := validateFlags(c, psFlags); err != nil { - return err - } - if err := checkFlagsPassed(c); err != nil { return errors.Wrapf(err, "error with flags passed") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { return errors.Errorf("too many arguments, ps takes no arguments") } opts := shared.PsOptions{ - All: c.Bool("all"), - Format: c.String("format"), - Last: c.Int("last"), - Latest: c.Bool("latest"), - NoTrunc: c.Bool("no-trunc"), - Pod: c.Bool("pod"), - Quiet: c.Bool("quiet"), - Size: c.Bool("size"), - Namespace: c.Bool("namespace"), - Sort: c.String("sort"), - Sync: c.Bool("sync"), - } - - filters := c.StringSlice("filter") + All: c.All, + Format: c.Format, + Last: c.Last, + Latest: c.Latest, + NoTrunc: c.NoTrunct, + Pod: c.Pod, + Quiet: c.Quiet, + Size: c.Size, + Namespace: c.Namespace, + Sort: c.Sort, + Sync: c.Sync, + } + + filters := c.Filter if len(filters) > 0 { for _, f := range filters { filterSplit := strings.SplitN(f, "=", 2) @@ -299,7 +266,7 @@ func psCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("ps") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) @@ -384,20 +351,20 @@ func printQuiet(containers []shared.PsContainerOutput) error { } // checkFlagsPassed checks if mutually exclusive flags are passed together -func checkFlagsPassed(c *cli.Context) error { +func checkFlagsPassed(c *cliconfig.PsValues) error { // latest, and last are mutually exclusive. - if c.Int("last") >= 0 && c.Bool("latest") { + if c.Last >= 0 && c.Latest { return errors.Errorf("last and latest are mutually exclusive") } // Quiet conflicts with size, namespace, and format with a Go template - if c.Bool("quiet") { - if c.Bool("size") || c.Bool("namespace") || (c.IsSet("format") && - c.String("format") != formats.JSONString) { + if c.Quiet { + if c.Size || c.Namespace || (c.Flag("format").Changed && + c.Format != formats.JSONString) { return errors.Errorf("quiet conflicts with size, namespace, and format with go template") } } // Size and namespace conflict with each other - if c.Bool("size") && c.Bool("namespace") { + if c.Size && c.Namespace { return errors.Errorf("size and namespace options conflict") } return nil diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 2349265d0..d70719164 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -9,68 +9,58 @@ import ( dockerarchive "github.com/containers/image/docker/archive" "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/adapter" image2 "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - pullFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`pathname` of a directory containing TLS certificates and keys", - }, - cli.StringFlag{ - Name: "creds", - Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when pulling images", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } - + pullCommand cliconfig.PullValues pullDescription = ` Pulls an image from a registry and stores it locally. An image can be pulled using its tag or digest. If a tag is not specified, the image with the 'latest' tag (if it exists) is pulled ` - pullCommand = cli.Command{ - Name: "pull", - Usage: "Pull an image from a registry", - Description: pullDescription, - Flags: sortFlags(pullFlags), - Action: pullCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + _pullCommand = &cobra.Command{ + Use: "pull", + Short: "Pull an image from a registry", + Long: pullDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pullCommand.InputArgs = args + pullCommand.GlobalFlags = MainGlobalOpts + return pullCmd(&pullCommand) + }, + Example: "", } ) +func init() { + pullCommand.Command = _pullCommand + flags := pullCommand.Flags() + flags.StringVar(&pullCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pullCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.StringVar(&pullCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.BoolVarP(&pullCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.StringVar(&pullCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&pullCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + + rootCmd.AddCommand(pullCommand.Command) +} + // 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 { - runtime, err := adapter.GetRuntime(c) +func pullCmd(c *cliconfig.PullValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) == 0 { logrus.Errorf("an image name must be specified") return nil @@ -79,15 +69,12 @@ func pullCmd(c *cli.Context) error { logrus.Errorf("too many arguments. Requires exactly 1") return nil } - if err := validateFlags(c, pullFlags); err != nil { - return err - } image := args[0] var registryCreds *types.DockerAuthConfig - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) + if c.Flag("creds").Changed { + creds, err := util.ParseRegistryCreds(c.Creds) if err != nil { return err } @@ -98,16 +85,16 @@ func pullCmd(c *cli.Context) error { writer io.Writer imgID string ) - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } dockerRegistryOptions := image2.DockerRegistryOptions{ DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), + DockerCertPath: c.CertDir, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } // Possible for docker-archive to have multiple tags, so use LoadFromArchiveReference instead @@ -116,14 +103,14 @@ func pullCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "error parsing %q", image) } - newImage, err := runtime.LoadFromArchiveReference(getContext(), srcRef, c.String("signature-policy"), writer) + newImage, err := runtime.LoadFromArchiveReference(getContext(), srcRef, c.SignaturePolicy, writer) if err != nil { return errors.Wrapf(err, "error pulling image from %q", image) } imgID = newImage[0].ID() } else { - authfile := getAuthFile(c.String("authfile")) - newImage, err := runtime.New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true, nil) + authfile := getAuthFile(c.Authfile) + newImage, err := runtime.New(getContext(), image, c.SignaturePolicy, authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true, nil) if err != nil { return errors.Wrapf(err, "error pulling image %q", image) } diff --git a/cmd/podman/push.go b/cmd/podman/push.go index 361a25e35..a1ee00a65 100644 --- a/cmd/podman/push.go +++ b/cmd/podman/push.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" "io" "os" "strings" @@ -14,76 +16,52 @@ import ( "github.com/containers/libpod/pkg/util" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" - "github.com/urfave/cli" ) var ( - pushFlags = []cli.Flag{ - cli.StringFlag{ - Name: "signature-policy", - Usage: "`Pathname` of signature policy file (not usually used)", - Hidden: true, - }, - cli.StringFlag{ - Name: "creds", - Usage: "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`Pathname` of a directory containing TLS certificates and keys", - }, - cli.BoolFlag{ - Name: "compress", - Usage: "Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir:' transport (default is manifest type of source)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - cli.BoolFlag{ - Name: "remove-signatures", - Usage: "Discard any pre-existing signatures in the image", - }, - cli.StringFlag{ - Name: "sign-by", - Usage: "Add a signature at the destination using the specified key", - }, - cli.BoolFlag{ - 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. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - } + pushCommand cliconfig.PushValues pushDescription = fmt.Sprintf(` Pushes an image to a specified location. The Image "DESTINATION" uses a "transport":"details" format. See podman-push(1) section "DESTINATION" for the expected format`) - pushCommand = cli.Command{ - Name: "push", - Usage: "Push an image to a specified destination", - Description: pushDescription, - Flags: sortFlags(pushFlags), - Action: pushCmd, - ArgsUsage: "IMAGE DESTINATION", - OnUsageError: usageErrorHandler, + _pushCommand = &cobra.Command{ + Use: "push", + Short: "Push an image to a specified destination", + Long: pushDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pushCommand.InputArgs = args + pushCommand.GlobalFlags = MainGlobalOpts + return pushCmd(&pushCommand) + }, + Example: "IMAGE DESTINATION", } ) -func pushCmd(c *cli.Context) error { +func init() { + pushCommand.Command = _pushCommand + flags := pushCommand.Flags() + flags.MarkHidden("signature-policy") + flags.StringVar(&pushCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pushCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.BoolVar(&pushCommand.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(&pushCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.StringVarP(&pushCommand.Format, "format", "f", "", "Manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir:' transport (default is manifest type of source)") + flags.BoolVarP(&pushCommand.Quiet, "quiet", "q", false, "Don't output progress information when pushing images") + flags.BoolVar(&pushCommand.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") + flags.StringVar(&pushCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.StringVar(&pushCommand.SignBy, "sign-by", "", "Add a signature at the destination using the specified key") + flags.BoolVar(&pushCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + rootCmd.AddCommand(pushCommand.Command) +} + +func pushCmd(c *cliconfig.PushValues) error { var ( registryCreds *types.DockerAuthConfig destName string ) - args := c.Args() + args := c.InputArgs if len(args) == 0 || len(args) > 2 { return errors.New("podman push requires at least one image name, and optionally a second to specify a different destination name") } @@ -94,43 +72,40 @@ func pushCmd(c *cli.Context) error { case 2: destName = args[1] } - if err := validateFlags(c, pushFlags); err != nil { - return err - } // --compress and --format can only be used for the "dir" transport splitArg := strings.SplitN(destName, ":", 2) - if c.IsSet("compress") || c.IsSet("format") { + if c.Flag("compress").Changed || c.Flag("format").Changed { if splitArg[0] != directory.Transport.Name() { return errors.Errorf("--compress and --format can be set only when pushing to a directory using the 'dir' transport") } } - certPath := c.String("cert-dir") - removeSignatures := c.Bool("remove-signatures") - signBy := c.String("sign-by") + certPath := c.CertDir + removeSignatures := c.RemoveSignatures + signBy := c.SignBy - if c.IsSet("creds") { - creds, err := util.ParseRegistryCreds(c.String("creds")) + if c.Flag("creds").Changed { + creds, err := util.ParseRegistryCreds(c.Creds) if err != nil { return err } registryCreds = creds } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) var writer io.Writer - if !c.Bool("quiet") { + if !c.Quiet { writer = os.Stderr } var manifestType string - if c.IsSet("format") { + if c.Flag("format").Changed { switch c.String("format") { case "oci": manifestType = imgspecv1.MediaTypeImageManifest @@ -147,8 +122,8 @@ func pushCmd(c *cli.Context) error { DockerRegistryCreds: registryCreds, DockerCertPath: certPath, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } so := image.SigningOptions{ @@ -161,7 +136,7 @@ func pushCmd(c *cli.Context) error { return err } - authfile := getAuthFile(c.String("authfile")) + authfile := getAuthFile(c.Authfile) - return newImage.PushImageToHeuristicDestination(getContext(), destName, manifestType, authfile, c.String("signature-policy"), writer, c.Bool("compress"), so, &dockerRegistryOptions, nil) + return newImage.PushImageToHeuristicDestination(getContext(), destName, manifestType, authfile, c.SignaturePolicy, writer, c.Compress, so, &dockerRegistryOptions, nil) } diff --git a/cmd/podman/refresh.go b/cmd/podman/refresh.go index b07376170..3890188b4 100644 --- a/cmd/podman/refresh.go +++ b/cmd/podman/refresh.go @@ -4,37 +4,38 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - refreshFlags = []cli.Flag{} - + refreshCommand cliconfig.RefreshValues refreshDescription = "The refresh command resets the state of all containers to handle database changes after a Podman upgrade. All running containers will be restarted." - - refreshCommand = cli.Command{ - Name: "refresh", - Usage: "Refresh container state", - Description: refreshDescription, - Flags: sortFlags(refreshFlags), - Action: refreshCmd, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _refreshCommand = &cobra.Command{ + Use: "refresh", + Short: "Refresh container state", + Long: refreshDescription, + RunE: func(cmd *cobra.Command, args []string) error { + refreshCommand.InputArgs = args + refreshCommand.GlobalFlags = MainGlobalOpts + return refreshCmd(&refreshCommand) + }, } ) -func refreshCmd(c *cli.Context) error { - if len(c.Args()) > 0 { - return errors.Errorf("refresh does not accept any arguments") - } +func init() { + refreshCommand.Command = _refreshCommand + rootCmd.AddCommand(refreshCommand.Command) +} - if err := validateFlags(c, refreshFlags); err != nil { - return err +func refreshCmd(c *cliconfig.RefreshValues) error { + if len(c.InputArgs) > 0 { + return errors.Errorf("refresh does not accept any arguments") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 2e52ce5e4..f94e745ed 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -4,47 +4,45 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - restartFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Restart all non-running containers", - }, - cli.BoolFlag{ - Name: "running", - Usage: "Restart only running containers when --all is used", - }, - cli.UintFlag{ - Name: "timeout, time, t", - Usage: "Seconds to wait for stop before killing the container", - Value: libpod.CtrRemoveTimeout, - }, - LatestFlag, - } + restartCommand cliconfig.RestartValues restartDescription = `Restarts one or more running containers. The container ID or name can be used. A timeout before forcibly stopping can be set, but defaults to 10 seconds` - - restartCommand = cli.Command{ - Name: "restart", - Usage: "Restart one or more containers", - Description: restartDescription, - Flags: sortFlags(restartFlags), - Action: restartCmd, - ArgsUsage: "CONTAINER [CONTAINER ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _restartCommand = &cobra.Command{ + Use: "restart", + Short: "Restart one or more containers", + Long: restartDescription, + RunE: func(cmd *cobra.Command, args []string) error { + restartCommand.InputArgs = args + restartCommand.GlobalFlags = MainGlobalOpts + return restartCmd(&restartCommand) + }, + Example: "CONTAINER [CONTAINER ...]", } ) -func restartCmd(c *cli.Context) error { +func init() { + restartCommand.Command = _restartCommand + flags := restartCommand.Flags() + flags.BoolVarP(&restartCommand.All, "all", "a", false, "Restart all non-running containers") + flags.BoolVarP(&restartCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&restartCommand.Running, "running", false, "Restart only running containers when --all is used") + flags.UintVarP(&restartCommand.Timeout, "timeout", "t", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") + flags.UintVar(&restartCommand.Timeout, "time", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") + + rootCmd.AddCommand(restartCommand.Command) +} + +func restartCmd(c *cliconfig.RestartValues) error { var ( restartFuncs []shared.ParallelWorkerInput containers []*libpod.Container @@ -55,34 +53,31 @@ func restartCmd(c *cli.Context) error { rootless.SetSkipStorageSetup(true) } - args := c.Args() - runOnly := c.Bool("running") - all := c.Bool("all") - if len(args) < 1 && !c.Bool("latest") && !all { + args := c.InputArgs + runOnly := c.Running + all := c.All + if len(args) < 1 && !c.Latest && !all { return errors.Wrapf(libpod.ErrInvalidArg, "you must provide at least one container name or ID") } - if err := validateFlags(c, restartFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - timeout := c.Uint("timeout") - useTimeout := c.IsSet("timeout") + timeout := c.Timeout + useTimeout := c.Flag("timeout").Changed // Handle --latest - if c.Bool("latest") { + if c.Latest { lastCtr, err := runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to get latest container") } restartContainers = append(restartContainers, lastCtr) } else if runOnly { - containers, err = getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err = getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { return err } @@ -105,7 +100,7 @@ func restartCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("restart") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 664475e22..daee635f0 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -5,68 +5,67 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + restoreCommand cliconfig.RestoreValues restoreDescription = ` podman container restore Restores a container from a checkpoint. The container name or ID can be used. ` - restoreFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "keep, k", - Usage: "Keep all temporary checkpoint files", + _restoreCommand = &cobra.Command{ + Use: "restore", + Short: "Restores one or more containers from a checkpoint", + Long: restoreDescription, + RunE: func(cmd *cobra.Command, args []string) error { + restoreCommand.InputArgs = args + restoreCommand.GlobalFlags = MainGlobalOpts + return restoreCmd(&restoreCommand) }, - // restore --all would make more sense if there would be - // dedicated state for container which are checkpointed. - // TODO: add ContainerStateCheckpointed - cli.BoolFlag{ - Name: "tcp-established", - Usage: "Checkpoint a container with established TCP connections", - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Restore all checkpointed containers", - }, - LatestFlag, - } - restoreCommand = cli.Command{ - Name: "restore", - Usage: "Restores one or more containers from a checkpoint", - Description: restoreDescription, - Flags: sortFlags(restoreFlags), - Action: restoreCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func restoreCmd(c *cli.Context) error { +func init() { + restoreCommand.Command = _restoreCommand + flags := restoreCommand.Flags() + flags.BoolVarP(&restoreCommand.All, "all", "a", false, "Restore all checkpointed containers") + flags.BoolVarP(&restoreCommand.Keep, "keep", "k", false, "Keep all temporary checkpoint files") + flags.BoolVarP(&restoreCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + // TODO: add ContainerStateCheckpointed + flags.BoolVar(&restoreCommand.TcpEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections") + + rootCmd.AddCommand(restoreCommand.Command) +} + +func restoreCmd(c *cliconfig.RestoreValues) error { if rootless.IsRootless() { return errors.New("restoring a container requires root") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) options := libpod.ContainerCheckpointOptions{ - Keep: c.Bool("keep"), - TCPEstablished: c.Bool("tcp-established"), + Keep: c.Keep, + TCPEstablished: c.TcpEstablished, } - if err := checkAllAndLatest(c); err != nil { + if err := checkAllAndLatest(&c.PodmanCommand); err != nil { return err } - containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateExited, "checkpointed") + containers, lastError := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateExited, "checkpointed") for _, ctr := range containers { if err = ctr.Restore(context.TODO(), options); err != nil { diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 7c0569b78..bb9a913c9 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -2,67 +2,64 @@ package main import ( "fmt" + + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - rmFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all containers", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force removal of a running container. The default is false", - }, - LatestFlag, - cli.BoolFlag{ - Name: "volumes, v", - Usage: "Remove the volumes associated with the container (Not implemented yet)", - }, - } + rmCommand cliconfig.RmValues rmDescription = fmt.Sprintf(` Podman rm will remove one or more containers from the host. The container name or ID can be used. This does not remove images. Running containers will not be removed without the -f option. `) - rmCommand = cli.Command{ - Name: "rm", - Usage: "Remove one or more containers", - Description: rmDescription, - Flags: sortFlags(rmFlags), - Action: rmCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _rmCommand = &cobra.Command{ + Use: "rm", + Short: "Remove one or more containers", + Long: rmDescription, + RunE: func(cmd *cobra.Command, args []string) error { + rmCommand.InputArgs = args + rmCommand.GlobalFlags = MainGlobalOpts + return rmCmd(&rmCommand) + }, + Example: "", } ) +func init() { + rmCommand.Command = _rmCommand + flags := rmCommand.Flags() + flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") + flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") + flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container (Not implemented yet)") + + rootCmd.AddCommand(rmCommand.Command) +} + // saveCmd saves the image to either docker-archive or oci -func rmCmd(c *cli.Context) error { +func rmCmd(c *cliconfig.RmValues) error { var ( deleteFuncs []shared.ParallelWorkerInput ) ctx := getContext() - if err := validateFlags(c, rmFlags); err != nil { - return err - } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - if err := checkAllAndLatest(c); err != nil { + if err := checkAllAndLatest(&c.PodmanCommand); err != nil { return err } - delContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + delContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { if len(delContainers) == 0 { return err @@ -73,7 +70,7 @@ func rmCmd(c *cli.Context) error { for _, container := range delContainers { con := container f := func() error { - return runtime.RemoveContainer(ctx, con, c.Bool("force")) + return runtime.RemoveContainer(ctx, con, c.Force) } deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{ @@ -83,7 +80,7 @@ func rmCmd(c *cli.Context) error { } maxWorkers := shared.Parallelize("rm") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index 39757272e..70f58e844 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -4,47 +4,39 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/adapter" "github.com/containers/storage" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + rmiCommand cliconfig.RmiValues rmiDescription = "Removes one or more locally stored images." - rmiFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all images", + _rmiCommand = &cobra.Command{ + Use: "rmi", + Short: "Removes one or more images from local storage", + Long: rmiDescription, + RunE: func(cmd *cobra.Command, args []string) error { + rmiCommand.InputArgs = args + rmiCommand.GlobalFlags = MainGlobalOpts + return rmiCmd(&rmiCommand) }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force removal of the image", - }, - } - rmiCommand = cli.Command{ - Name: "rmi", - Usage: "Remove one or more images from local storage", - Description: rmiDescription, - Action: rmiCmd, - ArgsUsage: "IMAGE-NAME-OR-ID [...]", - Flags: sortFlags(rmiFlags), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - } - rmImageCommand = cli.Command{ - Name: "rm", - Usage: "Removes one or more images from local storage", - Description: rmiDescription, - Action: rmiCmd, - ArgsUsage: "IMAGE-NAME-OR-ID [...]", - Flags: rmiFlags, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + Example: "IMAGE-NAME-OR-ID [...]", } ) -func rmiCmd(c *cli.Context) error { +func init() { + rmiCommand.Command = _rmiCommand + flags := rmiCommand.Flags() + flags.BoolVarP(&rmiCommand.All, "all", "a", false, "Remove all images") + flags.BoolVarP(&rmiCommand.Force, "force", "f", false, "Force Removal of the image") + + rootCmd.AddCommand(rmiCommand.Command) +} + +func rmiCmd(c *cliconfig.RmiValues) error { var ( lastError error deleted bool @@ -53,17 +45,14 @@ func rmiCmd(c *cli.Context) error { ) ctx := getContext() - if err := validateFlags(c, rmiFlags); err != nil { - return err - } - removeAll := c.Bool("all") - runtime, err := adapter.GetRuntime(c) + removeAll := c.All + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) == 0 && !removeAll { return errors.Errorf("image name or ID must be specified") } @@ -75,7 +64,7 @@ func rmiCmd(c *cli.Context) error { removeImage := func(img *adapter.ContainerImage) { deleted = true - msg, deleteErr = runtime.RemoveImage(ctx, img, c.Bool("force")) + msg, deleteErr = runtime.RemoveImage(ctx, img, c.Force) if deleteErr != nil { if errors.Cause(deleteErr) == storage.ErrImageUsedByContainer { fmt.Printf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()) diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 3ef546940..76b29cb84 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -8,49 +8,57 @@ import ( "strconv" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var runDescription = "Runs a command in a new container from the given image" - -var runFlags []cli.Flag = append(createFlags, cli.BoolTFlag{ - Name: "sig-proxy", - Usage: "Proxy received signals to the process (default true)", -}) - -var runCommand = cli.Command{ - Name: "run", - Usage: "Run a command in a new container", - Description: runDescription, - Flags: sortFlags(runFlags), - Action: runCmd, - ArgsUsage: "IMAGE [COMMAND [ARG...]]", - HideHelp: true, - SkipArgReorder: true, - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, +var ( + runCommmand cliconfig.RunValues + + runDescription = "Runs a command in a new container from the given image" + _runCommmand = &cobra.Command{ + Use: "run", + Short: "Run a command in a new container", + Long: runDescription, + RunE: func(cmd *cobra.Command, args []string) error { + runCommmand.InputArgs = args + runCommmand.GlobalFlags = MainGlobalOpts + return runCmd(&runCommmand) + }, + Example: "IMAGE [COMMAND [ARG...]]", + } +) + +func init() { + runCommmand.Command = _runCommmand + flags := runCommmand.Flags() + flags.SetInterspersed(false) + flags.Bool("sig-proxy", true, "Proxy received signals to the process (default true)") + getCreateFlags(&runCommmand.PodmanCommand) + + rootCmd.AddCommand(runCommmand.Command) } -func runCmd(c *cli.Context) error { - if err := createInit(c); err != nil { +func runCmd(c *cliconfig.RunValues) error { + if err := createInit(&c.PodmanCommand); err != nil { return err } if os.Geteuid() != 0 { rootless.SetSkipStorageSetup(true) } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - ctr, createConfig, err := createContainer(c, runtime) + ctr, createConfig, err := createContainer(&c.PodmanCommand, runtime) if err != nil { return err } @@ -110,7 +118,7 @@ func runCmd(c *cli.Context) error { } } } - if err := startAttachCtr(ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.BoolT("sig-proxy"), true); err != nil { + if err := startAttachCtr(ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true); err != nil { // This means the command did not exist exitCode = 127 if strings.Index(err.Error(), "permission denied") > -1 { diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index 33c0a4bfe..5ea39e457 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -4,24 +4,19 @@ import ( "runtime" "testing" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/inspect" cc "github.com/containers/libpod/pkg/spec" - units "github.com/docker/go-units" + "github.com/docker/go-units" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" - "github.com/urfave/cli" ) var ( - cmd = []string{"podman", "test", "alpine"} - CLI *cli.Context - testCommand = cli.Command{ - Name: "test", - Flags: sortFlags(createFlags), - Action: testCmd, - HideHelp: true, - } + cmd = []string{"podman", "test", "alpine"} + CLI *cliconfig.PodmanCommand ) // generates a mocked ImageData structure based on alpine @@ -53,23 +48,29 @@ func generateAlpineImageData() *inspect.ImageData { } // sets a global CLI -func testCmd(c *cli.Context) error { - CLI = c +func testCmd(c *cobra.Command) error { + CLI = &cliconfig.PodmanCommand{Command: c} return nil } // creates the mocked cli pointing to our create flags // global flags like log-level are not implemented -func createCLI() cli.App { - a := cli.App{ - Commands: []cli.Command{ - testCommand, +func createCLI(args []string) *cliconfig.PodmanCommand { + var testCommand = &cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "test", + RunE: func(cmd *cobra.Command, args []string) error { + return testCmd(cmd) + }, }, } - return a + rootCmd := testCommand + getCreateFlags(rootCmd) + rootCmd.ParseFlags(args) + return rootCmd } -func getRuntimeSpec(c *cli.Context) (*spec.Spec, error) { +func getRuntimeSpec(c *cliconfig.PodmanCommand) (*spec.Spec, error) { /* TODO: This test has never worked. Need to install content runtime, err := getRuntime(c) @@ -98,10 +99,11 @@ func TestPIDsLimit(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } - a := createCLI() args := []string{"--pids-limit", "22"} - a.Run(append(cmd, args...)) - runtimeSpec, err := getRuntimeSpec(CLI) + a := createCLI(args) + a.InputArgs = args + //a.Run(append(cmd, args...)) + runtimeSpec, err := getRuntimeSpec(a) if err != nil { t.Fatalf(err.Error()) } @@ -116,10 +118,10 @@ func TestBLKIOWeightDevice(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } - a := createCLI() args := []string{"--blkio-weight-device", "/dev/zero:100"} - a.Run(append(cmd, args...)) - runtimeSpec, err := getRuntimeSpec(CLI) + a := createCLI(args) + a.InputArgs = args + runtimeSpec, err := getRuntimeSpec(a) if err != nil { t.Fatalf(err.Error()) } @@ -134,10 +136,11 @@ func TestMemorySwap(t *testing.T) { if runtime.GOOS != "linux" { t.Skip("seccomp, which is enabled by default, is only supported on Linux") } - a := createCLI() args := []string{"--memory-swap", "45m", "--memory", "40m"} - a.Run(append(cmd, args...)) - runtimeSpec, err := getRuntimeSpec(CLI) + a := createCLI(args) + a.InputArgs = args + //a.Run(append(cmd, args...)) + runtimeSpec, err := getRuntimeSpec(a) if err != nil { t.Fatalf(err.Error()) } diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index 38905b5ca..ff8a282d3 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -7,88 +7,59 @@ import ( "strings" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - runlabelFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.BoolFlag{ - Name: "display", - Usage: "Preview the command that the label would run", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`Pathname` of a directory containing TLS certificates and keys", - }, - cli.StringFlag{ - Name: "creds", - Usage: "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.StringFlag{ - Name: "name", - Usage: "Assign a name to the container", - }, - cli.StringFlag{ - Name: "opt1", - Usage: "Optional parameter to pass for install", - Hidden: true, - }, - cli.StringFlag{ - Name: "opt2", - Usage: "Optional parameter to pass for install", - Hidden: true, - }, - cli.StringFlag{ - Name: "opt3", - Usage: "Optional parameter to pass for install", - Hidden: true, - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when installing images", - }, - cli.BoolFlag{ - Name: "pull, p", - Usage: "Pull the image if it does not exist locally prior to executing the label contents", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`Pathname` of signature policy file (not usually used)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } - + runlabelCommand cliconfig.RunlabelValues runlabelDescription = ` Executes a command as described by a container image label. ` - runlabelCommand = cli.Command{ - Name: "runlabel", - Usage: "Execute the command described by an image label", - Description: runlabelDescription, - Flags: sortFlags(runlabelFlags), - Action: runlabelCmd, - ArgsUsage: "", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _runlabelCommand = &cobra.Command{ + Use: "runlabel", + Short: "Execute the command described by an image label", + Long: runlabelDescription, + RunE: func(cmd *cobra.Command, args []string) error { + runlabelCommand.InputArgs = args + runlabelCommand.GlobalFlags = MainGlobalOpts + return runlabelCmd(&runlabelCommand) + }, + Example: "", } ) +func init() { + runlabelCommand.Command = _runlabelCommand + flags := runlabelCommand.Flags() + flags.StringVar(&runlabelCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&runlabelCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.StringVar(&runlabelCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.BoolVar(&runlabelCommand.Display, "display", false, "Preview the command that the label would run") + flags.StringVar(&runlabelCommand.Name, "name", "", "Assign a name to the container") + + flags.StringVar(&runlabelCommand.Opt1, "opt1", "", "Optional parameter to pass for install") + flags.StringVar(&runlabelCommand.Opt2, "opt2", "", "Optional parameter to pass for install") + flags.StringVar(&runlabelCommand.Opt3, "opt3", "", "Optional parameter to pass for install") + flags.MarkHidden("opt1") + flags.MarkHidden("opt3") + flags.MarkHidden("opt3") + + flags.BoolVarP(&runlabelCommand.Pull, "pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents") + flags.BoolVarP(&runlabelCommand.Quiet, "quiet", "q", false, "Suppress output information when installing images") + flags.StringVar(&runlabelCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") +} + // installCmd gets the data from the command line and calls installImage // to copy an image from a registry to a local machine -func runlabelCmd(c *cli.Context) error { +func runlabelCmd(c *cliconfig.RunlabelValues) error { var ( imageName string stdErr, stdOut io.Writer @@ -105,40 +76,38 @@ func runlabelCmd(c *cli.Context) error { } opts := make(map[string]string) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() + args := c.InputArgs if len(args) < 2 { logrus.Errorf("the runlabel command requires at least 2 arguments: LABEL IMAGE") return nil } - if err := validateFlags(c, runlabelFlags); err != nil { - return err - } - if c.Bool("display") && c.Bool("quiet") { + if c.Display && c.Quiet { return errors.Errorf("the display and quiet flags cannot be used together.") } if len(args) > 2 { extraArgs = args[2:] } - pull := c.Bool("pull") + pull := c.Pull label := args[0] runlabelImage := args[1] - if c.IsSet("opt1") { - opts["opt1"] = c.String("opt1") + if c.Flag("opt1").Changed { + opts["opt1"] = c.Opt1 } - if c.IsSet("opt2") { - opts["opt2"] = c.String("opt2") + + if c.Flag("opt2").Changed { + opts["opt2"] = c.Opt2 } - if c.IsSet("opt3") { - opts["opt3"] = c.String("opt3") + if c.Flag("opt3").Changed { + opts["opt3"] = c.Opt3 } ctx := getContext() @@ -147,21 +116,21 @@ func runlabelCmd(c *cli.Context) error { stdOut = os.Stdout stdIn = os.Stdin - if c.Bool("quiet") { + if c.Quiet { stdErr = nil stdOut = nil stdIn = nil } dockerRegistryOptions := image.DockerRegistryOptions{ - DockerCertPath: c.String("cert-dir"), + DockerCertPath: c.CertDir, } - if c.IsSet("tls-verify") { - dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } - authfile := getAuthFile(c.String("authfile")) - runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.String("creds"), dockerRegistryOptions, authfile, c.String("signature-policy"), stdOut) + authfile := getAuthFile(c.Authfile) + runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.Creds, dockerRegistryOptions, authfile, c.SignaturePolicy, stdOut) if err != nil { return err } @@ -169,13 +138,13 @@ func runlabelCmd(c *cli.Context) error { return errors.Errorf("%s does not have a label of %s", runlabelImage, label) } - cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.String("name"), opts, extraArgs) + cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs) if err != nil { return err } - if !c.Bool("quiet") { + if !c.Quiet { fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) - if c.Bool("display") { + if c.Display { return nil } } diff --git a/cmd/podman/save.go b/cmd/podman/save.go index 325140b76..766561f1a 100644 --- a/cmd/podman/save.go +++ b/cmd/podman/save.go @@ -12,12 +12,13 @@ import ( "github.com/containers/image/manifest" ociarchive "github.com/containers/image/oci/archive" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" libpodImage "github.com/containers/libpod/libpod/image" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( @@ -26,67 +27,58 @@ const ( ) 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)", - }, - } + saveCommand cliconfig.SaveValues 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: sortFlags(saveFlags), - Action: saveCmd, - ArgsUsage: "", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _saveCommand = &cobra.Command{ + Use: "save", + Short: "Save image to an archive", + Long: saveDescription, + RunE: func(cmd *cobra.Command, args []string) error { + saveCommand.InputArgs = args + saveCommand.GlobalFlags = MainGlobalOpts + return saveCmd(&saveCommand) + }, + Example: "", } ) +func init() { + saveCommand.Command = _saveCommand + flags := saveCommand.Flags() + flags.BoolVar(&saveCommand.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(&saveCommand.Format, "format", "", "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-dir (directory with v2s2 manifest type)") + flags.StringVarP(&saveCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") + flags.BoolVarP(&saveCommand.Quiet, "quiet", "q", false, "Suppress the output") + + rootCmd.AddCommand(saveCommand.Command) +} + // saveCmd saves the image to either docker-archive or oci -func saveCmd(c *cli.Context) error { - args := c.Args() +func saveCmd(c *cliconfig.SaveValues) error { + args := c.InputArgs 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) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) 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") == "") { + if c.Flag("compress").Changed && (c.Format != ociManifestDir && c.Format != v2s2ManifestDir && c.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") { + if !c.Quiet { writer = os.Stderr } - output := c.String("output") + output := c.Output if output == "/dev/stdout" { fi := os.Stdout if logrus.IsTerminal(fi) { @@ -105,7 +97,7 @@ func saveCmd(c *cli.Context) error { var destRef types.ImageReference var manifestType string - switch c.String("format") { + switch c.Format { case "oci-archive": destImageName := imageNameForSaveDestination(newImage, source) destRef, err = ociarchive.NewReference(output, destImageName) // destImageName may be "" diff --git a/cmd/podman/search.go b/cmd/podman/search.go index 81469a0f8..b15da1f6d 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -8,13 +8,14 @@ import ( "github.com/containers/image/docker" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod/common" sysreg "github.com/containers/libpod/pkg/registries" "github.com/docker/distribution/reference" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) const ( @@ -23,46 +24,36 @@ const ( ) var ( - searchFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", - }, - cli.StringSliceFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions provided (default [])", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to a Go template", - }, - cli.IntFlag{ - Name: "limit", - Usage: "Limit the number of results", - }, - cli.BoolFlag{ - Name: "no-trunc", - Usage: "Do not truncate the output", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "Require HTTPS and verify certificates when contacting registries (default: true)", - }, - } + searchCommand cliconfig.SearchValues searchDescription = ` Search registries for a given image. Can search all the default registries or a specific registry. Can limit the number of results, and filter the output based on certain conditions.` - searchCommand = cli.Command{ - Name: "search", - Usage: "Search registry for image", - Description: searchDescription, - Flags: sortFlags(searchFlags), - Action: searchCmd, - ArgsUsage: "TERM", - OnUsageError: usageErrorHandler, + _searchCommand = &cobra.Command{ + Use: "search", + Short: "Search registry for image", + Long: searchDescription, + RunE: func(cmd *cobra.Command, args []string) error { + searchCommand.InputArgs = args + searchCommand.GlobalFlags = MainGlobalOpts + return searchCmd(&searchCommand) + }, + Example: "TERM", } ) +func init() { + searchCommand.Command = _searchCommand + flags := searchCommand.Flags() + flags.StringVar(&searchCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringSliceVarP(&searchCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + flags.StringVar(&searchCommand.Format, "format", "", "Change the output format to a Go template") + flags.IntVar(&searchCommand.Limit, "limit", 0, "Limit the number of results") + flags.BoolVar(&searchCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") + flags.BoolVar(&searchCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + + rootCmd.AddCommand(searchCommand.Command) +} + type searchParams struct { Index string Name string @@ -87,8 +78,8 @@ type searchFilterParams struct { isOfficial *bool } -func searchCmd(c *cli.Context) error { - args := c.Args() +func searchCmd(c *cliconfig.SearchValues) error { + args := c.InputArgs if len(args) > 1 { return errors.Errorf("too many arguments. Requires exactly 1") } @@ -106,20 +97,16 @@ func searchCmd(c *cli.Context) error { term = term[len(registry)+1:] } - if err := validateFlags(c, searchFlags); err != nil { - return err - } - - format := genSearchFormat(c.String("format")) + format := genSearchFormat(c.Format) opts := searchOpts{ format: format, - noTrunc: c.Bool("no-trunc"), - limit: c.Int("limit"), - filter: c.StringSlice("filter"), - authfile: getAuthFile(c.String("authfile")), + noTrunc: c.NoTrunc, + limit: c.Limit, + filter: c.Filter, + authfile: getAuthFile(c.Authfile), } - if c.IsSet("tls-verify") { - opts.insecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + if c.Flag("tls-verify").Changed { + opts.insecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } registries, err := getRegistries(registry) if err != nil { diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index f84fb8261..c74d8fdce 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -88,6 +88,7 @@ type PsContainerOutput struct { PIDNS string User string UTS string + Mounts string } // Namespace describes output for ps namespace @@ -228,6 +229,7 @@ func NewBatchContainer(ctr *libpod.Container, opts PsOptions) (PsContainerOutput pso.CreatedAt = ctr.CreatedTime() pso.StartedAt = startedAt pso.Labels = ctr.Labels() + pso.Mounts = strings.Join(ctr.UserVolumes(), " ") if opts.Namespace { pso.Cgroup = ns.Cgroup diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go index 22aa07230..a87c12556 100644 --- a/cmd/podman/sign.go +++ b/cmd/podman/sign.go @@ -11,60 +11,63 @@ import ( "github.com/containers/image/signature" "github.com/containers/image/transports" "github.com/containers/image/transports/alltransports" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/trust" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - signFlags = []cli.Flag{ - cli.StringFlag{ - Name: "sign-by", - Usage: "Name of the signing key", - }, - cli.StringFlag{ - Name: "directory, d", - Usage: "Define an alternate directory to store signatures", - }, - } - + signCommand cliconfig.SignValues signDescription = "Create a signature file that can be used later to verify the image" - signCommand = cli.Command{ - Name: "sign", - Usage: "Sign an image", - Description: signDescription, - Flags: sortFlags(signFlags), - Action: signCmd, - ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", - OnUsageError: usageErrorHandler, + _signCommand = &cobra.Command{ + Use: "sign", + Short: "Sign an image", + Long: signDescription, + RunE: func(cmd *cobra.Command, args []string) error { + signCommand.InputArgs = args + signCommand.GlobalFlags = MainGlobalOpts + return signCmd(&signCommand) + }, + Example: "IMAGE-NAME [IMAGE-NAME ...]", } ) +func init() { + signCommand.Command = _signCommand + flags := signCommand.Flags() + flags.StringVarP(&signCommand.Directory, "directory", "d", "", "Define an alternate directory to store signatures") + flags.StringVar(&signCommand.SignBy, "sign-by", "", "Name of the signing key") + + rootCmd.AddCommand(signCommand.Command) + +} + // SignatureStoreDir defines default directory to store signatures const SignatureStoreDir = "/var/lib/containers/sigstore" -func signCmd(c *cli.Context) error { - args := c.Args() +func signCmd(c *cliconfig.SignValues) error { + args := c.InputArgs if len(args) < 1 { return errors.Errorf("at least one image name must be specified") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } defer runtime.Shutdown(false) - signby := c.String("sign-by") + signby := c.SignBy if signby == "" { return errors.Errorf("please provide an identity") } var sigStoreDir string - if c.IsSet("directory") { - sigStoreDir = c.String("directory") + if c.Flag("directory").Changed { + sigStoreDir = c.Directory if _, err := os.Stat(sigStoreDir); err != nil { return errors.Wrapf(err, "invalid directory %s", sigStoreDir) } diff --git a/cmd/podman/start.go b/cmd/podman/start.go index f6e1d9882..483ab4081 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -5,84 +5,75 @@ import ( "fmt" "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" cc "github.com/containers/libpod/pkg/spec" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - startFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "attach, a", - Usage: "Attach container's STDOUT and STDERR", - }, - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.", - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Keep STDIN open even if not attached", - }, - cli.BoolTFlag{ - Name: "sig-proxy", - Usage: "Proxy received signals to the process (default true if attaching, false otherwise)", - }, - LatestFlag, - } + startCommand cliconfig.StartValues startDescription = ` podman start Starts one or more containers. The container name or ID can be used. ` - - startCommand = cli.Command{ - Name: "start", - Usage: "Start one or more containers", - Description: startDescription, - Flags: sortFlags(startFlags), - Action: startCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + _startCommand = &cobra.Command{ + Use: "start", + Short: "Start one or more containers", + Long: startDescription, + RunE: func(cmd *cobra.Command, args []string) error { + startCommand.InputArgs = args + startCommand.GlobalFlags = MainGlobalOpts + return startCmd(&startCommand) + }, + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func startCmd(c *cli.Context) error { - args := c.Args() - if len(args) < 1 && !c.Bool("latest") { +func init() { + startCommand.Command = _startCommand + flags := startCommand.Flags() + flags.BoolVarP(&startCommand.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") + flags.StringVar(&startCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") + flags.BoolVarP(&startCommand.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.BoolVarP(&startCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&startCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true if attaching, false otherwise)") + + rootCmd.AddCommand(startCommand.Command) +} + +func startCmd(c *cliconfig.StartValues) error { + args := c.InputArgs + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide at least one container name or id") } - attach := c.Bool("attach") + attach := c.Attach if len(args) > 1 && attach { return errors.Errorf("you cannot start and attach multiple containers at once") } - if err := validateFlags(c, startFlags); err != nil { - return err - } - - sigProxy := c.BoolT("sig-proxy") + sigProxy := c.SigProxy if sigProxy && !attach { - if c.IsSet("sig-proxy") { + if c.Flag("sig-proxy").Changed { return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") } else { sigProxy = false } } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if c.Bool("latest") { + if c.Latest { lastCtr, err := runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to get latest container") @@ -112,12 +103,12 @@ func startCmd(c *cli.Context) error { if attach { inputStream := os.Stdin - if !c.Bool("interactive") { + if !c.Interactive { inputStream = nil } // attach to the container and also start it not already running - err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), sigProxy, !ctrRunning) + err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning) if ctrRunning { return err } diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go index 769354b23..8c79ed290 100644 --- a/cmd/podman/stats.go +++ b/cmd/podman/stats.go @@ -8,12 +8,13 @@ import ( "time" tm "github.com/buger/goterm" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/docker/go-units" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) type statsOutputParams struct { @@ -28,48 +29,41 @@ type statsOutputParams struct { } var ( - statsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show all containers. Only running containers are shown by default. The default is false", - }, - cli.BoolFlag{ - Name: "no-stream", - Usage: "Disable streaming stats and only pull the first result, default setting is false", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print container statistics to JSON or using a Go template", - }, - cli.BoolFlag{ - Name: "no-reset", - Usage: "Disable resetting the screen between intervals", - }, LatestFlag, - } + statsCommand cliconfig.StatsValues - statsDescription = "Display a live stream of one or more containers' resource usage statistics" - statsCommand = cli.Command{ - Name: "stats", - Usage: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers", - Description: statsDescription, - Flags: sortFlags(statsFlags), - Action: statsCmd, - ArgsUsage: "", - OnUsageError: usageErrorHandler, + statsDescription = "display a live stream of one or more containers' resource usage statistics" + _statsCommand = &cobra.Command{ + Use: "stats", + Short: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers", + Long: statsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + statsCommand.InputArgs = args + statsCommand.GlobalFlags = MainGlobalOpts + return statsCmd(&statsCommand) + }, + Example: "", } ) -func statsCmd(c *cli.Context) error { - if err := validateFlags(c, statsFlags); err != nil { - return err - } +func init() { + statsCommand.Command = _statsCommand + flags := statsCommand.Flags() + flags.BoolVarP(&statsCommand.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") + flags.StringVar(&statsCommand.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + flags.BoolVarP(&statsCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&statsCommand.NoReset, "no-reset", false, "Disable resetting the screen between intervals") + flags.BoolVar(&statsCommand.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") + + rootCmd.AddCommand(statsCommand.Command) +} +func statsCmd(c *cliconfig.StatsValues) error { if os.Geteuid() != 0 { return errors.New("stats is not supported for rootless containers") } - all := c.Bool("all") - latest := c.Bool("latest") + all := c.All + latest := c.Latest ctr := 0 if all { ctr += 1 @@ -77,7 +71,7 @@ func statsCmd(c *cli.Context) error { if latest { ctr += 1 } - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { ctr += 1 } @@ -87,14 +81,14 @@ func statsCmd(c *cli.Context) error { return errors.Errorf("you must specify --all, --latest, or at least one container") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) times := -1 - if c.Bool("no-stream") { + if c.NoStream { times = 1 } @@ -102,8 +96,8 @@ func statsCmd(c *cli.Context) error { var containerFunc func() ([]*libpod.Container, error) containerFunc = runtime.GetRunningContainers - if len(c.Args()) > 0 { - containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.Args()) } + if len(c.InputArgs) > 0 { + containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.InputArgs) } } else if latest { containerFunc = func() ([]*libpod.Container, error) { lastCtr, err := runtime.GetLatestContainer() @@ -126,7 +120,7 @@ func statsCmd(c *cli.Context) error { initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{}) if err != nil { // when doing "all", dont worry about containers that are not running - if c.Bool("all") && errors.Cause(err) == libpod.ErrCtrRemoved || errors.Cause(err) == libpod.ErrNoSuchCtr || errors.Cause(err) == libpod.ErrCtrStateInvalid { + if c.All && errors.Cause(err) == libpod.ErrCtrRemoved || errors.Cause(err) == libpod.ErrNoSuchCtr || errors.Cause(err) == libpod.ErrCtrStateInvalid { continue } return err @@ -134,7 +128,7 @@ func statsCmd(c *cli.Context) error { containerStats[ctr.ID()] = initialStats } - format := genStatsFormat(c.String("format")) + format := genStatsFormat(c.Format) step := 1 if times == -1 { @@ -168,7 +162,7 @@ func statsCmd(c *cli.Context) error { if err != nil { return err } - if strings.ToLower(format) != formats.JSONString && !c.Bool("no-reset") { + if strings.ToLower(format) != formats.JSONString && !c.NoReset { tm.Clear() tm.MoveCursor(1, 1) tm.Flush() diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index 204515c70..e2c16fe20 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -3,27 +3,18 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - stopFlags = []cli.Flag{ - cli.UintFlag{ - Name: "timeout, time, t", - Usage: "Seconds to wait for stop before killing the container", - Value: libpod.CtrRemoveTimeout, - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Stop all running containers", - }, LatestFlag, - } + stopCommand cliconfig.StopValues stopDescription = ` podman stop @@ -31,36 +22,44 @@ var ( A timeout to forcibly stop the container can also be set but defaults to 10 seconds otherwise. ` - - stopCommand = cli.Command{ - Name: "stop", - Usage: "Stop one or more containers", - Description: stopDescription, - Flags: sortFlags(stopFlags), - Action: stopCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _stopCommand = &cobra.Command{ + Use: "stop", + Short: "Stop one or more containers", + Long: stopDescription, + RunE: func(cmd *cobra.Command, args []string) error { + stopCommand.InputArgs = args + stopCommand.GlobalFlags = MainGlobalOpts + return stopCmd(&stopCommand) + }, + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func stopCmd(c *cli.Context) error { +func init() { + stopCommand.Command = _stopCommand + flags := stopCommand.Flags() + flags.BoolVarP(&stopCommand.All, "all", "a", false, "Stop all running containers") + flags.BoolVarP(&stopCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.UintVar(&stopCommand.Timeout, "time", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") + flags.UintVarP(&stopCommand.Timeout, "timeout", "t", libpod.CtrRemoveTimeout, "Seconds to wait for stop before killing the container") - if err := checkAllAndLatest(c); err != nil { - return err - } + rootCmd.AddCommand(stopCommand.Command) +} + +func stopCmd(c *cliconfig.StopValues) error { - if err := validateFlags(c, stopFlags); err != nil { + if err := checkAllAndLatest(&c.PodmanCommand); err != nil { return err } rootless.SetSkipStorageSetup(true) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - containers, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") if err != nil { if len(containers) == 0 { return err @@ -72,8 +71,8 @@ func stopCmd(c *cli.Context) error { for _, ctr := range containers { con := ctr var stopTimeout uint - if c.IsSet("timeout") { - stopTimeout = c.Uint("timeout") + if c.Flag("timeout").Changed { + stopTimeout = c.Timeout } else { stopTimeout = ctr.StopTimeout() } @@ -92,7 +91,7 @@ func stopCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("stop") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/system.go b/cmd/podman/system.go index 9596252ad..f6b28fee1 100644 --- a/cmd/podman/system.go +++ b/cmd/podman/system.go @@ -1,29 +1,23 @@ package main import ( - "sort" - - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - systemSubCommands = []cli.Command{ - pruneSystemCommand, - } systemDescription = "Manage podman" - systemCommand = cli.Command{ - Name: "system", - Usage: "Manage podman", - Description: systemDescription, - ArgsUsage: "", - Subcommands: getSystemSubCommandsSorted(), - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, + + systemCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "system", + Short: "Manage podman", + Long: systemDescription, + }, } ) -func getSystemSubCommandsSorted() []cli.Command { - systemSubCommands = append(systemSubCommands, getSystemSubCommands()...) - sort.Sort(commandSortedAlpha{systemSubCommands}) - return systemSubCommands +func init() { + systemCommand.AddCommand(getSystemSubCommands()...) + rootCmd.AddCommand(systemCommand.Command) } diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go index 64d291560..c918fbe3b 100644 --- a/cmd/podman/system_prune.go +++ b/cmd/podman/system_prune.go @@ -6,50 +6,50 @@ import ( "os" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + pruneSystemCommand cliconfig.SystemPruneValues pruneSystemDescription = ` podman system prune Remove unused data ` - pruneSystemFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all unused data", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Do not prompt for confirmation", - }, - cli.BoolFlag{ - Name: "volumes", - Usage: "Prune volumes", + _pruneSystemCommand = &cobra.Command{ + Use: "prune", + Short: "Remove unused data", + Long: pruneSystemDescription, + RunE: func(cmd *cobra.Command, args []string) error { + pruneSystemCommand.InputArgs = args + pruneSystemCommand.GlobalFlags = MainGlobalOpts + return pruneSystemCmd(&pruneSystemCommand) }, } - pruneSystemCommand = cli.Command{ - Name: "prune", - Usage: "Remove unused data", - Description: pruneSystemDescription, - Action: pruneSystemCmd, - OnUsageError: usageErrorHandler, - Flags: pruneSystemFlags, - } ) -func pruneSystemCmd(c *cli.Context) error { +func init() { + + pruneSystemCommand.Command = _pruneSystemCommand + flags := pruneSystemCommand.Flags() + flags.BoolVarP(&pruneSystemCommand.All, "all", "a", false, "Remove all unused data") + flags.BoolVarP(&pruneSystemCommand.Force, "force", "f", false, "Do not prompt for confirmation") + flags.BoolVar(&pruneSystemCommand.Volume, "volumes", false, "Prune volumes") + +} + +func pruneSystemCmd(c *cliconfig.SystemPruneValues) error { // Prompt for confirmation if --force is not set - if !c.Bool("force") { + if !c.Force { reader := bufio.NewReader(os.Stdin) volumeString := "" - if c.Bool("volumes") { + if c.Volume { volumeString = ` - all volumes not used by at least one container` } @@ -68,7 +68,7 @@ Are you sure you want to continue? [y/N] `, volumeString) } } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -90,7 +90,7 @@ Are you sure you want to continue? [y/N] `, volumeString) // Call prune; if any cids are returned, print them and then // return err in case an error also came up - pruneCids, err := runtime.PruneImages(c.Bool("all")) + pruneCids, err := runtime.PruneImages(c.All) if len(pruneCids) > 0 { fmt.Println("Deleted Images") for _, cid := range pruneCids { diff --git a/cmd/podman/tag.go b/cmd/podman/tag.go index d19cf69a2..2c7cf4abb 100644 --- a/cmd/podman/tag.go +++ b/cmd/podman/tag.go @@ -1,29 +1,41 @@ package main import ( + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + tagCommand cliconfig.TagValues + tagDescription = "Adds one or more additional names to locally-stored image" - tagCommand = cli.Command{ - Name: "tag", - Usage: "Add an additional name to a local image", - Description: tagDescription, - Action: tagCmd, - ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", - OnUsageError: usageErrorHandler, + _tagCommand = &cobra.Command{ + Use: "tag", + Short: "Add an additional name to a local image", + Long: tagDescription, + RunE: func(cmd *cobra.Command, args []string) error { + tagCommand.InputArgs = args + tagCommand.GlobalFlags = MainGlobalOpts + return tagCmd(&tagCommand) + }, + Example: "IMAGE-NAME [IMAGE-NAME ...]", } ) -func tagCmd(c *cli.Context) error { - args := c.Args() +func init() { + tagCommand.Command = _tagCommand + rootCmd.AddCommand(tagCommand.Command) + +} + +func tagCmd(c *cliconfig.TagValues) error { + args := c.InputArgs if len(args) < 2 { return errors.Errorf("image name and at least one new name must be specified") } - runtime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not create runtime") } diff --git a/cmd/podman/top.go b/cmd/podman/top.go index 3012265ea..a03830ee5 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -6,11 +6,12 @@ import ( "strings" "text/tabwriter" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) func getDescriptorString() string { @@ -24,13 +25,7 @@ Format Descriptors: } var ( - topFlags = []cli.Flag{ - LatestFlag, - cli.BoolFlag{ - Name: "list-descriptors", - Hidden: true, - }, - } + topCommand cliconfig.TopValues topDescription = fmt.Sprintf(`Display the running processes of the container. Specify format descriptors to alter the output. You may run "podman top -l pid pcpu seccomp" to print the process ID, the CPU percentage and the seccomp mode of each process of @@ -38,24 +33,35 @@ the latest container. %s `, getDescriptorString()) - topCommand = cli.Command{ - Name: "top", - Usage: "Display the running processes of a container", - Description: topDescription, - Flags: sortFlags(topFlags), - Action: topCmd, - ArgsUsage: "CONTAINER-NAME [format descriptors]", - SkipArgReorder: true, - OnUsageError: usageErrorHandler, + _topCommand = &cobra.Command{ + Use: "top", + Short: "Display the running processes of a container", + Long: topDescription, + RunE: func(cmd *cobra.Command, args []string) error { + topCommand.InputArgs = args + topCommand.GlobalFlags = MainGlobalOpts + return topCmd(&topCommand) + }, + Example: "CONTAINER-NAME [format descriptors]", } ) -func topCmd(c *cli.Context) error { +func init() { + topCommand.Command = _topCommand + flags := topCommand.Flags() + flags.BoolVar(&topCommand.ListDescriptors, "list-descriptors", false, "") + flags.MarkHidden("list-descriptors") + flags.BoolVarP(&topCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + rootCmd.AddCommand(topCommand.Command) +} + +func topCmd(c *cliconfig.TopValues) error { var container *libpod.Container var err error - args := c.Args() + args := c.InputArgs - if c.Bool("list-descriptors") { + if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { return err @@ -64,22 +70,19 @@ func topCmd(c *cli.Context) error { return nil } - if len(args) < 1 && !c.Bool("latest") { + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide the name or id of a running container") } - if err := validateFlags(c, topFlags); err != nil { - return err - } rootless.SetSkipStorageSetup(true) - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) var descriptors []string - if c.Bool("latest") { + if c.Latest { descriptors = args container, err = runtime.GetLatestContainer() } else { diff --git a/cmd/podman/trust.go b/cmd/podman/trust.go index a99be6ba2..6304586c3 100644 --- a/cmd/podman/trust.go +++ b/cmd/podman/trust.go @@ -1,369 +1,21 @@ package main import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "sort" - "strings" - - "github.com/containers/image/types" - "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/trust" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) var ( - setTrustFlags = []cli.Flag{ - cli.StringFlag{ - Name: "type, t", - Usage: "Trust type, accept values: signedBy(default), accept, reject.", - Value: "signedBy", - }, - cli.StringSliceFlag{ - Name: "pubkeysfile, f", - Usage: `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.`, - }, - cli.StringFlag{ - Name: "policypath", - Hidden: true, - }, - } - showTrustFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "raw", - Usage: "Output raw policy file", + trustCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "trust", + Short: "Manage container image trust policy", + Long: "podman image trust command", }, - cli.BoolFlag{ - Name: "json, j", - Usage: "Output as json", - }, - cli.StringFlag{ - Name: "policypath", - Hidden: true, - }, - cli.StringFlag{ - Name: "registrypath", - Hidden: true, - }, - } - - setTrustDescription = "Set default trust policy or add a new trust policy for a registry" - setTrustCommand = cli.Command{ - Name: "set", - Usage: "Set default trust policy or a new trust policy for a registry", - Description: setTrustDescription, - Flags: sortFlags(setTrustFlags), - ArgsUsage: "default | REGISTRY[/REPOSITORY]", - Action: setTrustCmd, - OnUsageError: usageErrorHandler, - } - - showTrustDescription = "Display trust policy for the system" - showTrustCommand = cli.Command{ - Name: "show", - Usage: "Display trust policy for the system", - Description: showTrustDescription, - Flags: sortFlags(showTrustFlags), - Action: showTrustCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - OnUsageError: usageErrorHandler, - } - - trustSubCommands = []cli.Command{ - setTrustCommand, - showTrustCommand, - } - - trustDescription = fmt.Sprintf(`Manages the trust policy of the host system. (%s) - Trust policy describes a registry scope that must be signed by public keys.`, getDefaultPolicyPath()) - trustCommand = cli.Command{ - Name: "trust", - Usage: "Manage container image trust policy", - Description: trustDescription, - ArgsUsage: "{set,show} ...", - Subcommands: trustSubCommands, - OnUsageError: usageErrorHandler, } ) -func showTrustCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - - var ( - policyPath string - systemRegistriesDirPath string - outjson interface{} - ) - if c.IsSet("policypath") { - policyPath = c.String("policypath") - } else { - policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) - } - policyContent, err := ioutil.ReadFile(policyPath) - if err != nil { - return errors.Wrapf(err, "unable to read %s", policyPath) - } - if c.IsSet("registrypath") { - systemRegistriesDirPath = c.String("registrypath") - } else { - systemRegistriesDirPath = trust.RegistriesDirPath(runtime.SystemContext()) - } - - if c.Bool("raw") { - _, err := os.Stdout.Write(policyContent) - if err != nil { - return errors.Wrap(err, "could not read trust policies") - } - return nil - } - - policyContentStruct, err := trust.GetPolicy(policyPath) - if err != nil { - return errors.Wrapf(err, "could not read trust policies") - } - - if c.Bool("json") { - policyJSON, err := getPolicyJSON(policyContentStruct, systemRegistriesDirPath) - if err != nil { - return errors.Wrapf(err, "could not show trust policies in JSON format") - } - outjson = policyJSON - out := formats.JSONStruct{Output: outjson} - return formats.Writer(out).Out() - } - - showOutputMap, err := getPolicyShowOutput(policyContentStruct, systemRegistriesDirPath) - if err != nil { - return errors.Wrapf(err, "could not show trust policies") - } - out := formats.StdoutTemplateArray{Output: showOutputMap, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} - return formats.Writer(out).Out() -} - -func setTrustCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - var ( - policyPath string - policyContentStruct trust.PolicyContent - newReposContent []trust.RepoContent - ) - args := c.Args() - if len(args) != 1 { - return errors.Errorf("default or a registry name must be specified") - } - valid, err := image.IsValidImageURI(args[0]) - if err != nil || !valid { - return errors.Wrapf(err, "invalid image uri %s", args[0]) - } - - trusttype := c.String("type") - if !isValidTrustType(trusttype) { - return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", trusttype) - } - if trusttype == "accept" { - trusttype = "insecureAcceptAnything" - } - - pubkeysfile := c.StringSlice("pubkeysfile") - if len(pubkeysfile) == 0 && trusttype == "signedBy" { - return errors.Errorf("At least one public key must be defined for type 'signedBy'") - } - - if c.IsSet("policypath") { - policyPath = c.String("policypath") - } else { - policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) - } - _, err = os.Stat(policyPath) - if !os.IsNotExist(err) { - policyContent, err := ioutil.ReadFile(policyPath) - if err != nil { - return errors.Wrapf(err, "unable to read %s", policyPath) - } - if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { - return errors.Errorf("could not read trust policies") - } - } - if len(pubkeysfile) != 0 { - for _, filepath := range pubkeysfile { - newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype, KeyType: "GPGKeys", KeyPath: filepath}) - } - } else { - newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype}) - } - if args[0] == "default" { - policyContentStruct.Default = newReposContent - } else { - if len(policyContentStruct.Default) == 0 { - return errors.Errorf("Default trust policy must be set.") - } - registryExists := false - for transport, transportval := range policyContentStruct.Transports { - _, registryExists = transportval[args[0]] - if registryExists { - policyContentStruct.Transports[transport][args[0]] = newReposContent - break - } - } - if !registryExists { - if policyContentStruct.Transports == nil { - policyContentStruct.Transports = make(map[string]trust.RepoMap) - } - if policyContentStruct.Transports["docker"] == nil { - policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent) - } - policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...) - } - } - - data, err := json.MarshalIndent(policyContentStruct, "", " ") - if err != nil { - return errors.Wrapf(err, "error setting trust policy") - } - err = ioutil.WriteFile(policyPath, data, 0644) - if err != nil { - return errors.Wrapf(err, "error setting trust policy") - } - return nil -} - -func sortShowOutputMapKey(m map[string]trust.ShowOutput) []string { - keys := make([]string, len(m)) - i := 0 - for k := range m { - keys[i] = k - i++ - } - sort.Strings(keys) - return keys -} - -func isValidTrustType(t string) bool { - if t == "accept" || t == "insecureAcceptAnything" || t == "reject" || t == "signedBy" { - return true - } - return false -} - -func getDefaultPolicyPath() string { - return trust.DefaultPolicyPath(&types.SystemContext{}) -} - -func getPolicyJSON(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) { - registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) - if err != nil { - return nil, err - } - - policyJSON := make(map[string]map[string]interface{}) - if len(policyContentStruct.Default) > 0 { - policyJSON["* (default)"] = make(map[string]interface{}) - policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type - } - for transname, transval := range policyContentStruct.Transports { - for repo, repoval := range transval { - policyJSON[repo] = make(map[string]interface{}) - policyJSON[repo]["type"] = repoval[0].Type - policyJSON[repo]["transport"] = transname - keyarr := []string{} - uids := []string{} - for _, repoele := range repoval { - if len(repoele.KeyPath) > 0 { - keyarr = append(keyarr, repoele.KeyPath) - uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) - } - if len(repoele.KeyData) > 0 { - keyarr = append(keyarr, string(repoele.KeyData)) - uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) - } - } - policyJSON[repo]["keys"] = keyarr - policyJSON[repo]["sigstore"] = "" - registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) - if registryNamespace != nil { - policyJSON[repo]["sigstore"] = registryNamespace.SigStore - } - } - } - return policyJSON, nil -} - -var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} - -func trustTypeDescription(trustType string) string { - trustDescription, exist := typeDescription[trustType] - if !exist { - logrus.Warnf("invalid trust type %s", trustType) - } - return trustDescription -} - -func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]interface{}, error) { - var output []interface{} - - registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) - if err != nil { - return nil, err - } - - trustShowOutputMap := make(map[string]trust.ShowOutput) - if len(policyContentStruct.Default) > 0 { - defaultPolicyStruct := trust.ShowOutput{ - Repo: "default", - Trusttype: trustTypeDescription(policyContentStruct.Default[0].Type), - } - trustShowOutputMap["* (default)"] = defaultPolicyStruct - } - for _, transval := range policyContentStruct.Transports { - for repo, repoval := range transval { - tempTrustShowOutput := trust.ShowOutput{ - Repo: repo, - Trusttype: repoval[0].Type, - } - keyarr := []string{} - uids := []string{} - for _, repoele := range repoval { - if len(repoele.KeyPath) > 0 { - keyarr = append(keyarr, repoele.KeyPath) - uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) - } - if len(repoele.KeyData) > 0 { - keyarr = append(keyarr, string(repoele.KeyData)) - uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) - } - } - tempTrustShowOutput.GPGid = strings.Join(uids, ", ") - - registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) - if registryNamespace != nil { - tempTrustShowOutput.Sigstore = registryNamespace.SigStore - } - trustShowOutputMap[repo] = tempTrustShowOutput - } - } - - sortedRepos := sortShowOutputMapKey(trustShowOutputMap) - for _, reponame := range sortedRepos { - showOutput, exists := trustShowOutputMap[reponame] - if exists { - output = append(output, interface{}(showOutput)) - } - } - return output, nil +func init() { + trustCommand.AddCommand(getTrustSubCommands()...) + imageCommand.AddCommand(trustCommand.Command) } diff --git a/cmd/podman/trust_set_show.go b/cmd/podman/trust_set_show.go new file mode 100644 index 000000000..cd9438220 --- /dev/null +++ b/cmd/podman/trust_set_show.go @@ -0,0 +1,343 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + "os" + "sort" + "strings" + + "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/formats" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/trust" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + setTrustCommand cliconfig.SetTrustValues + showTrustCommand cliconfig.ShowTrustValues + setTrustDescription = "Set default trust policy or add a new trust policy for a registry" + _setTrustCommand = &cobra.Command{ + Use: "set", + Short: "Set default trust policy or a new trust policy for a registry", + Long: setTrustDescription, + Example: "default | REGISTRY[/REPOSITORY]", + RunE: func(cmd *cobra.Command, args []string) error { + setTrustCommand.InputArgs = args + setTrustCommand.GlobalFlags = MainGlobalOpts + return setTrustCmd(&setTrustCommand) + }, + } + + showTrustDescription = "Display trust policy for the system" + _showTrustCommand = &cobra.Command{ + Use: "show", + Short: "Display trust policy for the system", + Long: showTrustDescription, + RunE: func(cmd *cobra.Command, args []string) error { + showTrustCommand.InputArgs = args + showTrustCommand.GlobalFlags = MainGlobalOpts + return showTrustCmd(&showTrustCommand) + }, + Example: "", + } +) + +func init() { + + setTrustCommand.Command = _setTrustCommand + showTrustCommand.Command = _showTrustCommand + setFlags := setTrustCommand.Flags() + setFlags.StringVar(&setTrustCommand.PolicyPath, "policypath", "", "") + setFlags.MarkHidden("policypath") + setFlags.StringSliceVarP(&setTrustCommand.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(&setTrustCommand.TrustType, "type", "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") + + showFlags := showTrustCommand.Flags() + showFlags.BoolVarP(&showTrustCommand.Json, "json", "j", false, "Output as json") + showFlags.StringVar(&showTrustCommand.PolicyPath, "policypath", "", "") + showFlags.BoolVar(&showTrustCommand.Raw, "raw", false, "Output raw policy file") + showFlags.MarkHidden("policypath") + showFlags.StringVar(&showTrustCommand.RegistryPath, "registrypath", "", "") + showFlags.MarkHidden("registrypath") +} + +func showTrustCmd(c *cliconfig.ShowTrustValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + + var ( + policyPath string + systemRegistriesDirPath string + outjson interface{} + ) + if c.Flag("policypath").Changed { + policyPath = c.PolicyPath + } else { + policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) + } + policyContent, err := ioutil.ReadFile(policyPath) + if err != nil { + return errors.Wrapf(err, "unable to read %s", policyPath) + } + if c.Flag("registrypath").Changed { + systemRegistriesDirPath = c.RegistryPath + } else { + systemRegistriesDirPath = trust.RegistriesDirPath(runtime.SystemContext()) + } + + if c.Raw { + _, err := os.Stdout.Write(policyContent) + if err != nil { + return errors.Wrap(err, "could not read raw trust policies") + } + return nil + } + + policyContentStruct, err := trust.GetPolicy(policyPath) + if err != nil { + return errors.Wrapf(err, "could not read trust policies") + } + + if c.Json { + policyJSON, err := getPolicyJSON(policyContentStruct, systemRegistriesDirPath) + if err != nil { + return errors.Wrapf(err, "could not show trust policies in JSON format") + } + outjson = policyJSON + out := formats.JSONStruct{Output: outjson} + return formats.Writer(out).Out() + } + + showOutputMap, err := getPolicyShowOutput(policyContentStruct, systemRegistriesDirPath) + if err != nil { + return errors.Wrapf(err, "could not show trust policies") + } + out := formats.StdoutTemplateArray{Output: showOutputMap, Template: "{{.Repo}}\t{{.Trusttype}}\t{{.GPGid}}\t{{.Sigstore}}"} + return formats.Writer(out).Out() +} + +func setTrustCmd(c *cliconfig.SetTrustValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + var ( + policyPath string + policyContentStruct trust.PolicyContent + newReposContent []trust.RepoContent + ) + args := c.InputArgs + if len(args) != 1 { + return errors.Errorf("default or a registry name must be specified") + } + valid, err := image.IsValidImageURI(args[0]) + if err != nil || !valid { + return errors.Wrapf(err, "invalid image uri %s", args[0]) + } + + trusttype := c.TrustType + if !isValidTrustType(trusttype) { + return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", trusttype) + } + if trusttype == "accept" { + trusttype = "insecureAcceptAnything" + } + + pubkeysfile := c.PubKeysFile + if len(pubkeysfile) == 0 && trusttype == "signedBy" { + return errors.Errorf("At least one public key must be defined for type 'signedBy'") + } + + if c.Flag("policypath").Changed { + policyPath = c.PolicyPath + } else { + policyPath = trust.DefaultPolicyPath(runtime.SystemContext()) + } + _, err = os.Stat(policyPath) + if !os.IsNotExist(err) { + policyContent, err := ioutil.ReadFile(policyPath) + if err != nil { + return errors.Wrapf(err, "unable to read %s", policyPath) + } + if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil { + return errors.Errorf("could not read trust policies") + } + } + if len(pubkeysfile) != 0 { + for _, filepath := range pubkeysfile { + newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype, KeyType: "GPGKeys", KeyPath: filepath}) + } + } else { + newReposContent = append(newReposContent, trust.RepoContent{Type: trusttype}) + } + if args[0] == "default" { + policyContentStruct.Default = newReposContent + } else { + if len(policyContentStruct.Default) == 0 { + return errors.Errorf("Default trust policy must be set.") + } + registryExists := false + for transport, transportval := range policyContentStruct.Transports { + _, registryExists = transportval[args[0]] + if registryExists { + policyContentStruct.Transports[transport][args[0]] = newReposContent + break + } + } + if !registryExists { + if policyContentStruct.Transports == nil { + policyContentStruct.Transports = make(map[string]trust.RepoMap) + } + if policyContentStruct.Transports["docker"] == nil { + policyContentStruct.Transports["docker"] = make(map[string][]trust.RepoContent) + } + policyContentStruct.Transports["docker"][args[0]] = append(policyContentStruct.Transports["docker"][args[0]], newReposContent...) + } + } + + data, err := json.MarshalIndent(policyContentStruct, "", " ") + if err != nil { + return errors.Wrapf(err, "error setting trust policy") + } + err = ioutil.WriteFile(policyPath, data, 0644) + if err != nil { + return errors.Wrapf(err, "error setting trust policy") + } + return nil +} + +func sortShowOutputMapKey(m map[string]trust.ShowOutput) []string { + keys := make([]string, len(m)) + i := 0 + for k := range m { + keys[i] = k + i++ + } + sort.Strings(keys) + return keys +} + +func isValidTrustType(t string) bool { + if t == "accept" || t == "insecureAcceptAnything" || t == "reject" || t == "signedBy" { + return true + } + return false +} + +func getDefaultPolicyPath() string { + return trust.DefaultPolicyPath(&types.SystemContext{}) +} + +func getPolicyJSON(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) { + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, err + } + + policyJSON := make(map[string]map[string]interface{}) + if len(policyContentStruct.Default) > 0 { + policyJSON["* (default)"] = make(map[string]interface{}) + policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type + } + for transname, transval := range policyContentStruct.Transports { + for repo, repoval := range transval { + policyJSON[repo] = make(map[string]interface{}) + policyJSON[repo]["type"] = repoval[0].Type + policyJSON[repo]["transport"] = transname + keyarr := []string{} + uids := []string{} + for _, repoele := range repoval { + if len(repoele.KeyPath) > 0 { + keyarr = append(keyarr, repoele.KeyPath) + uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) + } + if len(repoele.KeyData) > 0 { + keyarr = append(keyarr, string(repoele.KeyData)) + uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) + } + } + policyJSON[repo]["keys"] = keyarr + policyJSON[repo]["sigstore"] = "" + registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) + if registryNamespace != nil { + policyJSON[repo]["sigstore"] = registryNamespace.SigStore + } + } + } + return policyJSON, nil +} + +var typeDescription = map[string]string{"insecureAcceptAnything": "accept", "signedBy": "signed", "reject": "reject"} + +func trustTypeDescription(trustType string) string { + trustDescription, exist := typeDescription[trustType] + if !exist { + logrus.Warnf("invalid trust type %s", trustType) + } + return trustDescription +} + +func getPolicyShowOutput(policyContentStruct trust.PolicyContent, systemRegistriesDirPath string) ([]interface{}, error) { + var output []interface{} + + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return nil, err + } + + trustShowOutputMap := make(map[string]trust.ShowOutput) + if len(policyContentStruct.Default) > 0 { + defaultPolicyStruct := trust.ShowOutput{ + Repo: "default", + Trusttype: trustTypeDescription(policyContentStruct.Default[0].Type), + } + trustShowOutputMap["* (default)"] = defaultPolicyStruct + } + for _, transval := range policyContentStruct.Transports { + for repo, repoval := range transval { + tempTrustShowOutput := trust.ShowOutput{ + Repo: repo, + Trusttype: repoval[0].Type, + } + keyarr := []string{} + uids := []string{} + for _, repoele := range repoval { + if len(repoele.KeyPath) > 0 { + keyarr = append(keyarr, repoele.KeyPath) + uids = append(uids, trust.GetGPGIdFromKeyPath(repoele.KeyPath)...) + } + if len(repoele.KeyData) > 0 { + keyarr = append(keyarr, string(repoele.KeyData)) + uids = append(uids, trust.GetGPGIdFromKeyData(string(repoele.KeyData))...) + } + } + tempTrustShowOutput.GPGid = strings.Join(uids, ", ") + + registryNamespace := trust.HaveMatchRegistry(repo, registryConfigs) + if registryNamespace != nil { + tempTrustShowOutput.Sigstore = registryNamespace.SigStore + } + trustShowOutputMap[repo] = tempTrustShowOutput + } + } + + sortedRepos := sortShowOutputMapKey(trustShowOutputMap) + for _, reponame := range sortedRepos { + showOutput, exists := trustShowOutputMap[reponame] + if exists { + output = append(output, interface{}(showOutput)) + } + } + return output, nil +} diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go index ab6925e65..4622e8276 100644 --- a/cmd/podman/umount.go +++ b/cmd/podman/umount.go @@ -3,60 +3,62 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - umountFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Umount all of the currently mounted containers", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Force the complete umount all of the currently mounted containers", - }, - LatestFlag, - } - - description = ` + umountCommand cliconfig.UmountValues + description = ` Container storage increments a mount counter each time a container is mounted. When a container is unmounted, the mount counter is decremented and the container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount. An unmount can be forced with the --force flag. ` - umountCommand = cli.Command{ - Name: "umount", - Aliases: []string{"unmount"}, - Usage: "Unmount working container's root filesystem", - Description: description, - Flags: sortFlags(umountFlags), - Action: umountCmd, - ArgsUsage: "CONTAINER-NAME-OR-ID", - OnUsageError: usageErrorHandler, + _umountCommand = &cobra.Command{ + Use: "umount", + Aliases: []string{"unmount"}, + Short: "Unmounts working container's root filesystem", + Long: description, + RunE: func(cmd *cobra.Command, args []string) error { + umountCommand.InputArgs = args + umountCommand.GlobalFlags = MainGlobalOpts + return umountCmd(&umountCommand) + }, + Example: "CONTAINER-NAME-OR-ID", } ) -func umountCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) +func init() { + umountCommand.Command = _umountCommand + flags := umountCommand.Flags() + flags.BoolVarP(&umountCommand.All, "all", "a", false, "Umount all of the currently mounted containers") + flags.BoolVarP(&umountCommand.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers") + flags.BoolVarP(&umountCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + rootCmd.AddCommand(umountCommand.Command) +} + +func umountCmd(c *cliconfig.UmountValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - force := c.Bool("force") - umountAll := c.Bool("all") - if err := checkAllAndLatest(c); err != nil { + force := c.Force + umountAll := c.All + if err := checkAllAndLatest(&c.PodmanCommand); err != nil { return err } - containers, err := getAllOrLatestContainers(c, runtime, -1, "all") + containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { if len(containers) == 0 { return err diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index 91b5fda33..fb3a7b57a 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -3,38 +3,45 @@ package main import ( "os" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( - unpauseFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Unpause all paused containers", - }, - } + unpauseCommand cliconfig.UnpauseValues + unpauseDescription = ` podman unpause Unpauses one or more running containers. The container name or ID can be used. ` - unpauseCommand = cli.Command{ - Name: "unpause", - Usage: "Unpause the processes in one or more containers", - Description: unpauseDescription, - Flags: unpauseFlags, - Action: unpauseCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + _unpauseCommand = &cobra.Command{ + Use: "unpause", + Short: "Unpause the processes in one or more containers", + Long: unpauseDescription, + RunE: func(cmd *cobra.Command, args []string) error { + unpauseCommand.InputArgs = args + unpauseCommand.GlobalFlags = MainGlobalOpts + return unpauseCmd(&unpauseCommand) + }, + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func unpauseCmd(c *cli.Context) error { +func init() { + unpauseCommand.Command = _unpauseCommand + flags := unpauseCommand.Flags() + flags.BoolVarP(&unpauseCommand.All, "all", "a", false, "Unpause all paused containers") + + rootCmd.AddCommand(unpauseCommand.Command) +} + +func unpauseCmd(c *cliconfig.UnpauseValues) error { var ( unpauseContainers []*libpod.Container unpauseFuncs []shared.ParallelWorkerInput @@ -43,18 +50,18 @@ func unpauseCmd(c *cli.Context) error { return errors.New("unpause is not supported for rootless containers") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - args := c.Args() - if len(args) < 1 && !c.Bool("all") { + args := c.InputArgs + if len(args) < 1 && !c.All { return errors.Errorf("you must provide at least one container name or id") } - if c.Bool("all") { - cs, err := getAllOrLatestContainers(c, runtime, libpod.ContainerStatePaused, "paused") + if c.All { + cs, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStatePaused, "paused") if err != nil { return err } @@ -84,7 +91,7 @@ func unpauseCmd(c *cli.Context) error { maxWorkers := shared.Parallelize("unpause") if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalInt("max-workers") + maxWorkers = c.GlobalFlags.MaxWorks } logrus.Debugf("Setting maximum workers to %d", maxWorkers) diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index a59535b43..744d010d5 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -6,12 +6,12 @@ import ( "os" gosignal "os/signal" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/term" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" "golang.org/x/crypto/ssh/terminal" "k8s.io/client-go/tools/remotecommand" ) @@ -158,13 +158,10 @@ func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { return bytes, err } -func checkMutuallyExclusiveFlags(c *cli.Context) error { +func checkMutuallyExclusiveFlags(c *cliconfig.PodmanCommand) error { if err := checkAllAndLatest(c); err != nil { return err } - if err := validateFlags(c, startFlags); err != nil { - return err - } return nil } @@ -174,8 +171,8 @@ func checkMutuallyExclusiveFlags(c *cli.Context) error { // will hold all of the successful pods, and error will hold the last error. // The remaining errors will be logged. On success, pods will hold all pods and // error will be nil. -func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error) { - args := c.Args() +func getPodsFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpod.Pod, error) { + args := c.InputArgs var pods []*libpod.Pod var lastError error var err error @@ -209,8 +206,8 @@ func getPodsFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Pod, error return pods, lastError } -func getVolumesFromContext(c *cli.Context, r *libpod.Runtime) ([]*libpod.Volume, error) { - args := c.Args() +func getVolumesFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpod.Volume, error) { + args := c.InputArgs var ( vols []*libpod.Volume lastError error diff --git a/cmd/podman/varlink.go b/cmd/podman/varlink.go index 38ce77415..2b76e034e 100644 --- a/cmd/podman/varlink.go +++ b/cmd/podman/varlink.go @@ -5,55 +5,60 @@ package main import ( "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/pkg/varlinkapi" "github.com/containers/libpod/version" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" "github.com/varlink/go/varlink" ) var ( + varlinkCommand cliconfig.VarlinkValues varlinkDescription = ` podman varlink run varlink interface ` - varlinkFlags = []cli.Flag{ - cli.IntFlag{ - Name: "timeout, t", - Usage: "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout.", - Value: 1000, + _varlinkCommand = &cobra.Command{ + Use: "varlink", + Short: "Run varlink interface", + Long: varlinkDescription, + RunE: func(cmd *cobra.Command, args []string) error { + varlinkCommand.InputArgs = args + varlinkCommand.GlobalFlags = MainGlobalOpts + return varlinkCmd(&varlinkCommand) }, - } - varlinkCommand = &cli.Command{ - Name: "varlink", - Usage: "Run varlink interface", - Description: varlinkDescription, - Flags: sortFlags(varlinkFlags), - Action: varlinkCmd, - ArgsUsage: "VARLINK_URI", - OnUsageError: usageErrorHandler, + Example: "VARLINK_URI", } ) -func varlinkCmd(c *cli.Context) error { - args := c.Args() +func init() { + varlinkCommand.Command = _varlinkCommand + flags := varlinkCommand.Flags() + flags.Int64VarP(&varlinkCommand.Timeout, "timeout", "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") + + rootCmd.AddCommand(varlinkCommand.Command) +} + +func varlinkCmd(c *cliconfig.VarlinkValues) error { + args := c.InputArgs if len(args) < 1 { return errors.Errorf("you must provide a varlink URI") } - timeout := time.Duration(c.Int64("timeout")) * time.Millisecond + timeout := time.Duration(c.Timeout) * time.Millisecond // Create a single runtime for varlink - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(c, runtime)} + var varlinkInterfaces = []*iopodman.VarlinkInterface{varlinkapi.New(&c.PodmanCommand, runtime)} // Register varlink service. The metadata can be retrieved with: // $ varlink info [varlink address URI] service, err := varlink.NewService( diff --git a/cmd/podman/varlink_dummy.go b/cmd/podman/varlink_dummy.go index ec4bbb208..8d7a7e8ca 100644 --- a/cmd/podman/varlink_dummy.go +++ b/cmd/podman/varlink_dummy.go @@ -3,7 +3,7 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" ) -var varlinkCommand *cli.Command +var varlinkCommand *cliconfig.PodmanCommand diff --git a/cmd/podman/version.go b/cmd/podman/version.go index ce773ee2e..0e7cd43d5 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -6,20 +6,41 @@ import ( "text/tabwriter" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) +var ( + versionCommand cliconfig.VersionValues + _versionCommand = &cobra.Command{ + Use: "version", + Short: "Display the Podman Version Information", + RunE: func(cmd *cobra.Command, args []string) error { + versionCommand.InputArgs = args + versionCommand.GlobalFlags = MainGlobalOpts + return versionCmd(&versionCommand) + }, + } +) + +func init() { + versionCommand.Command = _versionCommand + flags := versionCommand.Flags() + flags.StringVarP(&versionCommand.Format, "format", "f", "", "Change the output format to JSON or a Go template") + rootCmd.AddCommand(versionCommand.Command) +} + // versionCmd gets and prints version info for version command -func versionCmd(c *cli.Context) error { +func versionCmd(c *cliconfig.VersionValues) error { output, err := libpod.GetVersion() if err != nil { errors.Wrapf(err, "unable to determine version") } - versionOutputFormat := c.String("format") + versionOutputFormat := c.Format if versionOutputFormat != "" { var out formats.Writer switch versionOutputFormat { @@ -46,19 +67,3 @@ func versionCmd(c *cli.Context) error { fmt.Fprintf(w, "OS/Arch:\t%s\n", output.OsArch) return nil } - -// Cli command to print out the full version of podman -var ( - versionCommand = cli.Command{ - Name: "version", - Usage: "Display the Podman Version Information", - Action: versionCmd, - Flags: versionFlags, - } - versionFlags = []cli.Flag{ - cli.StringFlag{ - Name: "format, f", - Usage: "Change the output format to JSON or a Go template", - }, - } -) diff --git a/cmd/podman/volume.go b/cmd/podman/volume.go index 913592e74..bae778e52 100644 --- a/cmd/podman/volume.go +++ b/cmd/podman/volume.go @@ -1,26 +1,23 @@ package main import ( - "github.com/urfave/cli" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" ) -var ( - volumeDescription = `Manage volumes. +var volumeDescription = `Manage volumes. Volumes are created in and can be shared between containers.` - volumeSubCommands = []cli.Command{ - volumeCreateCommand, - volumeLsCommand, - volumeRmCommand, - volumeInspectCommand, - volumePruneCommand, - } - volumeCommand = cli.Command{ - Name: "volume", - Usage: "Manage volumes", - Description: volumeDescription, - UseShortOptionHandling: true, - Subcommands: volumeSubCommands, - } -) +var volumeCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "volume", + Short: "Manage volumes", + Long: volumeDescription, + }, +} + +func init() { + volumeCommand.AddCommand(getVolumeSubCommands()...) + rootCmd.AddCommand(volumeCommand.Command) +} diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go index 0b5f8d1e3..e0ff4c341 100644 --- a/cmd/podman/volume_create.go +++ b/cmd/podman/volume_create.go @@ -3,75 +3,70 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumeCreateDescription = ` +var ( + volumeCreateCommand cliconfig.VolumeCreateValues + volumeCreateDescription = ` podman volume create Creates a new volume. If using the default driver, "local", the volume will be created at.` -var volumeCreateFlags = []cli.Flag{ - cli.StringFlag{ - Name: "driver", - Usage: "Specify volume driver name (default local)", - }, - cli.StringSliceFlag{ - Name: "label, l", - Usage: "Set metadata for a volume (default [])", - }, - cli.StringSliceFlag{ - Name: "opt, o", - Usage: "Set driver specific options (default [])", - }, -} + _volumeCreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a new volume", + Long: volumeCreateDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeCreateCommand.InputArgs = args + volumeCreateCommand.GlobalFlags = MainGlobalOpts + return volumeCreateCmd(&volumeCreateCommand) + }, + Example: "[VOLUME-NAME]", + } +) + +func init() { + volumeCreateCommand.Command = _volumeCreateCommand + flags := volumeCreateCommand.Flags() + flags.StringVar(&volumeCreateCommand.Driver, "driver", "", "Specify volume driver name (default local)") + flags.StringSliceVarP(&volumeCreateCommand.Label, "label", "l", []string{}, "Set metadata for a volume (default [])") + flags.StringSliceVarP(&volumeCreateCommand.Opt, "opt", "o", []string{}, "Set driver specific options (default [])") -var volumeCreateCommand = cli.Command{ - Name: "create", - Usage: "Create a new volume", - Description: volumeCreateDescription, - Flags: volumeCreateFlags, - Action: volumeCreateCmd, - SkipArgReorder: true, - ArgsUsage: "[VOLUME-NAME]", - UseShortOptionHandling: true, } -func volumeCreateCmd(c *cli.Context) error { +func volumeCreateCmd(c *cliconfig.VolumeCreateValues) error { var ( options []libpod.VolumeCreateOption err error volName string ) - if err = validateFlags(c, volumeCreateFlags); err != nil { - return err - } - - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 1 { + if len(c.InputArgs) > 1 { return errors.Errorf("too many arguments, create takes at most 1 argument") } - if len(c.Args()) > 0 { - volName = c.Args()[0] + if len(c.InputArgs) > 0 { + volName = c.InputArgs[0] options = append(options, libpod.WithVolumeName(volName)) } - if c.IsSet("driver") { + if c.Flag("driver").Changed { options = append(options, libpod.WithVolumeDriver(c.String("driver"))) } - labels, err := getAllLabels([]string{}, c.StringSlice("label")) + labels, err := getAllLabels([]string{}, c.Label) if err != nil { return errors.Wrapf(err, "unable to process labels") } @@ -79,7 +74,7 @@ func volumeCreateCmd(c *cli.Context) error { options = append(options, libpod.WithVolumeLabels(labels)) } - opts, err := getAllLabels([]string{}, c.StringSlice("opt")) + opts, err := getAllLabels([]string{}, c.Opt) if err != nil { return errors.Wrapf(err, "unable to process options") } diff --git a/cmd/podman/volume_inspect.go b/cmd/podman/volume_inspect.go index 152f1d098..7cb5703da 100644 --- a/cmd/podman/volume_inspect.go +++ b/cmd/podman/volume_inspect.go @@ -1,60 +1,56 @@ package main import ( + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumeInspectDescription = ` +var ( + volumeInspectCommand cliconfig.VolumeInspectValues + volumeInspectDescription = ` podman volume inspect Display detailed information on one or more volumes. Can change the format from JSON to a Go template. ` + _volumeInspectCommand = &cobra.Command{ + Use: "inspect", + Short: "Display detailed information on one or more volumes", + Long: volumeInspectDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeInspectCommand.InputArgs = args + volumeInspectCommand.GlobalFlags = MainGlobalOpts + return volumeInspectCmd(&volumeInspectCommand) + }, + Example: "[VOLUME-NAME ...]", + } +) -var volumeInspectFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Inspect all volumes", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Format volume output using Go template", - Value: "json", - }, -} +func init() { + volumeInspectCommand.Command = _volumeInspectCommand + flags := volumeInspectCommand.Flags() + flags.BoolVarP(&volumeInspectCommand.All, "all", "a", false, "Inspect all volumes") + flags.StringVarP(&volumeInspectCommand.Format, "format", "f", "json", "Format volume output using Go template") -var volumeInspectCommand = cli.Command{ - Name: "inspect", - Usage: "Display detailed information on one or more volumes", - Description: volumeInspectDescription, - Flags: volumeInspectFlags, - Action: volumeInspectCmd, - SkipArgReorder: true, - ArgsUsage: "[VOLUME-NAME ...]", - UseShortOptionHandling: true, } -func volumeInspectCmd(c *cli.Context) error { +func volumeInspectCmd(c *cliconfig.VolumeInspectValues) error { var err error - if err = validateFlags(c, volumeInspectFlags); err != nil { - return err - } - - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) opts := volumeLsOptions{ - Format: c.String("format"), + Format: c.Format, } - vols, lastError := getVolumesFromContext(c, runtime) + vols, lastError := getVolumesFromContext(&c.PodmanCommand, runtime) if lastError != nil { logrus.Errorf("%q", lastError) } diff --git a/cmd/podman/volume_ls.go b/cmd/podman/volume_ls.go index 0f94549ee..78fdfed64 100644 --- a/cmd/podman/volume_ls.go +++ b/cmd/podman/volume_ls.go @@ -4,11 +4,12 @@ import ( "reflect" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) // volumeOptions is the "ls" command options @@ -37,64 +38,57 @@ type volumeLsJSONParams struct { Scope string `json:"scope"` } -var volumeLsDescription = ` +var ( + volumeLsCommand cliconfig.VolumeLsValues + + volumeLsDescription = ` podman volume ls List all available volumes. The output of the volumes can be filtered and the output format can be changed to JSON or a user specified Go template. ` + _volumeLsCommand = &cobra.Command{ + Use: "ls", + Aliases: []string{"list"}, + Short: "List volumes", + Long: volumeLsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeLsCommand.InputArgs = args + volumeLsCommand.GlobalFlags = MainGlobalOpts + return volumeLsCmd(&volumeLsCommand) + }, + } +) -var volumeLsFlags = []cli.Flag{ - cli.StringFlag{ - Name: "filter, f", - Usage: "Filter volume output", - }, - cli.StringFlag{ - Name: "format", - Usage: "Format volume output using Go template", - Value: "table {{.Driver}}\t{{.Name}}", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Print volume output in quiet mode", - }, -} +func init() { + volumeLsCommand.Command = _volumeLsCommand + flags := volumeLsCommand.Flags() -var volumeLsCommand = cli.Command{ - Name: "ls", - Aliases: []string{"list"}, - Usage: "List volumes", - Description: volumeLsDescription, - Flags: volumeLsFlags, - Action: volumeLsCmd, - SkipArgReorder: true, - UseShortOptionHandling: true, + flags.StringVarP(&volumeLsCommand.Filter, "filter", "f", "", "Filter volume output") + flags.StringVar(&volumeLsCommand.Format, "format", "table {{.Driver}}\t{{.Name}}", "Format volume output using Go template") + flags.BoolVarP(&volumeLsCommand.Quiet, "quiet", "q", false, "Print volume output in quiet mode") } -func volumeLsCmd(c *cli.Context) error { - if err := validateFlags(c, volumeLsFlags); err != nil { - return err - } - - runtime, err := libpodruntime.GetRuntime(c) +func volumeLsCmd(c *cliconfig.VolumeLsValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - if len(c.Args()) > 0 { + if len(c.InputArgs) > 0 { return errors.Errorf("too many arguments, ls takes no arguments") } opts := volumeLsOptions{ - Quiet: c.Bool("quiet"), + Quiet: c.Quiet, } opts.Format = genVolLsFormat(c) // Get the filter functions based on any filters set var filterFuncs []libpod.VolumeFilter - if c.String("filter") != "" { - filters := strings.Split(c.String("filter"), ",") + if c.Filter != "" { + filters := strings.Split(c.Filter, ",") for _, f := range filters { filterSplit := strings.Split(f, "=") if len(filterSplit) < 2 { @@ -129,14 +123,14 @@ func volumeLsCmd(c *cli.Context) error { } // generate the template based on conditions given -func genVolLsFormat(c *cli.Context) string { +func genVolLsFormat(c *cliconfig.VolumeLsValues) string { var format string - if c.String("format") != "" { + if c.Format != "" { // "\t" from the command line is not being recognized as a tab // replacing the string "\t" to a tab character if the user passes in "\t" - format = strings.Replace(c.String("format"), `\t`, "\t", -1) + format = strings.Replace(c.Format, `\t`, "\t", -1) } - if c.Bool("quiet") { + if c.Quiet { format = "{{.Name}}" } return format diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go index 41d95f9c7..29dc6ead4 100644 --- a/cmd/podman/volume_prune.go +++ b/cmd/podman/volume_prune.go @@ -7,35 +7,39 @@ import ( "os" "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumePruneDescription = ` +var ( + volumePruneCommand cliconfig.VolumePruneValues + volumePruneDescription = ` podman volume prune Remove all unused volumes. Will prompt for confirmation if not using force. ` + _volumePruneCommand = &cobra.Command{ + Use: "prune", + Short: "Remove all unused volumes", + Long: volumePruneDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumePruneCommand.InputArgs = args + volumePruneCommand.GlobalFlags = MainGlobalOpts + return volumePruneCmd(&volumePruneCommand) + }, + } +) -var volumePruneFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "force, f", - Usage: "Do not prompt for confirmation", - }, -} +func init() { + volumePruneCommand.Command = _volumePruneCommand + flags := volumePruneCommand.Flags() -var volumePruneCommand = cli.Command{ - Name: "prune", - Usage: "Remove all unused volumes", - Description: volumePruneDescription, - Flags: volumePruneFlags, - Action: volumePruneCmd, - SkipArgReorder: true, - UseShortOptionHandling: true, + flags.BoolVarP(&volumePruneCommand.Force, "force", "f", false, "Do not prompt for confirmation") } func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error { @@ -60,20 +64,15 @@ func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error { return lastError } -func volumePruneCmd(c *cli.Context) error { - - if err := validateFlags(c, volumePruneFlags); err != nil { - return err - } - - runtime, err := adapter.GetRuntime(c) +func volumePruneCmd(c *cliconfig.VolumePruneValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) // Prompt for confirmation if --force is not set - if !c.Bool("force") { + if !c.Force { reader := bufio.NewReader(os.Stdin) fmt.Println("WARNING! This will remove all volumes not used by at least one container.") fmt.Print("Are you sure you want to continue? [y/N] ") diff --git a/cmd/podman/volume_rm.go b/cmd/podman/volume_rm.go index 3fb623624..b02a06ed9 100644 --- a/cmd/podman/volume_rm.go +++ b/cmd/podman/volume_rm.go @@ -3,51 +3,47 @@ package main import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) -var volumeRmDescription = ` +var ( + volumeRmCommand cliconfig.VolumeRmValues + volumeRmDescription = ` podman volume rm Remove one or more existing volumes. Will only remove volumes that are not being used by any containers. To remove the volumes anyways, use the --force flag. ` + _volumeRmCommand = &cobra.Command{ + Use: "rm", + Aliases: []string{"remove"}, + Short: "Remove one or more volumes", + Long: volumeRmDescription, + RunE: func(cmd *cobra.Command, args []string) error { + volumeRmCommand.InputArgs = args + volumeRmCommand.GlobalFlags = MainGlobalOpts + return volumeRmCmd(&volumeRmCommand) + }, + Example: "[VOLUME-NAME ...]", + } +) -var volumeRmFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all volumes", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "Remove a volume by force, even if it is being used by a container", - }, -} - -var volumeRmCommand = cli.Command{ - Name: "rm", - Aliases: []string{"remove"}, - Usage: "Remove one or more volumes", - Description: volumeRmDescription, - Flags: volumeRmFlags, - Action: volumeRmCmd, - ArgsUsage: "[VOLUME-NAME ...]", - SkipArgReorder: true, - UseShortOptionHandling: true, +func init() { + volumeRmCommand.Command = _volumeRmCommand + flags := volumeRmCommand.Flags() + flags.BoolVarP(&volumeRmCommand.All, "all", "a", false, "Remove all volumes") + flags.BoolVarP(&volumeRmCommand.Force, "force", "f", false, "Remove a volume by force, even if it is being used by a container") } -func volumeRmCmd(c *cli.Context) error { +func volumeRmCmd(c *cliconfig.VolumeRmValues) error { var err error - if err = validateFlags(c, volumeRmFlags); err != nil { - return err - } - - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -55,9 +51,9 @@ func volumeRmCmd(c *cli.Context) error { ctx := getContext() - vols, lastError := getVolumesFromContext(c, runtime) + vols, lastError := getVolumesFromContext(&c.PodmanCommand, runtime) for _, vol := range vols { - err = runtime.RemoveVolume(ctx, vol, c.Bool("force"), false) + err = runtime.RemoveVolume(ctx, vol, c.Force, false) if err != nil { if lastError != nil { logrus.Errorf("%q", lastError) diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go index 35ad7a662..fa195b7ce 100644 --- a/cmd/podman/wait.go +++ b/cmd/podman/wait.go @@ -5,43 +5,49 @@ import ( "os" "time" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) var ( + waitCommand cliconfig.WaitValues + waitDescription = ` podman wait Block until one or more containers stop and then print their exit codes ` - waitFlags = []cli.Flag{ - cli.UintFlag{ - Name: "interval, i", - Usage: "Milliseconds to wait before polling for completion", - Value: 250, + _waitCommand = &cobra.Command{ + Use: "wait", + Short: "Block on one or more containers", + Long: waitDescription, + RunE: func(cmd *cobra.Command, args []string) error { + waitCommand.InputArgs = args + waitCommand.GlobalFlags = MainGlobalOpts + return waitCmd(&waitCommand) }, - LatestFlag, - } - waitCommand = cli.Command{ - Name: "wait", - Usage: "Block on one or more containers", - Description: waitDescription, - Flags: sortFlags(waitFlags), - Action: waitCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - OnUsageError: usageErrorHandler, + Example: "CONTAINER-NAME [CONTAINER-NAME ...]", } ) -func waitCmd(c *cli.Context) error { - args := c.Args() - if len(args) < 1 && !c.Bool("latest") { +func init() { + waitCommand.Command = _waitCommand + flags := waitCommand.Flags() + flags.UintVarP(&waitCommand.Interval, "interval", "i", 250, "Milliseconds to wait before polling for completion") + flags.BoolVarP(&waitCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + + rootCmd.AddCommand(waitCommand.Command) +} + +func waitCmd(c *cliconfig.WaitValues) error { + args := c.InputArgs + if len(args) < 1 && !c.Latest { return errors.Errorf("you must provide at least one container name or id") } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -52,7 +58,7 @@ func waitCmd(c *cli.Context) error { } var lastError error - if c.Bool("latest") { + if c.Latest { latestCtr, err := runtime.GetLatestContainer() if err != nil { return errors.Wrapf(err, "unable to wait on latest container") @@ -65,10 +71,10 @@ func waitCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "unable to find container %s", container) } - if c.Uint("interval") == 0 { + if c.Interval == 0 { return errors.Errorf("interval must be greater then 0") } - returnCode, err := ctr.WaitWithInterval(time.Duration(c.Uint("interval")) * time.Millisecond) + returnCode, err := ctr.WaitWithInterval(time.Duration(c.Interval) * time.Millisecond) if err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 178542f0d..98b1a2a17 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -610,6 +610,12 @@ It will also set the default stop signal to SIGRTMIN+3. This allow systemd to run in a confined container without any modifications. +Note: On `SELinux` systems, systemd attempts to write to the cgroup +file system. Containers writing to the cgroup file system are denied by default. +The `container_manage_cgroup` boolean must be enabled for this to be allowed on an SELinux separated system. + +`setsebool -P container_manage_cgroup true` + **--tmpfs**=[] Create a tmpfs mount Mount a temporary filesystem (`tmpfs`) mount into a container, for example: @@ -804,7 +810,7 @@ WantedBy=multi-user.target **/etc/subgid** ## SEE ALSO -subgid(5), subuid(5), libpod.conf(5), systemd.unit(5) +subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8) ## HISTORY October 2017, converted from Docker documentation to podman by Dan Walsh for podman <dwalsh@redhat.com> diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 8b96ea6d9..828ae96a8 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -612,6 +612,12 @@ It will also set the default stop signal to SIGRTMIN+3. This allow systemd to run in a confined container without any modifications. +Note: On `SELinux` systems, systemd attempts to write to the cgroup +file system. Containers writing to the cgroup file system are denied by default. +The `container_manage_cgroup` boolean must be enabled for this to be allowed on an SELinux separated system. + +`setsebool -P container_manage_cgroup true` + **--tmpfs**=[] Create a tmpfs mount Mount a temporary filesystem (`tmpfs`) mount into a container, for example: @@ -1096,7 +1102,7 @@ WantedBy=multi-user.target **/etc/subgid** ## SEE ALSO -subgid(5), subuid(5), libpod.conf(5), systemd.unit(5) +subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8) ## HISTORY September 2018, updated by Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> diff --git a/install.md b/install.md index 4f075332d..eba1ce8ea 100644 --- a/install.md +++ b/install.md @@ -220,6 +220,18 @@ make BUILDTAGS='seccomp apparmor' | selinux | selinux process and mount labeling | libselinux | | apparmor | apparmor profile support | libapparmor | +### Vendoring - Dependency Management + +This project is using [vndr](https://github.com/LK4D4/vndr) for managing dependencies, which is a tedious and error-prone task. Doing it manually is likely to cause inconsistencies between the `./vendor` directory (i.e., the downloaded dependencies), the source code that imports those dependencies and the `vendor.conf` configuration file that describes which packages in which version (e.g., a release or git commit) are a dependency. + +To ease updating dependencies, we provide the `make vendor` target, which fetches all dependencies mentioned in `vendor.conf`. `make vendor` whitelists certain packages to prevent the `vndr` tool from removing packages that the test suite (see `./test`) imports. + +The CI of this project makes sure that each pull request leaves a clean vendor state behind by first running the aforementioned `make vendor` followed by running `./hack/tree_status.sh` which checks if any file in the git tree has changed. + +##### Vendor Troubleshooting + +If the CI is complaining about a pull request leaving behind an unclean state, it is very likely right about it. Make sure to run `make vendor` and add all the changes to the commit. Also make sure that your local git tree does not include files not under version control that may reference other go packages. If some dependencies are removed but they should not, for instance, because the CI is needing them, then whitelist those dependencies in the `make vendor` target of the Makefile. Whitelisting a package will instruct `vndr` to not remove if during its cleanup phase. + ## Configuration files ### [registries.conf](https://src.fedoraproject.org/rpms/skopeo/blob/master/f/registries.conf) diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go index 2c408dd2f..46771b5b6 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -11,11 +11,11 @@ import ( "strconv" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" - "github.com/urfave/cli" ) // LocalRuntime describes a typical libpod runtime @@ -35,7 +35,7 @@ type Container struct { } // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it -func GetRuntime(c *cli.Context) (*LocalRuntime, error) { +func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) { runtime, err := libpodruntime.GetRuntime(c) if err != nil { return nil, err diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index c73e98c95..f754aaee6 100644 --- a/libpod/adapter/runtime_remote.go +++ b/libpod/adapter/runtime_remote.go @@ -14,12 +14,12 @@ import ( "time" "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" - "github.com/urfave/cli" "github.com/varlink/go/varlink" ) @@ -38,7 +38,7 @@ type LocalRuntime struct { } // GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it -func GetRuntime(c *cli.Context) (*LocalRuntime, error) { +func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) { runtime := RemoteRuntime{} conn, err := runtime.Connect() if err != nil { diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 344f4afb9..f6c77d61c 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -331,6 +331,9 @@ func (c *CreateConfig) createExitCommand() []string { "--cgroup-manager", config.CgroupManager, "--tmpdir", config.TmpDir, } + if config.OCIRuntime != "" { + command = append(command, []string{"--runtime", config.OCIRuntime}...) + } if config.StorageConfig.GraphDriverName != "" { command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) } diff --git a/pkg/varlinkapi/config.go b/pkg/varlinkapi/config.go index 8dd217b77..f557d04e5 100644 --- a/pkg/varlinkapi/config.go +++ b/pkg/varlinkapi/config.go @@ -1,20 +1,21 @@ package varlinkapi import ( + "github.com/containers/libpod/cmd/podman/cliconfig" iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" - "github.com/urfave/cli" + "github.com/spf13/cobra" ) // LibpodAPI is the basic varlink struct for libpod type LibpodAPI struct { - Cli *cli.Context + Cli *cobra.Command iopodman.VarlinkInterface Runtime *libpod.Runtime } // New creates a new varlink client -func New(cli *cli.Context, runtime *libpod.Runtime) *iopodman.VarlinkInterface { - lp := LibpodAPI{Cli: cli, Runtime: runtime} +func New(cli *cliconfig.PodmanCommand, runtime *libpod.Runtime) *iopodman.VarlinkInterface { + lp := LibpodAPI{Cli: cli.Command, Runtime: runtime} return iopodman.VarlinkNew(&lp) } diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 3c77444d8..5c914a367 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -151,7 +151,7 @@ var _ = Describe("Podman restart", func() { startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) startTime.WaitWithDefaultTimeout() - session := podmanTest.Podman([]string{"restart", "-all"}) + session := podmanTest.Podman([]string{"restart", "--all"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 1438fd97b..167f8fa25 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -63,10 +63,10 @@ var _ = Describe("Podman search", func() { }) It("podman search single registry flag", func() { - search := podmanTest.Podman([]string{"search", "registry.access.redhat.com/rhel7"}) + search := podmanTest.Podman([]string{"search", "quay.io/libpod/gate:latest"}) search.WaitWithDefaultTimeout() Expect(search.ExitCode()).To(Equal(0)) - Expect(search.LineInOutputContains("registry.access.redhat.com/rhel7")).To(BeTrue()) + Expect(search.LineInOutputContains("quay.io/libpod/gate")).To(BeTrue()) }) It("podman search format flag", func() { diff --git a/troubleshooting.md b/troubleshooting.md index d210d85df..3f66b56ef 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -173,3 +173,21 @@ cat ~/.config/containers/storage.conf [storage.options] mount_program = "/bin/fuse-overlayfs" ``` + +### 8) Permission denied when running systemd within a Podman container + +When running systemd as PID 1 inside of a container on an SELinux +separated machine, it needs to write to the cgroup file system. + +#### Symptom + +Systemd gets permission denied when attempting to write to the cgroup file +system, and AVC messages start to show up in the audit.log file or journal on +the system. + +#### Solution + +SELinux provides a boolean `container_manage_cgroup`, which allows container +processes to write to the cgroup file system. Turn on this boolean, on SELinux separated systems, to allow systemd to run properly in the container. + +`setsebool -P container_manage_cgroup true` diff --git a/vendor.conf b/vendor.conf index cd018e3c2..116120aa8 100644 --- a/vendor.conf +++ b/vendor.conf @@ -15,8 +15,8 @@ github.com/containerd/cgroups 39b18af02c4120960f517a3a4c2588fabb61d02c github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d github.com/containernetworking/cni v0.7.0-alpha1 github.com/containernetworking/plugins v0.7.4 -github.com/containers/image 67b1f789f2ce8a3654592a582fff26c396326236 -github.com/containers/storage v1.8 +github.com/containers/image v1.3 +github.com/containers/storage v1.9 github.com/containers/psgo v1.1 github.com/coreos/go-systemd v14 github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c @@ -91,7 +91,7 @@ k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apim k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 github.com/varlink/go 3ac79db6fd6aec70924193b090962f92985fe199 -github.com/containers/buildah e7ca330f923701dba8859f5c014d0a9a3f7f0a49 +github.com/containers/buildah 973bb88ef1861a5b782c722c471dd79b6732852c # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 # do not go beyond the below commit as the next one requires a more recent @@ -108,5 +108,7 @@ github.com/klauspost/cpuid v1.2.0 github.com/onsi/ginkgo v1.7.0 github.com/onsi/gomega v1.4.3 github.com/hpcloud/tail v1.0.0 -gopkg.in/fsnotify.v1 v1.4.2 gopkg.in/tomb.v1 v1 +github.com/spf13/cobra v0.0.3 +github.com/inconshreveable/mousetrap v1.0.0 +gopkg.in/fsnotify.v1 v1.4.7 diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index cbdb5c9f9..8b9baea12 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "time" "github.com/containers/buildah/docker" "github.com/containers/buildah/util" @@ -25,7 +26,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.6-dev" + Version = "1.7-dev" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to @@ -175,6 +176,15 @@ type Builder struct { // after processing the AddCapabilities set, when running commands in the container. // If a capability appears in both lists, it will be dropped. DropCapabilities []string + // PrependedEmptyLayers are history entries that we'll add to a + // committed image, after any history items that we inherit from a base + // image, but before the history item for the layer that we're + // committing. + PrependedEmptyLayers []v1.History + // AppendedEmptyLayers are history entries that we'll add to a + // committed image after the history item for the layer that we're + // committing. + AppendedEmptyLayers []v1.History CommonBuildOpts *CommonBuildOptions // TopLayer is the top layer of the image @@ -209,11 +219,24 @@ type BuilderInfo struct { DefaultCapabilities []string AddCapabilities []string DropCapabilities []string + History []v1.History } // GetBuildInfo gets a pointer to a Builder object and returns a BuilderInfo object from it. // This is used in the inspect command to display Manifest and Config as string and not []byte. func GetBuildInfo(b *Builder) BuilderInfo { + history := copyHistory(b.OCIv1.History) + history = append(history, copyHistory(b.PrependedEmptyLayers)...) + now := time.Now().UTC() + created := &now + history = append(history, v1.History{ + Created: created, + CreatedBy: b.ImageCreatedBy, + Author: b.Maintainer(), + Comment: b.ImageHistoryComment, + EmptyLayer: false, + }) + history = append(history, copyHistory(b.AppendedEmptyLayers)...) return BuilderInfo{ Type: b.Type, FromImage: b.FromImage, @@ -239,6 +262,7 @@ func GetBuildInfo(b *Builder) BuilderInfo { DefaultCapabilities: append([]string{}, util.DefaultCapabilities...), AddCapabilities: append([]string{}, b.AddCapabilities...), DropCapabilities: append([]string{}, b.DropCapabilities...), + History: history, } } diff --git a/vendor/github.com/containers/buildah/chroot/run.go b/vendor/github.com/containers/buildah/chroot/run.go index 6a1400e61..9bcac1683 100644 --- a/vendor/github.com/containers/buildah/chroot/run.go +++ b/vendor/github.com/containers/buildah/chroot/run.go @@ -351,21 +351,6 @@ func runUsingChrootMain() { defer stdoutRead.Close() defer stderrRead.Close() } - // A helper that returns false if err is an error that would cause us - // to give up. - logIfNotRetryable := func(err error, what string) (retry bool) { - if err == nil { - return true - } - if errno, isErrno := err.(syscall.Errno); isErrno { - switch errno { - case syscall.EINTR, syscall.EAGAIN: - return true - } - } - logrus.Error(what) - return false - } for readFd, writeFd := range relays { if err := unix.SetNonblock(readFd, true); err != nil { logrus.Errorf("error setting descriptor %d (%s) non-blocking: %v", readFd, fdDesc[readFd], err) @@ -388,7 +373,7 @@ func runUsingChrootMain() { fds = append(fds, unix.PollFd{Fd: int32(fd), Events: unix.POLLIN | unix.POLLHUP}) } _, err := unix.Poll(fds, pollTimeout) - if !logIfNotRetryable(err, fmt.Sprintf("poll: %v", err)) { + if !util.LogIfNotRetryable(err, fmt.Sprintf("poll: %v", err)) { return } removeFds := make(map[int]struct{}) @@ -405,7 +390,7 @@ func runUsingChrootMain() { } b := make([]byte, 8192) nread, err := unix.Read(int(rfd.Fd), b) - logIfNotRetryable(err, fmt.Sprintf("read %s: %v", fdDesc[int(rfd.Fd)], err)) + util.LogIfNotRetryable(err, fmt.Sprintf("read %s: %v", fdDesc[int(rfd.Fd)], err)) if nread > 0 { if wfd, ok := relays[int(rfd.Fd)]; ok { nwritten, err := buffers[wfd].Write(b[:nread]) @@ -422,7 +407,7 @@ func runUsingChrootMain() { // from this descriptor, read as much as there is to read. for rfd.Revents&unix.POLLHUP == unix.POLLHUP { nr, err := unix.Read(int(rfd.Fd), b) - logIfNotRetryable(err, fmt.Sprintf("read %s: %v", fdDesc[int(rfd.Fd)], err)) + util.LogIfUnexpectedWhileDraining(err, fmt.Sprintf("read %s: %v", fdDesc[int(rfd.Fd)], err)) if nr <= 0 { break } @@ -447,7 +432,7 @@ func runUsingChrootMain() { for wfd, buffer := range buffers { if buffer.Len() > 0 { nwritten, err := unix.Write(wfd, buffer.Bytes()) - logIfNotRetryable(err, fmt.Sprintf("write %s: %v", fdDesc[wfd], err)) + util.LogIfNotRetryable(err, fmt.Sprintf("write %s: %v", fdDesc[wfd], err)) if nwritten >= 0 { _ = buffer.Next(nwritten) } diff --git a/vendor/github.com/containers/buildah/commit.go b/vendor/github.com/containers/buildah/commit.go index 591ead4e6..da28bea61 100644 --- a/vendor/github.com/containers/buildah/commit.go +++ b/vendor/github.com/containers/buildah/commit.go @@ -67,6 +67,10 @@ type CommitOptions struct { OnBuild []string // Parent is the base image that this image was created by. Parent string + + // OmitTimestamp forces epoch 0 as created timestamp to allow for + // deterministic, content-addressable builds. + OmitTimestamp bool } // PushOptions can be used to alter how an image is copied somewhere. @@ -97,6 +101,10 @@ type PushOptions struct { // regenerate from on-disk layers, substituting them in the list of // blobs to copy whenever possible. BlobDirectory string + // Quiet is a boolean value that determines if minimal output to + // the user will be displayed, this is best used for logging. + // The default is false. + Quiet bool } // Commit writes the contents of the container, along with its updated @@ -140,7 +148,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options } } } - src, err := b.makeImageRef(options.PreferredManifestType, options.Parent, exportBaseLayers, options.Squash, options.BlobDirectory, options.Compression, options.HistoryTimestamp) + src, err := b.makeImageRef(options.PreferredManifestType, options.Parent, exportBaseLayers, options.Squash, options.BlobDirectory, options.Compression, options.HistoryTimestamp, options.OmitTimestamp) if err != nil { return imgID, nil, "", errors.Wrapf(err, "error computing layer digests and building metadata for container %q", b.ContainerID) } @@ -224,6 +232,9 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options func Push(ctx context.Context, image string, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) { systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath) + if options.Quiet { + options.ReportWriter = nil // Turns off logging output + } blocked, err := isReferenceBlocked(dest, systemContext) if err != nil { return nil, "", errors.Wrapf(err, "error checking if pushing to registry for %q is blocked", transports.ImageName(dest)) diff --git a/vendor/github.com/containers/buildah/config.go b/vendor/github.com/containers/buildah/config.go index 114b8ca37..05b0abb23 100644 --- a/vendor/github.com/containers/buildah/config.go +++ b/vendor/github.com/containers/buildah/config.go @@ -574,3 +574,50 @@ func (b *Builder) SetHealthcheck(config *docker.HealthConfig) { } } } + +// AddPrependedEmptyLayer adds an item to the history that we'll create when +// commiting the image, after any history we inherit from the base image, but +// before the history item that we'll use to describe the new layer that we're +// adding. +func (b *Builder) AddPrependedEmptyLayer(created *time.Time, createdBy, author, comment string) { + if created != nil { + copiedTimestamp := *created + created = &copiedTimestamp + } + b.PrependedEmptyLayers = append(b.PrependedEmptyLayers, ociv1.History{ + Created: created, + CreatedBy: createdBy, + Author: author, + Comment: comment, + EmptyLayer: true, + }) +} + +// ClearPrependedEmptyLayers clears the list of history entries that we'll add +// to the committed image before the entry for the layer that we're adding. +func (b *Builder) ClearPrependedEmptyLayers() { + b.PrependedEmptyLayers = nil +} + +// AddAppendedEmptyLayer adds an item to the history that we'll create when +// commiting the image, after the history item that we'll use to describe the +// new layer that we're adding. +func (b *Builder) AddAppendedEmptyLayer(created *time.Time, createdBy, author, comment string) { + if created != nil { + copiedTimestamp := *created + created = &copiedTimestamp + } + b.AppendedEmptyLayers = append(b.AppendedEmptyLayers, ociv1.History{ + Created: created, + CreatedBy: createdBy, + Author: author, + Comment: comment, + EmptyLayer: true, + }) +} + +// ClearAppendedEmptyLayers clears the list of history entries that we'll add +// to the committed image after the entry for the layer that we're adding. +func (b *Builder) ClearAppendedEmptyLayers() { + b.AppendedEmptyLayers = nil +} diff --git a/vendor/github.com/containers/buildah/docker/types.go b/vendor/github.com/containers/buildah/docker/types.go index 6847d36fd..c59be0e60 100644 --- a/vendor/github.com/containers/buildah/docker/types.go +++ b/vendor/github.com/containers/buildah/docker/types.go @@ -18,7 +18,7 @@ const TypeLayers = "layers" const V2S2MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar" // github.com/moby/moby/image/rootfs.go -// RootFS describes images root filesystem +// V2S2RootFS describes images root filesystem // This is currently a placeholder that only supports layers. In the future // this can be made into an interface that supports different implementations. type V2S2RootFS struct { @@ -27,7 +27,7 @@ type V2S2RootFS struct { } // github.com/moby/moby/image/image.go -// History stores build commands that were used to create an image +// V2S2History stores build commands that were used to create an image type V2S2History struct { // Created is the timestamp at which the image was created Created time.Time `json:"created"` @@ -158,7 +158,7 @@ type V1Image struct { } // github.com/moby/moby/image/image.go -// Image stores the image configuration +// V2Image stores the image configuration type V2Image struct { V1Image Parent ID `json:"parent,omitempty"` @@ -176,7 +176,7 @@ type V2Image struct { } // github.com/docker/distribution/manifest/versioned.go -// Versioned provides a struct with the manifest schemaVersion and mediaType. +// V2Versioned provides a struct with the manifest schemaVersion and mediaType. // Incoming content with unknown schema version can be decoded against this // struct to check the version. type V2Versioned struct { @@ -188,21 +188,21 @@ type V2Versioned struct { } // github.com/docker/distribution/manifest/schema1/manifest.go -// FSLayer is a container struct for BlobSums defined in an image manifest +// V2S1FSLayer is a container struct for BlobSums defined in an image manifest type V2S1FSLayer struct { // BlobSum is the tarsum of the referenced filesystem image layer BlobSum digest.Digest `json:"blobSum"` } // github.com/docker/distribution/manifest/schema1/manifest.go -// History stores unstructured v1 compatibility information +// V2S1History stores unstructured v1 compatibility information type V2S1History struct { // V1Compatibility is the raw v1 compatibility information V1Compatibility string `json:"v1Compatibility"` } // github.com/docker/distribution/manifest/schema1/manifest.go -// Manifest provides the base accessible fields for working with V2 image +// V2S1Manifest provides the base accessible fields for working with V2 image // format in the registry. type V2S1Manifest struct { V2Versioned @@ -225,7 +225,7 @@ type V2S1Manifest struct { } // github.com/docker/distribution/blobs.go -// Descriptor describes targeted content. Used in conjunction with a blob +// V2S2Descriptor describes targeted content. Used in conjunction with a blob // store, a descriptor can be used to fetch, store and target any kind of // blob. The struct also describes the wire protocol format. Fields should // only be added but never changed. @@ -250,7 +250,7 @@ type V2S2Descriptor struct { } // github.com/docker/distribution/manifest/schema2/manifest.go -// Manifest defines a schema2 manifest. +// V2S2Manifest defines a schema2 manifest. type V2S2Manifest struct { V2Versioned diff --git a/vendor/github.com/containers/buildah/image.go b/vendor/github.com/containers/buildah/image.go index 47842db7d..b0876fb6d 100644 --- a/vendor/github.com/containers/buildah/image.go +++ b/vendor/github.com/containers/buildah/image.go @@ -58,6 +58,8 @@ type containerImageRef struct { tarPath func(path string) (io.ReadCloser, error) parent string blobDirectory string + preEmptyLayers []v1.History + postEmptyLayers []v1.History } type containerImageSource struct { @@ -396,6 +398,35 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System } // Build history notes in the image configurations. + appendHistory := func(history []v1.History) { + for i := range history { + var created *time.Time + if history[i].Created != nil { + copiedTimestamp := *history[i].Created + created = &copiedTimestamp + } + onews := v1.History{ + Created: created, + CreatedBy: history[i].CreatedBy, + Author: history[i].Author, + Comment: history[i].Comment, + EmptyLayer: true, + } + oimage.History = append(oimage.History, onews) + if created == nil { + created = &time.Time{} + } + dnews := docker.V2S2History{ + Created: *created, + CreatedBy: history[i].CreatedBy, + Author: history[i].Author, + Comment: history[i].Comment, + EmptyLayer: true, + } + dimage.History = append(dimage.History, dnews) + } + } + appendHistory(i.preEmptyLayers) onews := v1.History{ Created: &i.created, CreatedBy: i.createdBy, @@ -412,6 +443,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System EmptyLayer: false, } dimage.History = append(dimage.History, dnews) + appendHistory(i.postEmptyLayers) dimage.Parent = docker.ID(digest.FromString(i.parent)) // Sanity check that we didn't just create a mismatch between non-empty layers in the @@ -603,7 +635,7 @@ func (i *containerImageSource) GetBlob(ctx context.Context, blob types.BlobInfo, return ioutils.NewReadCloserWrapper(layerFile, closer), size, nil } -func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squash bool, blobDirectory string, compress archive.Compression, historyTimestamp *time.Time) (types.ImageReference, error) { +func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squash bool, blobDirectory string, compress archive.Compression, historyTimestamp *time.Time, omitTimestamp bool) (types.ImageReference, error) { var name reference.Named container, err := b.store.Container(b.ContainerID) if err != nil { @@ -630,6 +662,10 @@ func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squa created = historyTimestamp.UTC() } + if omitTimestamp { + created = time.Unix(0, 0) + } + ref := &containerImageRef{ store: b.store, compression: compress, @@ -650,6 +686,8 @@ func (b *Builder) makeImageRef(manifestType, parent string, exporting bool, squa tarPath: b.tarPath(), parent: parent, blobDirectory: blobDirectory, + preEmptyLayers: b.PrependedEmptyLayers, + postEmptyLayers: b.AppendedEmptyLayers, } return ref, nil } diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index 217bcfc79..56ab7aa57 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -1311,13 +1311,17 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (strin // Check if we have a one line Dockerfile making layers irrelevant // or the user told us to ignore layers. - ignoreLayers := (len(stages) < 2 && len(stages[0].Node.Children) < 2) || (!b.layers && !b.noCache) + singleLineDockerfile := (len(stages) < 2 && len(stages[0].Node.Children) < 1) + ignoreLayers := singleLineDockerfile || !b.layers && !b.noCache if ignoreLayers { imgID, ref, err := stageExecutor.Commit(ctx, stages[len(stages)-1].Builder, "") if err != nil { return "", nil, err } + if singleLineDockerfile { + b.log("COMMIT %s", ref) + } imageID = imgID imageRef = ref } @@ -1527,6 +1531,17 @@ func (b *Executor) deleteSuccessfulIntermediateCtrs() error { return lastErr } +func (b *Executor) EnsureContainerPath(path string) error { + _, err := os.Stat(filepath.Join(b.mountPoint, path)) + if err != nil && os.IsNotExist(err) { + err = os.MkdirAll(filepath.Join(b.mountPoint, path), 0755) + } + if err != nil { + return errors.Wrapf(err, "error ensuring container path %q", path) + } + return nil +} + // preprocessDockerfileContents runs CPP(1) in preprocess-only mode on the input // dockerfile content and will use ctxDir as the base include path. // diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index dc1b2bc69..bbbbf3476 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -13,263 +13,183 @@ import ( "github.com/containers/buildah/util" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/spf13/pflag" ) -var ( - usernsFlags = []cli.Flag{ - cli.StringFlag{ - Name: "userns", - Usage: "'container', `path` of user namespace to join, or 'host'", - }, - cli.StringSliceFlag{ - Name: "userns-uid-map", - Usage: "`containerID:hostID:length` UID mapping to use in user namespace", - }, - cli.StringSliceFlag{ - Name: "userns-gid-map", - Usage: "`containerID:hostID:length` GID mapping to use in user namespace", - }, - cli.StringFlag{ - Name: "userns-uid-map-user", - Usage: "`name` of entries from /etc/subuid to use to set user namespace UID mapping", - }, - cli.StringFlag{ - Name: "userns-gid-map-group", - Usage: "`name` of entries from /etc/subgid to use to set user namespace GID mapping", - }, - } +// LayerResults represents the results of the layer flags +type LayerResults struct { + ForceRm bool + Layers bool +} - NamespaceFlags = []cli.Flag{ - cli.StringFlag{ - Name: string(specs.IPCNamespace), - Usage: "'container', `path` of IPC namespace to join, or 'host'", - }, - cli.StringFlag{ - Name: string(specs.NetworkNamespace) + ", net", - Usage: "'container', `path` of network namespace to join, or 'host'", - }, - cli.StringFlag{ - Name: "cni-config-dir", - Usage: "`directory` of CNI configuration files", - Value: util.DefaultCNIConfigDir, - }, - cli.StringFlag{ - Name: "cni-plugin-path", - Usage: "`path` of CNI network plugins", - Value: util.DefaultCNIPluginPath, - }, - cli.StringFlag{ - Name: string(specs.PIDNamespace), - Usage: "'container', `path` of PID namespace to join, or 'host'", - }, - cli.StringFlag{ - Name: string(specs.UTSNamespace), - Usage: "'container', `path` of UTS namespace to join, or 'host'", - }, - } +// UserNSResults represents the results for the UserNS flags +type UserNSResults struct { + UserNS string + UserNSUIDMap []string + UserNSGIDMap []string + UserNSUIDMapUser string + UserNSGIDMapGroup string +} - LayerFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "force-rm", - Usage: "Always remove intermediate containers after a build, even if the build is unsuccessful.", - }, - cli.BoolFlag{ - Name: "layers", - Usage: fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. (default %t)", UseLayers()), - }, - } +// NameSpaceResults represents the results for Namespace flags +type NameSpaceResults struct { + IPC string + Network string + CNIConfigDir string + CNIPlugInPath string + PID string + UTS string +} - BudFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "annotation", - Usage: "Set metadata for an image (default [])", - }, - cli.StringFlag{ - Name: "authfile", - Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", - }, - cli.StringSliceFlag{ - Name: "build-arg", - Usage: "`argument=value` to supply to the builder", - }, - cli.StringFlag{ - Name: "cache-from", - Usage: "Images to utilise as potential cache sources. The build process does not currently support caching so this is a NOOP.", - }, - cli.StringFlag{ - Name: "cert-dir", - Value: "", - Usage: "use certificates at the specified path to access the registry", - }, - cli.BoolFlag{ - Name: "compress", - Usage: "This is legacy option, which has no effect on the image", - }, - cli.StringFlag{ - Name: "creds", - Value: "", - Usage: "use `[username[:password]]` for accessing the registry", - }, - cli.BoolFlag{ - Name: "disable-compression, D", - Usage: "don't compress layers by default", - }, - cli.BoolFlag{ - Name: "disable-content-trust", - Usage: "This is a Docker specific option and is a NOOP", - }, - cli.StringSliceFlag{ - Name: "file, f", - Usage: "`pathname or URL` of a Dockerfile", - }, - cli.StringFlag{ - Name: "format", - Usage: "`format` of the built image's manifest and metadata. Use BUILDAH_FORMAT environment variable to override.", - Value: DefaultFormat(), - }, - cli.StringFlag{ - Name: "iidfile", - Usage: "`file` to write the image ID to", - }, - cli.StringSliceFlag{ - Name: "label", - Usage: "Set metadata for an image (default [])", - }, - cli.BoolFlag{ - Name: "no-cache", - Usage: "Do not use existing cached images for the container build. Build from the start with a new set of cached layers.", - }, - cli.StringFlag{ - Name: "logfile", - Usage: "log to `file` instead of stdout/stderr", - }, - cli.IntFlag{ - Name: "loglevel", - Usage: "adjust logging level (range from -2 to 3)", - }, - cli.StringFlag{ - Name: "platform", - Usage: "CLI compatibility: no action or effect", - }, - cli.BoolTFlag{ - Name: "pull", - Usage: "pull the image if not present", - }, - cli.BoolFlag{ - Name: "pull-always", - Usage: "pull the image, even if a version is present", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "refrain from announcing build instructions and image read/write progress", - }, - cli.BoolTFlag{ - Name: "rm", - Usage: "Remove intermediate containers after a successful build (default true)", - }, - cli.StringFlag{ - Name: "runtime", - Usage: "`path` to an alternate runtime. Use BUILDAH_RUNTIME environment variable to override.", - Value: util.Runtime(), - }, - cli.StringSliceFlag{ - Name: "runtime-flag", - Usage: "add global flags for the container runtime", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - }, - cli.BoolFlag{ - Name: "squash", - Usage: "Squash newly built layers into a single new layer. The build process does not currently support caching so this is a NOOP.", - }, - cli.StringSliceFlag{ - Name: "tag, t", - Usage: "tagged `name` to apply to the built image", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "require HTTPS and verify certificates when accessing the registry", - }, - } +// BudResults represents the results for Bud flags +type BudResults struct { + Annotation []string + Authfile string + BuildArg []string + CacheFrom string + CertDir string + Compress bool + Creds string + DisableCompression bool + DisableContentTrust bool + File []string + Format string + Iidfile string + NoCache bool + Label []string + Logfile string + Loglevel int + Platform string + Pull bool + PullAlways bool + Quiet bool + Rm bool + Runtime string + RuntimeOpts []string + SignaturePolicy string + Squash bool + Tag []string + TlsVerify bool +} - FromAndBudFlags = append(append([]cli.Flag{ - cli.StringSliceFlag{ - Name: "add-host", - Usage: "add a custom host-to-IP mapping (`host:ip`) (default [])", - }, - cli.StringFlag{ - Name: "blob-cache", - Value: "", - Usage: "assume image blobs in the specified directory will be available for pushing", - Hidden: true, // this is here mainly so that we can test the API during integration tests - }, - cli.StringSliceFlag{ - Name: "cap-add", - Usage: "add the specified capability when running (default [])", - }, - cli.StringSliceFlag{ - Name: "cap-drop", - Usage: "drop the specified capability when running (default [])", - }, - cli.StringFlag{ - Name: "cgroup-parent", - Usage: "optional parent cgroup for the container", - }, - cli.Uint64Flag{ - Name: "cpu-period", - Usage: "limit the CPU CFS (Completely Fair Scheduler) period", - }, - cli.Int64Flag{ - Name: "cpu-quota", - Usage: "limit the CPU CFS (Completely Fair Scheduler) quota", - }, - cli.Uint64Flag{ - Name: "cpu-shares, c", - Usage: "CPU shares (relative weight)", - }, - cli.StringFlag{ - Name: "cpuset-cpus", - Usage: "CPUs in which to allow execution (0-3, 0,1)", - }, - cli.StringFlag{ - Name: "cpuset-mems", - Usage: "memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", - }, - cli.StringFlag{ - Name: "isolation", - Usage: "`type` of process isolation to use. Use BUILDAH_ISOLATION environment variable to override.", - Value: DefaultIsolation(), - }, - cli.StringFlag{ - Name: "memory, m", - Usage: "memory limit (format: <number>[<unit>], where unit = b, k, m or g)", - }, - cli.StringFlag{ - Name: "memory-swap", - Usage: "swap limit equal to memory plus swap: '-1' to enable unlimited swap", - }, - cli.StringSliceFlag{ - Name: "security-opt", - Usage: "security options (default [])", - }, - cli.StringFlag{ - Name: "shm-size", - Usage: "size of '/dev/shm'. The format is `<number><unit>`.", - Value: "65536k", - }, - cli.StringSliceFlag{ - Name: "ulimit", - Usage: "ulimit options (default [])", - }, - cli.StringSliceFlag{ - Name: "volume, v", - Usage: "bind mount a volume into the container (default [])", - }, - }, usernsFlags...), NamespaceFlags...) -) +// FromAndBugResults represents the results for common flags +// in bud and from +type FromAndBudResults struct { + AddHost []string + BlobCache string + CapAdd []string + CapDrop []string + CgroupParent string + CPUPeriod uint64 + CPUQuota int64 + CPUSetCPUs string + CPUSetMems string + CPUShares uint64 + Isolation string + Memory string + MemorySwap string + SecurityOpt []string + ShmSize string + Ulimit []string + Volume []string +} + +// GetUserNSFlags returns the common flags for usernamespace +func GetUserNSFlags(flags *UserNSResults) pflag.FlagSet { + usernsFlags := pflag.FlagSet{} + usernsFlags.StringVar(&flags.UserNS, "userns", "", "'container', `path` of user namespace to join, or 'host'") + usernsFlags.StringSliceVar(&flags.UserNSUIDMap, "userns-uid-map", []string{}, "`containerID:hostID:length` UID mapping to use in user namespace") + usernsFlags.StringSliceVar(&flags.UserNSGIDMap, "userns-gid-map", []string{}, "`containerID:hostID:length` GID mapping to use in user namespace") + usernsFlags.StringVar(&flags.UserNSUIDMapUser, "userns-uid-map-user", "", "`name` of entries from /etc/subuid to use to set user namespace UID mapping") + usernsFlags.StringVar(&flags.UserNSGIDMapGroup, "userns-gid-map-group", "", "`name` of entries from /etc/subgid to use to set user namespace GID mapping") + return usernsFlags +} + +// GetNameSpaceFlags returns the common flags for a namespace menu +func GetNameSpaceFlags(flags *NameSpaceResults) pflag.FlagSet { + fs := pflag.FlagSet{} + fs.StringVar(&flags.IPC, string(specs.IPCNamespace), "", "'container', `path` of IPC namespace to join, or 'host'") + fs.StringVar(&flags.Network, string(specs.NetworkNamespace), "", "'container', `path` of network namespace to join, or 'host'") + // TODO How do we alias net and network? + fs.StringVar(&flags.Network, "net", "", "'container', `path` of network namespace to join, or 'host'") + fs.MarkHidden("net") + fs.StringVar(&flags.CNIConfigDir, "cni-config-dir", util.DefaultCNIConfigDir, "`directory` of CNI configuration files") + fs.StringVar(&flags.CNIPlugInPath, "cni-plugin-path", util.DefaultCNIPluginPath, "`path` of CNI network plugins") + fs.StringVar(&flags.PID, string(specs.PIDNamespace), "", "container, `path` of PID namespace to join, or 'host'") + fs.StringVar(&flags.UTS, string(specs.UTSNamespace), "", "container, :`path` of UTS namespace to join, or 'host'") + return fs +} + +// GetLayerFlags returns the common flags for layers +func GetLayerFlags(flags *LayerResults) pflag.FlagSet { + fs := pflag.FlagSet{} + fs.BoolVar(&flags.ForceRm, "force-rm", false, "Always remove intermediate containers after a build, even if the build is unsuccessful.") + fs.BoolVar(&flags.Layers, "layers", false, fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. (default %t)", UseLayers())) + return fs +} + +// GetBudFlags returns common bud flags +func GetBudFlags(flags *BudResults) pflag.FlagSet { + fs := pflag.FlagSet{} + fs.StringSliceVar(&flags.Annotation, "annotation", []string{}, "Set metadata for an image (default [])") + fs.StringVar(&flags.Authfile, "authfile", "", "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json") + fs.StringSliceVar(&flags.BuildArg, "build-arg", []string{}, "`argument=value` to supply to the builder") + fs.StringVar(&flags.CacheFrom, "cache-from", "", "Images to utilise as potential cache sources. The build process does not currently support caching so this is a NOOP.") + fs.StringVar(&flags.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry") + fs.BoolVar(&flags.Compress, "compress", false, "This is legacy option, which has no effect on the image") + fs.StringVar(&flags.Creds, "creds", "", "use `[username[:password]]` for accessing the registry") + fs.BoolVarP(&flags.DisableCompression, "disable-compression", "D", false, "don't compress layers by default") + fs.BoolVar(&flags.DisableContentTrust, "disable-content-trust", false, "This is a Docker specific option and is a NOOP") + fs.StringSliceVarP(&flags.File, "file", "f", []string{}, "`pathname or URL` of a Dockerfile") + fs.StringVar(&flags.Format, "format", DefaultFormat(), "`format` of the built image's manifest and metadata. Use BUILDAH_FORMAT environment variable to override.") + fs.StringVar(&flags.Iidfile, "iidfile", "", "`file` to write the image ID to") + fs.StringSliceVar(&flags.Label, "label", []string{}, "Set metadata for an image (default [])") + fs.BoolVar(&flags.NoCache, "no-cache", false, "Do not use existing cached images for the container build. Build from the start with a new set of cached layers.") + fs.StringVar(&flags.Logfile, "logfile", "", "log to `file` instead of stdout/stderr") + fs.IntVar(&flags.Loglevel, "loglevel", 0, "adjust logging level (range from -2 to 3)") + fs.StringVar(&flags.Platform, "platform", "", "CLI compatibility: no action or effect") + fs.BoolVar(&flags.Pull, "pull", true, "pull the image if not present") + fs.BoolVar(&flags.PullAlways, "pull-always", false, "pull the image, even if a version is present") + fs.BoolVarP(&flags.Quiet, "quiet", "q", false, "refrain from announcing build instructions and image read/write progress") + fs.BoolVar(&flags.Rm, "rm", true, "Remove intermediate containers after a successful build (default true)") + fs.StringVar(&flags.Runtime, "runtime", util.Runtime(), "`path` to an alternate runtime. Use BUILDAH_RUNTIME environment variable to override.") + fs.StringSliceVar(&flags.RuntimeOpts, "runtime-flag", []string{}, "add global flags for the container runtime") + fs.StringVar(&flags.SignaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") + fs.BoolVar(&flags.Squash, "squash", false, "Squash newly built layers into a single new layer. The build process does not currently support caching so this is a NOOP.") + fs.StringSliceVarP(&flags.Tag, "tag", "t", []string{}, "tagged `name` to apply to the built image") + fs.BoolVar(&flags.TlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") + return fs +} + +func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults, namespaceResults *NameSpaceResults) pflag.FlagSet { + fs := pflag.FlagSet{} + fs.StringSliceVar(&flags.AddHost, "add-host", []string{}, "add a custom host-to-IP mapping (`host:ip`) (default [])") + fs.StringVar(&flags.BlobCache, "blob-cache", "", "assume image blobs in the specified directory will be available for pushing") + fs.MarkHidden("blob-cache") + fs.StringSliceVar(&flags.CapAdd, "cap-add", []string{}, "add the specified capability when running (default [])") + fs.StringSliceVar(&flags.CapDrop, "cap-drop", []string{}, "drop the specified capability when running (default [])") + fs.StringVar(&flags.CgroupParent, "cgroup-parent", "", "optional parent cgroup for the container") + fs.Uint64Var(&flags.CPUPeriod, "cpu-period", 0, "limit the CPU CFS (Completely Fair Scheduler) period") + fs.Int64Var(&flags.CPUQuota, "cpu-quota", 0, "limit the CPU CFS (Completely Fair Scheduler) quota") + fs.Uint64VarP(&flags.CPUShares, "cpu-shares", "c", 0, "CPU shares (relative weight)") + fs.StringVar(&flags.CPUSetCPUs, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)") + fs.StringVar(&flags.CPUSetMems, "cpuset-mems", "", "memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.") + fs.StringVar(&flags.Isolation, "isolation", DefaultIsolation(), "`type` of process isolation to use. Use BUILDAH_ISOLATION environment variable to override.") + fs.StringVarP(&flags.Memory, "memory", "m", "", "memory limit (format: <number>[<unit>], where unit = b, k, m or g)") + fs.StringVar(&flags.MemorySwap, "memory-swap", "", "swap limit equal to memory plus swap: '-1' to enable unlimited swap") + fs.StringSliceVar(&flags.SecurityOpt, "security-opt", []string{}, "security options (default [])") + fs.StringVar(&flags.ShmSize, "shm-size", "65536k", "size of '/dev/shm'. The format is `<number><unit>`.") + fs.StringSliceVar(&flags.Ulimit, "ulimit", []string{}, "ulimit options (default [])") + fs.StringSliceVarP(&flags.Volume, "volume", "v", []string{}, "bind mount a volume into the container (default [])") + + // Add in the usernamespace and namespaceflags + usernsFlags := GetUserNSFlags(usernsResults) + namespaceFlags := GetNameSpaceFlags(namespaceResults) + fs.AddFlagSet(&usernsFlags) + fs.AddFlagSet(&namespaceFlags) + + return fs +} // UseLayers returns true if BUILDAH_LAYERS is set to "1" or "true" // otherwise it returns false @@ -299,6 +219,15 @@ func DefaultIsolation() string { return buildah.OCI } +// DefaultHistory returns the default add-history setting +func DefaultHistory() bool { + history := os.Getenv("BUILDAH_HISTORY") + if strings.ToLower(history) == "true" || history == "1" { + return true + } + return false +} + func VerifyFlagsArgsOrder(args []string) error { for _, arg := range args { if strings.HasPrefix(arg, "-") { diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index 41fdea8b1..ffc7c15bb 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -6,11 +6,10 @@ package parse import ( "fmt" + "github.com/spf13/cobra" "net" "os" "path/filepath" - "reflect" - "regexp" "strconv" "strings" "unicode" @@ -22,7 +21,6 @@ import ( "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/urfave/cli" "golang.org/x/crypto/ssh/terminal" "golang.org/x/sys/unix" ) @@ -35,7 +33,7 @@ const ( ) // CommonBuildOptions parses the build options from the bud cli -func CommonBuildOptions(c *cli.Context) (*buildah.CommonBuildOptions, error) { +func CommonBuildOptions(c *cobra.Command) (*buildah.CommonBuildOptions, error) { var ( memoryLimit int64 memorySwap int64 @@ -49,47 +47,57 @@ func CommonBuildOptions(c *cli.Context) (*buildah.CommonBuildOptions, error) { if err := unix.Setrlimit(unix.RLIMIT_NPROC, &rlim); err == nil { defaultLimits = append(defaultLimits, fmt.Sprintf("nproc=%d:%d", rlim.Cur, rlim.Max)) } - if c.String("memory") != "" { - memoryLimit, err = units.RAMInBytes(c.String("memory")) + memVal, _ := c.Flags().GetString("memory") + if memVal != "" { + memoryLimit, err = units.RAMInBytes(memVal) if err != nil { return nil, errors.Wrapf(err, "invalid value for memory") } } - if c.String("memory-swap") != "" { - memorySwap, err = units.RAMInBytes(c.String("memory-swap")) + + memSwapValue, _ := c.Flags().GetString("memory-swap") + if memSwapValue != "" { + memorySwap, err = units.RAMInBytes(memSwapValue) if err != nil { return nil, errors.Wrapf(err, "invalid value for memory-swap") } } - if len(c.StringSlice("add-host")) > 0 { - for _, host := range c.StringSlice("add-host") { + + addHost, _ := c.Flags().GetStringSlice("add-host") + if len(addHost) > 0 { + for _, host := range addHost { if err := validateExtraHost(host); err != nil { return nil, errors.Wrapf(err, "invalid value for add-host") } } } - if _, err := units.FromHumanSize(c.String("shm-size")); err != nil { + if _, err := units.FromHumanSize(c.Flag("shm-size").Value.String()); err != nil { return nil, errors.Wrapf(err, "invalid --shm-size") } - if err := ParseVolumes(c.StringSlice("volume")); err != nil { + volumes, _ := c.Flags().GetStringSlice("volume") + if err := ParseVolumes(volumes); err != nil { return nil, err } - + cpuPeriod, _ := c.Flags().GetUint64("cpu-period") + cpuQuota, _ := c.Flags().GetInt64("cpu-quota") + cpuShares, _ := c.Flags().GetUint64("cpu-shared") + ulimit, _ := c.Flags().GetStringSlice("ulimit") commonOpts := &buildah.CommonBuildOptions{ - AddHost: c.StringSlice("add-host"), - CgroupParent: c.String("cgroup-parent"), - CPUPeriod: c.Uint64("cpu-period"), - CPUQuota: c.Int64("cpu-quota"), - CPUSetCPUs: c.String("cpuset-cpus"), - CPUSetMems: c.String("cpuset-mems"), - CPUShares: c.Uint64("cpu-shares"), + AddHost: addHost, + CgroupParent: c.Flag("cgroup-parent").Value.String(), + CPUPeriod: cpuPeriod, + CPUQuota: cpuQuota, + CPUSetCPUs: c.Flag("cpuset-cpus").Value.String(), + CPUSetMems: c.Flag("cpuset-mems").Value.String(), + CPUShares: cpuShares, Memory: memoryLimit, MemorySwap: memorySwap, - ShmSize: c.String("shm-size"), - Ulimit: append(defaultLimits, c.StringSlice("ulimit")...), - Volumes: c.StringSlice("volume"), + ShmSize: c.Flag("shm-size").Value.String(), + Ulimit: append(defaultLimits, ulimit...), + Volumes: volumes, } - if err := parseSecurityOpts(c.StringSlice("security-opt"), commonOpts); err != nil { + securityOpts, _ := c.Flags().GetStringSlice("security-opt") + if err := parseSecurityOpts(securityOpts, commonOpts); err != nil { return nil, err } return commonOpts, nil @@ -231,79 +239,45 @@ func validateIPAddress(val string) (string, error) { return "", fmt.Errorf("%s is not an ip address", val) } -// ValidateFlags searches for StringFlags or StringSlice flags that never had -// a value set. This commonly occurs when the CLI mistakenly takes the next -// option and uses it as a value. -func ValidateFlags(c *cli.Context, flags []cli.Flag) error { - re, err := regexp.Compile("^-.+") - if err != nil { - return errors.Wrap(err, "compiling regex failed") - } - - // The --cmd flag can have a following command i.e. --cmd="--help". - // Let's skip this check just for the --cmd flag. - for _, flag := range flags { - switch reflect.TypeOf(flag).String() { - case "cli.StringSliceFlag": - { - f := flag.(cli.StringSliceFlag) - name := strings.Split(f.Name, ",") - if f.Name == "cmd" { - continue - } - val := c.StringSlice(name[0]) - for _, v := range val { - if ok := re.MatchString(v); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - case "cli.StringFlag": - { - f := flag.(cli.StringFlag) - name := strings.Split(f.Name, ",") - if f.Name == "cmd" { - continue - } - val := c.String(name[0]) - if ok := re.MatchString(val); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - } - return nil -} - // SystemContextFromOptions returns a SystemContext populated with values // per the input parameters provided by the caller for the use in authentication. -func SystemContextFromOptions(c *cli.Context) (*types.SystemContext, error) { +func SystemContextFromOptions(c *cobra.Command) (*types.SystemContext, error) { + certDir, err := c.Flags().GetString("cert-dir") + if err != nil { + certDir = "" + } ctx := &types.SystemContext{ - DockerCertPath: c.String("cert-dir"), + DockerCertPath: certDir, } - if c.IsSet("tls-verify") { - ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) - ctx.OCIInsecureSkipTLSVerify = !c.BoolT("tls-verify") - ctx.DockerDaemonInsecureSkipTLSVerify = !c.BoolT("tls-verify") + tlsVerify, err := c.Flags().GetBool("tls-verify") + if err == nil && c.Flag("tls-verify").Changed { + ctx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(tlsVerify) + ctx.OCIInsecureSkipTLSVerify = tlsVerify + ctx.DockerDaemonInsecureSkipTLSVerify = tlsVerify } - if c.IsSet("creds") { + creds, err := c.Flags().GetString("creds") + if err == nil && c.Flag("creds").Changed { var err error - ctx.DockerAuthConfig, err = getDockerAuth(c.String("creds")) + ctx.DockerAuthConfig, err = getDockerAuth(creds) if err != nil { return nil, err } } - if c.IsSet("signature-policy") { - ctx.SignaturePolicyPath = c.String("signature-policy") + sigPolicy, err := c.Flags().GetString("signature-policy") + if err == nil && c.Flag("signature-policy").Changed { + ctx.SignaturePolicyPath = sigPolicy } - if c.IsSet("authfile") { - ctx.AuthFilePath = c.String("authfile") + authfile, err := c.Flags().GetString("authfile") + if err == nil && c.Flag("authfile").Changed { + ctx.AuthFilePath = authfile } - if c.GlobalIsSet("registries-conf") { - ctx.SystemRegistriesConfPath = c.GlobalString("registries-conf") + regConf, err := c.Flags().GetString("registries-conf") + if err == nil && c.Flag("registries-conf").Changed { + ctx.SystemRegistriesConfPath = regConf } - if c.GlobalIsSet("registries-conf-dir") { - ctx.RegistriesDirPath = c.GlobalString("registries-conf-dir") + regConfDir, err := c.Flags().GetString("registries-conf-dir") + if err == nil && c.Flag("registries-conf-dir").Changed { + ctx.RegistriesDirPath = regConfDir } ctx.DockerRegistryUserAgent = fmt.Sprintf("Buildah/%s", buildah.Version) return ctx, nil @@ -345,9 +319,9 @@ func getDockerAuth(creds string) (*types.DockerAuthConfig, error) { } // IDMappingOptions parses the build options related to user namespaces and ID mapping. -func IDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, idmapOptions *buildah.IDMappingOptions, err error) { - user := c.String("userns-uid-map-user") - group := c.String("userns-gid-map-group") +func IDMappingOptions(c *cobra.Command) (usernsOptions buildah.NamespaceOptions, idmapOptions *buildah.IDMappingOptions, err error) { + user := c.Flag("userns-uid-map-user").Value.String() + group := c.Flag("userns-gid-map-group").Value.String() // If only the user or group was specified, use the same value for the // other, since we need both in order to initialize the maps using the // names. @@ -366,6 +340,7 @@ func IDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, i } mappings = submappings } + globalOptions := c.PersistentFlags() // We'll parse the UID and GID mapping options the same way. buildIDMap := func(basemap []idtools.IDMap, option string) ([]specs.LinuxIDMapping, error) { outmap := make([]specs.LinuxIDMapping, 0, len(basemap)) @@ -380,11 +355,11 @@ func IDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, i // Parse the flag's value as one or more triples (if it's even // been set), and append them. var spec []string - if c.GlobalIsSet(option) { - spec = c.GlobalStringSlice(option) + if globalOptions.Lookup(option) != nil && globalOptions.Lookup(option).Changed { + spec, _ = globalOptions.GetStringSlice(option) } - if c.IsSet(option) { - spec = c.StringSlice(option) + if c.Flag(option).Changed { + spec, _ = c.Flags().GetStringSlice(option) } idmap, err := parseIDMap(spec) if err != nil { @@ -424,8 +399,8 @@ func IDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, i } // If the user specifically requested that we either use or don't use // user namespaces, override that default. - if c.IsSet("userns") { - how := c.String("userns") + if c.Flag("userns").Changed { + how := c.Flag("userns").Value.String() switch how { case "", "container": usernsOption.Host = false @@ -440,7 +415,12 @@ func IDMappingOptions(c *cli.Context) (usernsOptions buildah.NamespaceOptions, i } } usernsOptions = buildah.NamespaceOptions{usernsOption} - if !c.IsSet("net") { + + // Because --net and --network are technically two different flags, we need + // to check each for nil and .Changed + usernet := c.Flags().Lookup("net") + usernetwork := c.Flags().Lookup("network") + if (usernet != nil && usernetwork != nil) && (!usernet.Changed && !usernetwork.Changed) { usernsOptions = append(usernsOptions, buildah.NamespaceOption{ Name: string(specs.NetworkNamespace), Host: usernsOption.Host, @@ -486,12 +466,12 @@ func parseIDMap(spec []string) (m [][3]uint32, err error) { } // NamespaceOptions parses the build options for all namespaces except for user namespace. -func NamespaceOptions(c *cli.Context) (namespaceOptions buildah.NamespaceOptions, networkPolicy buildah.NetworkConfigurationPolicy, err error) { +func NamespaceOptions(c *cobra.Command) (namespaceOptions buildah.NamespaceOptions, networkPolicy buildah.NetworkConfigurationPolicy, err error) { options := make(buildah.NamespaceOptions, 0, 7) policy := buildah.NetworkDefault - for _, what := range []string{string(specs.IPCNamespace), "net", string(specs.PIDNamespace), string(specs.UTSNamespace)} { - if c.IsSet(what) { - how := c.String(what) + for _, what := range []string{string(specs.IPCNamespace), "net", "network", string(specs.PIDNamespace), string(specs.UTSNamespace)} { + if c.Flags().Lookup(what) != nil && c.Flag(what).Changed { + how := c.Flag(what).Value.String() switch what { case "net", "network": what = string(specs.NetworkNamespace) @@ -560,9 +540,10 @@ func defaultIsolation() (buildah.Isolation, error) { } // IsolationOption parses the --isolation flag. -func IsolationOption(c *cli.Context) (buildah.Isolation, error) { - if c.String("isolation") != "" { - switch strings.ToLower(c.String("isolation")) { +func IsolationOption(c *cobra.Command) (buildah.Isolation, error) { + isolation, _ := c.Flags().GetString("isolation") + if isolation != "" { + switch strings.ToLower(isolation) { case "oci": return buildah.IsolationOCI, nil case "rootless": @@ -570,7 +551,7 @@ func IsolationOption(c *cli.Context) (buildah.Isolation, error) { case "chroot": return buildah.IsolationChroot, nil default: - return 0, errors.Errorf("unrecognized isolation type %q", c.String("isolation")) + return 0, errors.Errorf("unrecognized isolation type %q", isolation) } } return defaultIsolation() diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index e677c8925..aede1784b 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -2,12 +2,14 @@ package buildah import ( "context" + "fmt" "io" "strings" "github.com/containers/buildah/pkg/blobcache" "github.com/containers/buildah/util" cp "github.com/containers/image/copy" + "github.com/containers/image/docker" "github.com/containers/image/docker/reference" tarfile "github.com/containers/image/docker/tarfile" ociarchive "github.com/containers/image/oci/archive" @@ -17,6 +19,7 @@ import ( "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage" + "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -45,6 +48,13 @@ type PullOptions struct { // store copies of layer blobs that we pull down, if any. It should // already exist. BlobDirectory string + // AllTags is a boolean value that determines if all tagged images + // will be downloaded from the repository. The default is false. + AllTags bool + // Quiet is a boolean value that determines if minimal output to + // the user will be displayed, this is best used for logging. + // The default is false. + Quiet bool } func localImageNameForReference(ctx context.Context, store storage.Store, srcRef types.ImageReference, spec string) (string, error) { @@ -141,9 +151,64 @@ func localImageNameForReference(ctx context.Context, store storage.Store, srcRef } // Pull copies the contents of the image from somewhere else to local storage. -func Pull(ctx context.Context, imageName string, options PullOptions) (types.ImageReference, error) { +func Pull(ctx context.Context, imageName string, options PullOptions) error { + spec := imageName systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath) - return pullImage(ctx, options.Store, imageName, options, systemContext) + srcRef, err := alltransports.ParseImageName(spec) + if err != nil { + if options.Transport == "" { + options.Transport = util.DefaultTransport + } + logrus.Debugf("error parsing image name %q, trying with transport %q: %v", spec, options.Transport, err) + transport := options.Transport + if transport != util.DefaultTransport { + transport = transport + ":" + } + spec = transport + spec + srcRef2, err2 := alltransports.ParseImageName(spec) + if err2 != nil { + return errors.Wrapf(err2, "error parsing image name %q", imageName) + } + srcRef = srcRef2 + } + if options.Quiet { + options.ReportWriter = nil // Turns off logging output + } + var names []string + if options.AllTags { + if srcRef.DockerReference() == nil { + return errors.New("Non-docker transport is currently not supported") + } + tags, err := docker.GetRepositoryTags(ctx, systemContext, srcRef) + if err != nil { + return errors.Wrapf(err, "error getting repository tags") + } + for _, tag := range tags { + name := spec + ":" + tag + names = append(names, name) + } + } else { + names = append(names, spec) + } + var errs *multierror.Error + for _, name := range names { + if options.ReportWriter != nil { + options.ReportWriter.Write([]byte("Pulling " + name + "\n")) + } + ref, err := pullImage(ctx, options.Store, name, options, systemContext) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + img, err := is.Transport.GetStoreImage(options.Store, ref) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + fmt.Printf("%s\n", img.ID) + } + + return errs.ErrorOrNil() } func pullImage(ctx context.Context, store storage.Store, imageName string, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) { diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index a0627f489..3a248f4f2 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -1130,10 +1130,14 @@ func (b *Builder) Run(command []string, options RunOptions) error { case IsolationChroot: err = chroot.RunUsingChroot(spec, path, options.Stdin, options.Stdout, options.Stderr) case IsolationOCIRootless: + moreCreateArgs := []string{"--no-new-keyring"} + if options.NoPivot { + moreCreateArgs = append(moreCreateArgs, "--no-pivot") + } if err := setupRootlessSpecChanges(spec, path, rootUID, rootGID); err != nil { return err } - err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, []string{"--no-new-keyring"}, spec, mountPoint, path, Package+"-"+filepath.Base(path)) + err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, Package+"-"+filepath.Base(path)) default: err = errors.Errorf("don't know how to run this command") } @@ -1912,21 +1916,6 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy logrus.Errorf("error setting descriptor %d (%s) blocking: %v", wfd, writeDesc[wfd], err) } } - // A helper that returns false if err is an error that would cause us - // to give up. - logIfNotRetryable := func(err error, what string) (retry bool) { - if err == nil { - return true - } - if errno, isErrno := err.(syscall.Errno); isErrno { - switch errno { - case syscall.EINTR, syscall.EAGAIN: - return true - } - } - logrus.Errorf("%s: %v", what, err) - return false - } // Pass data back and forth. pollTimeout := -1 for len(relayMap) > 0 { @@ -1941,7 +1930,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy buf := make([]byte, 8192) // Wait for new data from any input descriptor, or a notification that we're done. _, err := unix.Poll(pollFds, pollTimeout) - if !logIfNotRetryable(err, fmt.Sprintf("error waiting for stdio/terminal data to relay: %v", err)) { + if !util.LogIfNotRetryable(err, fmt.Sprintf("error waiting for stdio/terminal data to relay: %v", err)) { return } removes := make(map[int]struct{}) @@ -1971,7 +1960,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy writeFD, needToRelay := relayMap[readFD] if needToRelay { n, err := unix.Read(readFD, buf) - if !logIfNotRetryable(err, fmt.Sprintf("unable to read %s data: %v", readDesc[readFD], err)) { + if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to read %s data: %v", readDesc[readFD], err)) { return } // If it's zero-length on our stdin and we're @@ -1996,7 +1985,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy // descriptor, read all that there is to read. for pollFd.Revents&unix.POLLHUP == unix.POLLHUP { nr, err := unix.Read(readFD, buf) - logIfNotRetryable(err, fmt.Sprintf("read %s: %v", readDesc[readFD], err)) + util.LogIfUnexpectedWhileDraining(err, fmt.Sprintf("read %s: %v", readDesc[readFD], err)) if nr <= 0 { break } @@ -2019,7 +2008,7 @@ func runCopyStdio(stdio *sync.WaitGroup, copyPipes bool, stdioPipe [][]int, copy for writeFD := range relayBuffer { if relayBuffer[writeFD].Len() > 0 { n, err := unix.Write(writeFD, relayBuffer[writeFD].Bytes()) - if !logIfNotRetryable(err, fmt.Sprintf("unable to write %s data: %v", writeDesc[writeFD], err)) { + if !util.LogIfNotRetryable(err, fmt.Sprintf("unable to write %s data: %v", writeDesc[writeFD], err)) { return } if n > 0 { diff --git a/vendor/github.com/containers/buildah/util.go b/vendor/github.com/containers/buildah/util.go index 5dadec7c2..08fb99706 100644 --- a/vendor/github.com/containers/buildah/util.go +++ b/vendor/github.com/containers/buildah/util.go @@ -1,10 +1,8 @@ package buildah import ( - "archive/tar" "io" "os" - "sync" "github.com/containers/image/docker/reference" "github.com/containers/image/pkg/sysregistries" @@ -15,6 +13,7 @@ import ( "github.com/containers/storage/pkg/chrootarchive" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/reexec" + "github.com/opencontainers/image-spec/specs-go/v1" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux" "github.com/opencontainers/selinux/go-selinux/label" @@ -43,6 +42,28 @@ func copyStringSlice(s []string) []string { return t } +func copyHistory(history []v1.History) []v1.History { + if len(history) == 0 { + return nil + } + h := make([]v1.History, 0, len(history)) + for _, entry := range history { + created := entry.Created + if created != nil { + timestamp := *created + created = ×tamp + } + h = append(h, v1.History{ + Created: created, + CreatedBy: entry.CreatedBy, + Author: entry.Author, + Comment: entry.Comment, + EmptyLayer: entry.EmptyLayer, + }) + } + return h +} + func convertStorageIDMaps(UIDMap, GIDMap []idtools.IDMap) ([]rspec.LinuxIDMapping, []rspec.LinuxIDMapping) { uidmap := make([]rspec.LinuxIDMapping, 0, len(UIDMap)) gidmap := make([]rspec.LinuxIDMapping, 0, len(GIDMap)) @@ -88,42 +109,7 @@ func convertRuntimeIDMaps(UIDMap, GIDMap []rspec.LinuxIDMapping) ([]idtools.IDMa // container's ID maps, possibly overridden using the passed-in chownOpts func (b *Builder) copyFileWithTar(chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) - untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) - archiver := chrootarchive.NewArchiverWithChown(nil, chownOpts, untarMappings) - if hasher != nil { - originalUntar := archiver.Untar - archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { - contentReader, contentWriter, err := os.Pipe() - if err != nil { - return errors.Wrapf(err, "error creating pipe extract data to %q", dest) - } - defer contentReader.Close() - defer contentWriter.Close() - var hashError error - var hashWorker sync.WaitGroup - hashWorker.Add(1) - go func() { - t := tar.NewReader(contentReader) - _, err := t.Next() - if err != nil { - hashError = err - } - if _, err = io.Copy(hasher, t); err != nil && err != io.EOF { - hashError = err - } - hashWorker.Done() - }() - if err = originalUntar(io.TeeReader(tarArchive, contentWriter), dest, options); err != nil { - err = errors.Wrapf(err, "error extracting data to %q while copying", dest) - } - hashWorker.Wait() - if err == nil { - err = errors.Wrapf(hashError, "error calculating digest of data for %q while copying", dest) - } - return err - } - } - return archiver.CopyFileWithTar + return chrootarchive.CopyFileWithTarAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap) } // copyWithTar returns a function which copies a directory tree from outside of @@ -131,15 +117,7 @@ func (b *Builder) copyFileWithTar(chownOpts *idtools.IDPair, hasher io.Writer) f // container's ID maps, possibly overridden using the passed-in chownOpts func (b *Builder) copyWithTar(chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) - untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) - archiver := chrootarchive.NewArchiverWithChown(nil, chownOpts, untarMappings) - if hasher != nil { - originalUntar := archiver.Untar - archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { - return originalUntar(io.TeeReader(tarArchive, hasher), dest, options) - } - } - return archiver.CopyWithTar + return chrootarchive.CopyWithTarAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap) } // untarPath returns a function which extracts an archive in a specified @@ -147,15 +125,7 @@ func (b *Builder) copyWithTar(chownOpts *idtools.IDPair, hasher io.Writer) func( // container's ID maps, possibly overridden using the passed-in chownOpts func (b *Builder) untarPath(chownOpts *idtools.IDPair, hasher io.Writer) func(src, dest string) error { convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) - untarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) - archiver := chrootarchive.NewArchiverWithChown(nil, chownOpts, untarMappings) - if hasher != nil { - originalUntar := archiver.Untar - archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { - return originalUntar(io.TeeReader(tarArchive, hasher), dest, options) - } - } - return archiver.UntarPath + return chrootarchive.UntarPathAndChown(chownOpts, hasher, convertedUIDMap, convertedGIDMap) } // tarPath returns a function which creates an archive of a specified @@ -163,14 +133,7 @@ func (b *Builder) untarPath(chownOpts *idtools.IDPair, hasher io.Writer) func(sr // container's ID maps func (b *Builder) tarPath() func(path string) (io.ReadCloser, error) { convertedUIDMap, convertedGIDMap := convertRuntimeIDMaps(b.IDMappingOptions.UIDMap, b.IDMappingOptions.GIDMap) - tarMappings := idtools.NewIDMappingsFromMaps(convertedUIDMap, convertedGIDMap) - return func(path string) (io.ReadCloser, error) { - return archive.TarWithOptions(path, &archive.TarOptions{ - Compression: archive.Uncompressed, - UIDMaps: tarMappings.UIDs(), - GIDMaps: tarMappings.GIDs(), - }) - } + return archive.TarPath(convertedUIDMap, convertedGIDMap) } // isRegistryBlocked checks if the named registry is marked as blocked diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index f4cac522e..e46f9b7cb 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -9,6 +9,7 @@ import ( "path" "strconv" "strings" + "syscall" "github.com/containers/image/directory" dockerarchive "github.com/containers/image/docker/archive" @@ -419,3 +420,32 @@ func GetPolicyContext(ctx *types.SystemContext) (*signature.PolicyContext, error } return policyContext, nil } + +// logIfNotErrno logs the error message unless err is either nil or one of the +// listed syscall.Errno values. It returns true if it logged an error. +func logIfNotErrno(err error, what string, ignores ...syscall.Errno) (logged bool) { + if err == nil { + return false + } + if errno, isErrno := err.(syscall.Errno); isErrno { + for _, ignore := range ignores { + if errno == ignore { + return false + } + } + } + logrus.Error(what) + return true +} + +// LogIfNotRetryable logs "what" if err is set and is not an EINTR or EAGAIN +// syscall.Errno. Returns "true" if we can continue. +func LogIfNotRetryable(err error, what string) (retry bool) { + return !logIfNotErrno(err, what, syscall.EINTR, syscall.EAGAIN) +} + +// LogIfUnexpectedWhileDraining logs "what" if err is set and is not an EINTR +// or EAGAIN or EIO syscall.Errno. +func LogIfUnexpectedWhileDraining(err error, what string) { + logIfNotErrno(err, what, syscall.EINTR, syscall.EAGAIN, syscall.EIO) +} diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf index ee8192935..bda5f3965 100644 --- a/vendor/github.com/containers/buildah/vendor.conf +++ b/vendor/github.com/containers/buildah/vendor.conf @@ -1,68 +1,79 @@ -github.com/Azure/go-ansiterm master -github.com/blang/semver master -github.com/BurntSushi/toml master -github.com/containerd/continuity master +github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 +github.com/blang/semver v3.5.0 +github.com/BurntSushi/toml v0.2.0 +github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d github.com/containernetworking/cni v0.7.0-alpha1 -github.com/containers/image 0c6cc8e1420001ae39fa89d308b3b3bc5ee81c57 -github.com/boltdb/bolt master -github.com/containers/libpod c8eaf59d5f4bec249db8134c6a9fcfbcac792519 -github.com/containers/storage 60a692f7ce891feb91ce0eda87bd06bfd5651dff +github.com/containers/image v1.3 +github.com/boltdb/bolt v1.3.1 +github.com/containers/libpod v1.0 +github.com/containers/storage v1.9 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 -github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1 -github.com/docker/engine-api master -github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d -github.com/docker/go-units 0dadbb0345b35ec7ef35e228dabb8de89a65bf52 +github.com/docker/docker-credential-helpers v0.6.1 +github.com/docker/go-connections v0.4.0 +github.com/docker/go-units v0.3.2 github.com/docker/libtrust aabc10ec26b754e797f9028f4589c5b7bd90dc20 -github.com/fsouza/go-dockerclient master -github.com/ghodss/yaml master -github.com/gogo/protobuf master -github.com/golang/glog master -github.com/gorilla/context master -github.com/gorilla/mux master -github.com/hashicorp/errwrap master -github.com/hashicorp/go-cleanhttp master -github.com/hashicorp/go-multierror master -github.com/imdario/mergo master -github.com/mattn/go-runewidth master -github.com/mattn/go-shellwords master -github.com/Microsoft/go-winio master -github.com/Microsoft/hcsshim master -github.com/mistifyio/go-zfs master +github.com/fsouza/go-dockerclient 29c1814d12c072344bb91aac5d2ff719db39c523 +github.com/ghodss/yaml v1.0.0 +github.com/gogo/protobuf v1.2.0 +github.com/gorilla/context v1.1.1 +github.com/gorilla/mux v1.6.2 +github.com/hashicorp/errwrap v1.0.0 +github.com/hashicorp/go-multierror v1.0.0 +github.com/imdario/mergo v0.3.6 +github.com/mattn/go-runewidth v0.0.4 +github.com/mattn/go-shellwords v1.0.3 +github.com/Microsoft/go-winio v0.4.11 +github.com/Microsoft/hcsshim v0.8.3 +github.com/mistifyio/go-zfs v2.1.1 github.com/moby/moby f8806b18b4b92c5e1980f6e11c917fad201cd73c -github.com/mtrmac/gpgme master -github.com/Nvveen/Gotty master +github.com/mtrmac/gpgme b2432428689ca58c2b8e8dea9449d3295cf96fc9 +# TODO: Gotty has not been updated since 2012. Can we find a replacement? +github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7 github.com/opencontainers/image-spec v1.0.0 -github.com/opencontainers/runc master +github.com/opencontainers/runc v1.0.0-rc6 github.com/opencontainers/runtime-spec v1.0.0 -github.com/opencontainers/runtime-tools master -github.com/opencontainers/selinux master -github.com/openshift/imagebuilder master +github.com/opencontainers/runtime-tools v0.8.0 +github.com/opencontainers/selinux v1.1 +github.com/openshift/imagebuilder a4122153148e3b34161191f868565d8dffe65a69 github.com/ostreedev/ostree-go 9ab99253d365aac3a330d1f7281cf29f3d22820b -github.com/pborman/uuid master -github.com/pkg/errors master +github.com/pkg/errors v0.8.1 github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac -github.com/seccomp/containers-golang master -github.com/sirupsen/logrus master -github.com/syndtr/gocapability master -github.com/tchap/go-patricia master -github.com/ulikunitz/xz v0.5.4 -github.com/urfave/cli 934abfb2f102315b5794e15ebc7949e4ca253920 +github.com/seccomp/libseccomp-golang v0.9.0 +github.com/seccomp/containers-golang v0.1 +github.com/sirupsen/logrus v1.0.0 +github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2 +github.com/tchap/go-patricia v2.2.6 +github.com/ulikunitz/xz v0.5.5 github.com/vbatts/tar-split v0.10.2 -github.com/xeipuuv/gojsonpointer master -github.com/xeipuuv/gojsonreference master -github.com/xeipuuv/gojsonschema master -golang.org/x/crypto master -golang.org/x/net master -golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e -golang.org/x/sys master -golang.org/x/text master +github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6 +github.com/xeipuuv/gojsonreference bd5ef7bd5415a7ac448318e64f11a24cd21e594b +github.com/xeipuuv/gojsonschema v1.1.0 +golang.org/x/crypto ff983b9c42bc9fbf91556e191cc8efb585c16908 https://github.com/golang/crypto +golang.org/x/net 45ffb0cd1ba084b73e26dee67e667e1be5acce83 https://github.com/golang/net +golang.org/x/sync 37e7f081c4d4c64e13b10787722085407fe5d15f https://github.com/golang/sync +golang.org/x/sys 7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba https://github.com/golang/sys +golang.org/x/text e6919f6577db79269a6443b9dc46d18f2238fb5d https://github.com/golang/text gopkg.in/cheggaaa/pb.v1 v1.0.27 -gopkg.in/yaml.v2 cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b -k8s.io/apimachinery master -k8s.io/client-go master -k8s.io/kubernetes master +gopkg.in/yaml.v2 v2.2.2 +k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/klauspost/pgzip v1.2.1 github.com/klauspost/compress v1.4.1 github.com/klauspost/cpuid v1.2.0 +github.com/onsi/gomega v1.4.3 +github.com/spf13/cobra v0.0.3 +github.com/cpuguy83/go-md2man v1.0.8 +github.com/spf13/pflag v1.0.3 +github.com/inconshreveable/mousetrap v1.0.0 +github.com/russross/blackfriday v2.0.1 +github.com/mitchellh/go-homedir v1.0.0 +github.com/spf13/viper v1.3.1 +github.com/fsnotify/fsnotify v1.4.7 +github.com/hashicorp/hcl v1.0.0 +github.com/magiconair/properties v1.8.0 +github.com/mitchellh/mapstructure v1.1.2 +github.com/pelletier/go-toml v1.2.0 +github.com/spf13/afero v1.2.0 +github.com/spf13/cast v1.3.0 +github.com/spf13/jwalterweatherman v1.0.0 diff --git a/vendor/github.com/containers/image/copy/copy.go b/vendor/github.com/containers/image/copy/copy.go index e783416ad..89c7e580f 100644 --- a/vendor/github.com/containers/image/copy/copy.go +++ b/vendor/github.com/containers/image/copy/copy.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "io/ioutil" - "os" "reflect" "runtime" "strings" @@ -23,7 +22,6 @@ import ( "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh/terminal" "golang.org/x/sync/semaphore" pb "gopkg.in/cheggaaa/pb.v1" ) @@ -86,7 +84,6 @@ type copier struct { dest types.ImageDestination rawSource types.ImageSource reportWriter io.Writer - progressOutput io.Writer progressInterval time.Duration progress chan types.ProgressProperties blobInfoCache types.BlobInfoCache @@ -155,19 +152,11 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, } }() - // If reportWriter is not a TTY (e.g., when piping to a file), do not - // print the progress bars to avoid long and hard to parse output. - // createProgressBar() will print a single line instead. - progressOutput := reportWriter - if !isTTY(reportWriter) { - progressOutput = ioutil.Discard - } copyInParallel := dest.HasThreadSafePutBlob() && rawSource.HasThreadSafeGetBlob() c := &copier{ dest: dest, rawSource: rawSource, reportWriter: reportWriter, - progressOutput: progressOutput, progressInterval: options.ProgressInterval, progress: options.Progress, copyInParallel: copyInParallel, @@ -405,30 +394,17 @@ func shortDigest(d digest.Digest) string { return d.Encoded()[:12] } -// createProgressBar creates a pb.ProgressBar. Note that if the copier's -// reportWriter is ioutil.Discard, the progress bar's output will be discarded -// and a single line will be printed instead. -func (c *copier) createProgressBar(srcInfo types.BlobInfo, kind string) *pb.ProgressBar { +// createProgressBar creates a pb.ProgressBar. +func createProgressBar(srcInfo types.BlobInfo, kind string, writer io.Writer) *pb.ProgressBar { bar := pb.New(int(srcInfo.Size)).SetUnits(pb.U_BYTES) bar.SetMaxWidth(80) bar.ShowTimeLeft = false bar.ShowPercent = false bar.Prefix(fmt.Sprintf("Copying %s %s:", kind, shortDigest(srcInfo.Digest))) - bar.Output = c.progressOutput - if bar.Output == ioutil.Discard { - c.Printf("Copying %s %s\n", kind, srcInfo.Digest) - } + bar.Output = writer return bar } -// isTTY returns true if the io.Writer is a file and a tty. -func isTTY(w io.Writer) bool { - if f, ok := w.(*os.File); ok { - return terminal.IsTerminal(int(f.Fd())) - } - return false -} - // copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest. func (ic *imageCopier) copyLayers(ctx context.Context) error { srcInfos := ic.src.LayerInfos() @@ -480,7 +456,7 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error { bar.Finish() } else { cld.destInfo = srcLayer - logrus.Debugf("Skipping foreign layer %q copy to %s", cld.destInfo.Digest, ic.c.dest.Reference().Transport().Name()) + logrus.Debugf("Skipping foreign layer %q copy to %s\n", cld.destInfo.Digest, ic.c.dest.Reference().Transport().Name()) bar.Prefix(fmt.Sprintf("Skipping blob %s (foreign layer):", shortDigest(srcLayer.Digest))) bar.Add64(bar.Total) bar.Finish() @@ -493,13 +469,12 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error { progressBars := make([]*pb.ProgressBar, numLayers) for i, srcInfo := range srcInfos { - bar := ic.c.createProgressBar(srcInfo, "blob") + bar := createProgressBar(srcInfo, "blob", nil) progressBars[i] = bar } progressPool := pb.NewPool(progressBars...) - progressPool.Output = ic.c.progressOutput - + progressPool.Output = ic.c.reportWriter if err := progressPool.Start(); err != nil { return errors.Wrapf(err, "error creating progress-bar pool") } @@ -593,7 +568,7 @@ func (c *copier) copyConfig(ctx context.Context, src types.Image) error { if err != nil { return errors.Wrapf(err, "Error reading config blob %s", srcInfo.Digest) } - bar := c.createProgressBar(srcInfo, "config") + bar := createProgressBar(srcInfo, "config", c.reportWriter) defer bar.Finish() bar.Start() destInfo, err := c.copyBlobFromStream(ctx, bytes.NewReader(configBlob), srcInfo, nil, false, true, bar) diff --git a/vendor/github.com/containers/image/docker/docker_client.go b/vendor/github.com/containers/image/docker/docker_client.go index 43eb22ba2..23d2ac70f 100644 --- a/vendor/github.com/containers/image/docker/docker_client.go +++ b/vendor/github.com/containers/image/docker/docker_client.go @@ -91,6 +91,7 @@ type dockerClient struct { password string signatureBase signatureStorageBase scope authScope + extraScope *authScope // If non-nil, a temporary extra token scope (necessary for mounting from another repo) // The following members are detected registry properties: // They are set after a successful detectProperties(), and never change afterwards. scheme string // Empty value also used to indicate detectProperties() has not yet succeeded. @@ -281,7 +282,7 @@ func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password client.username = username client.password = password - resp, err := client.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth, nil) + resp, err := client.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth) if err != nil { return err } @@ -361,8 +362,8 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima q.Set("n", strconv.Itoa(limit)) u.RawQuery = q.Encode() - logrus.Debugf("trying to talk to v1 search endpoint") - resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth, nil) + logrus.Debugf("trying to talk to v1 search endpoint\n") + resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth) if err != nil { logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, err) } else { @@ -378,8 +379,8 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima } } - logrus.Debugf("trying to talk to v2 search endpoint") - resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth, nil) + logrus.Debugf("trying to talk to v2 search endpoint\n") + resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth) if err != nil { logrus.Debugf("error getting search results from v2 endpoint %q: %v", registry, err) } else { @@ -408,20 +409,20 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima // makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. // The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/. -func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth, extraScope *authScope) (*http.Response, error) { +func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth) (*http.Response, error) { if err := c.detectProperties(ctx); err != nil { return nil, err } url := fmt.Sprintf("%s://%s%s", c.scheme, c.registry, path) - return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth, extraScope) + return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth) } // makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. // streamLen, if not -1, specifies the length of the data expected on stream. // makeRequest should generally be preferred. // TODO(runcom): too many arguments here, use a struct -func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth, extraScope *authScope) (*http.Response, error) { +func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth) (*http.Response, error) { req, err := http.NewRequest(method, url, stream) if err != nil { return nil, err @@ -440,7 +441,7 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url req.Header.Add("User-Agent", c.sys.DockerRegistryUserAgent) } if auth == v2Auth { - if err := c.setupRequestAuth(req, extraScope); err != nil { + if err := c.setupRequestAuth(req); err != nil { return nil, err } } @@ -459,7 +460,7 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url // 2) gcr.io is sending 401 without a WWW-Authenticate header in the real request // // debugging: https://github.com/containers/image/pull/211#issuecomment-273426236 and follows up -func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope) error { +func (c *dockerClient) setupRequestAuth(req *http.Request) error { if len(c.challenges) == 0 { return nil } @@ -473,10 +474,10 @@ func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope case "bearer": cacheKey := "" scopes := []authScope{c.scope} - if extraScope != nil { + if c.extraScope != nil { // Using ':' as a separator here is unambiguous because getBearerToken below uses the same separator when formatting a remote request (and because repository names can't contain colons). - cacheKey = fmt.Sprintf("%s:%s", extraScope.remoteName, extraScope.actions) - scopes = append(scopes, *extraScope) + cacheKey = fmt.Sprintf("%s:%s", c.extraScope.remoteName, c.extraScope.actions) + scopes = append(scopes, *c.extraScope) } var token bearerToken t, inCache := c.tokenCache.Load(cacheKey) @@ -563,7 +564,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error { ping := func(scheme string) error { url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry) - resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil) + resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth) if err != nil { logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err) return err @@ -590,7 +591,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error { // best effort to understand if we're talking to a V1 registry pingV1 := func(scheme string) bool { url := fmt.Sprintf(resolvedPingV1URL, scheme, c.registry) - resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil) + resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth) if err != nil { logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err) return false @@ -624,7 +625,7 @@ func (c *dockerClient) detectProperties(ctx context.Context) error { // using the original data structures. func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) { path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest) - res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil) + res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/image/docker/docker_image.go b/vendor/github.com/containers/image/docker/docker_image.go index 530c7513e..2ab95f329 100644 --- a/vendor/github.com/containers/image/docker/docker_image.go +++ b/vendor/github.com/containers/image/docker/docker_image.go @@ -66,7 +66,7 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types. tags := make([]string, 0) for { - res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil) + res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/image/docker/docker_image_dest.go b/vendor/github.com/containers/image/docker/docker_image_dest.go index e9882c024..973d160d0 100644 --- a/vendor/github.com/containers/image/docker/docker_image_dest.go +++ b/vendor/github.com/containers/image/docker/docker_image_dest.go @@ -113,7 +113,7 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) { // HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently. func (d *dockerImageDestination) HasThreadSafePutBlob() bool { - return true + return false } // PutBlob writes contents of stream and returns data representing the result (with all data filled in). @@ -140,7 +140,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, // FIXME? Chunked upload, progress reporting, etc. uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref)) logrus.Debugf("Uploading %s", uploadPath) - res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth, nil) + res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth) if err != nil { return types.BlobInfo{}, err } @@ -157,7 +157,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, digester := digest.Canonical.Digester() sizeCounter := &sizeCounter{} tee := io.TeeReader(stream, io.MultiWriter(digester.Hash(), sizeCounter)) - res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth, nil) + res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth) if err != nil { logrus.Debugf("Error uploading layer chunked, response %#v", res) return types.BlobInfo{}, err @@ -176,7 +176,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, // TODO: check inputInfo.Digest == computedDigest https://github.com/containers/image/pull/70#discussion_r77646717 locationQuery.Set("digest", computedDigest.String()) uploadLocation.RawQuery = locationQuery.Encode() - res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth, nil) + res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth) if err != nil { return types.BlobInfo{}, err } @@ -194,10 +194,10 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, // blobExists returns true iff repo contains a blob with digest, and if so, also its size. // If the destination does not contain the blob, or it is unknown, blobExists ordinarily returns (false, -1, nil); // it returns a non-nil error only on an unexpected failure. -func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.Named, digest digest.Digest, extraScope *authScope) (bool, int64, error) { +func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.Named, digest digest.Digest) (bool, int64, error) { checkPath := fmt.Sprintf(blobsPath, reference.Path(repo), digest.String()) logrus.Debugf("Checking %s", checkPath) - res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth, extraScope) + res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth) if err != nil { return false, -1, err } @@ -218,7 +218,7 @@ func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference. } // mountBlob tries to mount blob srcDigest from srcRepo to the current destination. -func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo reference.Named, srcDigest digest.Digest, extraScope *authScope) error { +func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo reference.Named, srcDigest digest.Digest) error { u := url.URL{ Path: fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref)), RawQuery: url.Values{ @@ -228,7 +228,7 @@ func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo referenc } mountPath := u.String() logrus.Debugf("Trying to mount %s", mountPath) - res, err := d.c.makeRequest(ctx, "POST", mountPath, nil, nil, v2Auth, extraScope) + res, err := d.c.makeRequest(ctx, "POST", mountPath, nil, nil, v2Auth) if err != nil { return err } @@ -246,7 +246,7 @@ func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo referenc return errors.Wrap(err, "Error determining upload URL after a mount attempt") } logrus.Debugf("... started an upload instead of mounting, trying to cancel at %s", uploadLocation.String()) - res2, err := d.c.makeRequestToResolvedURL(ctx, "DELETE", uploadLocation.String(), nil, nil, -1, v2Auth, extraScope) + res2, err := d.c.makeRequestToResolvedURL(ctx, "DELETE", uploadLocation.String(), nil, nil, -1, v2Auth) if err != nil { logrus.Debugf("Error trying to cancel an inadvertent upload: %s", err) } else { @@ -276,7 +276,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types. } // First, check whether the blob happens to already exist at the destination. - exists, size, err := d.blobExists(ctx, d.ref.ref, info.Digest, nil) + exists, size, err := d.blobExists(ctx, d.ref.ref, info.Digest) if err != nil { return false, types.BlobInfo{}, err } @@ -286,6 +286,15 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types. } // Then try reusing blobs from other locations. + + // Checking candidateRepo, and mounting from it, requires an expanded token scope. + // We still want to reuse the ping information and other aspects of the client, so rather than make a fresh copy, there is this a bit ugly extraScope hack. + if d.c.extraScope != nil { + return false, types.BlobInfo{}, errors.New("Internal error: dockerClient.extraScope was set before TryReusingBlob") + } + defer func() { + d.c.extraScope = nil + }() for _, candidate := range cache.CandidateLocations(d.ref.Transport(), bicTransportScope(d.ref), info.Digest, canSubstitute) { candidateRepo, err := parseBICLocationReference(candidate.Location) if err != nil { @@ -305,10 +314,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types. } // Whatever happens here, don't abort the entire operation. It's likely we just don't have permissions, and if it is a critical network error, we will find out soon enough anyway. - - // Checking candidateRepo, and mounting from it, requires an - // expanded token scope. - extraScope := &authScope{ + d.c.extraScope = &authScope{ remoteName: reference.Path(candidateRepo), actions: "pull", } @@ -319,7 +325,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types. // Even worse, docker/distribution does not actually reasonably implement canceling uploads // (it would require a "delete" action in the token, and Quay does not give that to anyone, so we can't ask); // so, be a nice client and don't create unnecesary upload sessions on the server. - exists, size, err := d.blobExists(ctx, candidateRepo, candidate.Digest, extraScope) + exists, size, err := d.blobExists(ctx, candidateRepo, candidate.Digest) if err != nil { logrus.Debugf("... Failed: %v", err) continue @@ -329,7 +335,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types. continue // logrus.Debug() already happened in blobExists } if candidateRepo.Name() != d.ref.ref.Name() { - if err := d.mountBlob(ctx, candidateRepo, candidate.Digest, extraScope); err != nil { + if err := d.mountBlob(ctx, candidateRepo, candidate.Digest); err != nil { logrus.Debugf("... Mount failed: %v", err) continue } @@ -363,7 +369,7 @@ func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte) erro if mimeType != "" { headers["Content-Type"] = []string{mimeType} } - res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth, nil) + res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth) if err != nil { return err } @@ -568,7 +574,7 @@ sigExists: } path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), d.manifestDigest.String()) - res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth, nil) + res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth) if err != nil { return err } diff --git a/vendor/github.com/containers/image/docker/docker_image_src.go b/vendor/github.com/containers/image/docker/docker_image_src.go index 063511535..c88ff2f34 100644 --- a/vendor/github.com/containers/image/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/docker/docker_image_src.go @@ -89,7 +89,7 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest) headers := make(map[string][]string) headers["Accept"] = manifest.DefaultRequestedManifestMIMETypes - res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth, nil) + res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth) if err != nil { return nil, "", err } @@ -137,7 +137,7 @@ func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string) err error ) for _, url := range urls { - resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil) + resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth) if err == nil { if resp.StatusCode != http.StatusOK { err = errors.Errorf("error fetching external blob from %q: %d (%s)", url, resp.StatusCode, http.StatusText(resp.StatusCode)) @@ -176,7 +176,7 @@ func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String()) logrus.Debugf("Downloading %s", path) - res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil) + res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth) if err != nil { return nil, 0, err } @@ -340,7 +340,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere return err } getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail) - get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth, nil) + get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth) if err != nil { return err } @@ -362,7 +362,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere // When retrieving the digest from a registry >= 2.3 use the following header: // "Accept": "application/vnd.docker.distribution.manifest.v2+json" - delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth, nil) + delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth) if err != nil { return err } diff --git a/vendor/github.com/containers/image/version/version.go b/vendor/github.com/containers/image/version/version.go index 10075992d..6644bcff3 100644 --- a/vendor/github.com/containers/image/version/version.go +++ b/vendor/github.com/containers/image/version/version.go @@ -8,7 +8,7 @@ const ( // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 1 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 5 + VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "-dev" diff --git a/vendor/github.com/containers/storage/pkg/archive/archive.go b/vendor/github.com/containers/storage/pkg/archive/archive.go index 228d8bb82..ba1704250 100644 --- a/vendor/github.com/containers/storage/pkg/archive/archive.go +++ b/vendor/github.com/containers/storage/pkg/archive/archive.go @@ -13,6 +13,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" "syscall" "github.com/containers/storage/pkg/fileutils" @@ -23,6 +24,7 @@ import ( "github.com/containers/storage/pkg/system" gzip "github.com/klauspost/pgzip" rsystem "github.com/opencontainers/runc/libcontainer/system" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -1331,3 +1333,108 @@ const ( // HeaderSize is the size in bytes of a tar header HeaderSize = 512 ) + +// NewArchiver returns a new Archiver +func NewArchiver(idMappings *idtools.IDMappings) *Archiver { + if idMappings == nil { + idMappings = &idtools.IDMappings{} + } + return &Archiver{Untar: Untar, TarIDMappings: idMappings, UntarIDMappings: idMappings} +} + +// NewArchiverWithChown returns a new Archiver which uses Untar and the provided ID mapping configuration on both ends +func NewArchiverWithChown(tarIDMappings *idtools.IDMappings, chownOpts *idtools.IDPair, untarIDMappings *idtools.IDMappings) *Archiver { + if tarIDMappings == nil { + tarIDMappings = &idtools.IDMappings{} + } + if untarIDMappings == nil { + untarIDMappings = &idtools.IDMappings{} + } + return &Archiver{Untar: Untar, TarIDMappings: tarIDMappings, ChownOpts: chownOpts, UntarIDMappings: untarIDMappings} +} + +// CopyFileWithTarAndChown returns a function which copies a single file from outside +// of any container into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func CopyFileWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error { + untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap) + archiver := NewArchiverWithChown(nil, chownOpts, untarMappings) + if hasher != nil { + originalUntar := archiver.Untar + archiver.Untar = func(tarArchive io.Reader, dest string, options *TarOptions) error { + contentReader, contentWriter, err := os.Pipe() + if err != nil { + return errors.Wrapf(err, "error creating pipe extract data to %q", dest) + } + defer contentReader.Close() + defer contentWriter.Close() + var hashError error + var hashWorker sync.WaitGroup + hashWorker.Add(1) + go func() { + t := tar.NewReader(contentReader) + _, err := t.Next() + if err != nil { + hashError = err + } + if _, err = io.Copy(hasher, t); err != nil && err != io.EOF { + hashError = err + } + hashWorker.Done() + }() + if err = originalUntar(io.TeeReader(tarArchive, contentWriter), dest, options); err != nil { + err = errors.Wrapf(err, "error extracting data to %q while copying", dest) + } + hashWorker.Wait() + if err == nil { + err = errors.Wrapf(hashError, "error calculating digest of data for %q while copying", dest) + } + return err + } + } + return archiver.CopyFileWithTar +} + +// CopyWithTarAndChown returns a function which copies a directory tree from outside of +// any container into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func CopyWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error { + untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap) + archiver := NewArchiverWithChown(nil, chownOpts, untarMappings) + if hasher != nil { + originalUntar := archiver.Untar + archiver.Untar = func(tarArchive io.Reader, dest string, options *TarOptions) error { + return originalUntar(io.TeeReader(tarArchive, hasher), dest, options) + } + } + return archiver.CopyWithTar +} + +// UntarPathAndChown returns a function which extracts an archive in a specified +// location into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func UntarPathAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error { + untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap) + archiver := NewArchiverWithChown(nil, chownOpts, untarMappings) + if hasher != nil { + originalUntar := archiver.Untar + archiver.Untar = func(tarArchive io.Reader, dest string, options *TarOptions) error { + return originalUntar(io.TeeReader(tarArchive, hasher), dest, options) + } + } + return archiver.UntarPath +} + +// TarPath returns a function which creates an archive of a specified +// location in the container's filesystem, mapping permissions using the +// container's ID maps +func TarPath(uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(path string) (io.ReadCloser, error) { + tarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap) + return func(path string) (io.ReadCloser, error) { + return TarWithOptions(path, &TarOptions{ + Compression: Uncompressed, + UIDMaps: tarMappings.UIDs(), + GIDMaps: tarMappings.GIDs(), + }) + } +} diff --git a/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go b/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go index dde8d44d3..a36ff1cb1 100644 --- a/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go +++ b/vendor/github.com/containers/storage/pkg/chrootarchive/archive.go @@ -1,34 +1,32 @@ package chrootarchive import ( + "archive/tar" "fmt" "io" "io/ioutil" "os" "path/filepath" + "sync" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" rsystem "github.com/opencontainers/runc/libcontainer/system" + "github.com/pkg/errors" ) // NewArchiver returns a new Archiver which uses chrootarchive.Untar func NewArchiver(idMappings *idtools.IDMappings) *archive.Archiver { - if idMappings == nil { - idMappings = &idtools.IDMappings{} - } - return &archive.Archiver{Untar: Untar, TarIDMappings: idMappings, UntarIDMappings: idMappings} + archiver := archive.NewArchiver(idMappings) + archiver.Untar = Untar + return archiver } // NewArchiverWithChown returns a new Archiver which uses chrootarchive.Untar and the provided ID mapping configuration on both ends func NewArchiverWithChown(tarIDMappings *idtools.IDMappings, chownOpts *idtools.IDPair, untarIDMappings *idtools.IDMappings) *archive.Archiver { - if tarIDMappings == nil { - tarIDMappings = &idtools.IDMappings{} - } - if untarIDMappings == nil { - untarIDMappings = &idtools.IDMappings{} - } - return &archive.Archiver{Untar: Untar, TarIDMappings: tarIDMappings, ChownOpts: chownOpts, UntarIDMappings: untarIDMappings} + archiver := archive.NewArchiverWithChown(tarIDMappings, chownOpts, untarIDMappings) + archiver.Untar = Untar + return archiver } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, @@ -81,3 +79,75 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions return invokeUnpack(r, dest, options) } + +// CopyFileWithTarAndChown returns a function which copies a single file from outside +// of any container into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func CopyFileWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error { + untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap) + archiver := NewArchiverWithChown(nil, chownOpts, untarMappings) + if hasher != nil { + originalUntar := archiver.Untar + archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { + contentReader, contentWriter, err := os.Pipe() + if err != nil { + return errors.Wrapf(err, "error creating pipe extract data to %q", dest) + } + defer contentReader.Close() + defer contentWriter.Close() + var hashError error + var hashWorker sync.WaitGroup + hashWorker.Add(1) + go func() { + t := tar.NewReader(contentReader) + _, err := t.Next() + if err != nil { + hashError = err + } + if _, err = io.Copy(hasher, t); err != nil && err != io.EOF { + hashError = err + } + hashWorker.Done() + }() + if err = originalUntar(io.TeeReader(tarArchive, contentWriter), dest, options); err != nil { + err = errors.Wrapf(err, "error extracting data to %q while copying", dest) + } + hashWorker.Wait() + if err == nil { + err = errors.Wrapf(hashError, "error calculating digest of data for %q while copying", dest) + } + return err + } + } + return archiver.CopyFileWithTar +} + +// CopyWithTarAndChown returns a function which copies a directory tree from outside of +// any container into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func CopyWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error { + untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap) + archiver := NewArchiverWithChown(nil, chownOpts, untarMappings) + if hasher != nil { + originalUntar := archiver.Untar + archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { + return originalUntar(io.TeeReader(tarArchive, hasher), dest, options) + } + } + return archiver.CopyWithTar +} + +// UntarPathAndChown returns a function which extracts an archive in a specified +// location into our working container, mapping permissions using the +// container's ID maps, possibly overridden using the passed-in chownOpts +func UntarPathAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error { + untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap) + archiver := NewArchiverWithChown(nil, chownOpts, untarMappings) + if hasher != nil { + originalUntar := archiver.Untar + archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error { + return originalUntar(io.TeeReader(tarArchive, hasher), dest, options) + } + } + return archiver.UntarPath +} diff --git a/vendor/github.com/inconshreveable/mousetrap/LICENSE b/vendor/github.com/inconshreveable/mousetrap/LICENSE new file mode 100644 index 000000000..5f0d1fb6a --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/LICENSE @@ -0,0 +1,13 @@ +Copyright 2014 Alan Shreve + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/inconshreveable/mousetrap/README.md b/vendor/github.com/inconshreveable/mousetrap/README.md new file mode 100644 index 000000000..7a950d177 --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/README.md @@ -0,0 +1,23 @@ +# mousetrap + +mousetrap is a tiny library that answers a single question. + +On a Windows machine, was the process invoked by someone double clicking on +the executable file while browsing in explorer? + +### Motivation + +Windows developers unfamiliar with command line tools will often "double-click" +the executable for a tool. Because most CLI tools print the help and then exit +when invoked without arguments, this is often very frustrating for those users. + +mousetrap provides a way to detect these invocations so that you can provide +more helpful behavior and instructions on how to run the CLI tool. To see what +this looks like, both from an organizational and a technical perspective, see +https://inconshreveable.com/09-09-2014/sweat-the-small-stuff/ + +### The interface + +The library exposes a single interface: + + func StartedByExplorer() (bool) diff --git a/vendor/github.com/inconshreveable/mousetrap/trap_others.go b/vendor/github.com/inconshreveable/mousetrap/trap_others.go new file mode 100644 index 000000000..9d2d8a4ba --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/trap_others.go @@ -0,0 +1,15 @@ +// +build !windows + +package mousetrap + +// StartedByExplorer returns true if the program was invoked by the user +// double-clicking on the executable from explorer.exe +// +// It is conservative and returns false if any of the internal calls fail. +// It does not guarantee that the program was run from a terminal. It only can tell you +// whether it was launched from explorer.exe +// +// On non-Windows platforms, it always returns false. +func StartedByExplorer() bool { + return false +} diff --git a/vendor/github.com/inconshreveable/mousetrap/trap_windows.go b/vendor/github.com/inconshreveable/mousetrap/trap_windows.go new file mode 100644 index 000000000..336142a5e --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/trap_windows.go @@ -0,0 +1,98 @@ +// +build windows +// +build !go1.4 + +package mousetrap + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +const ( + // defined by the Win32 API + th32cs_snapprocess uintptr = 0x2 +) + +var ( + kernel = syscall.MustLoadDLL("kernel32.dll") + CreateToolhelp32Snapshot = kernel.MustFindProc("CreateToolhelp32Snapshot") + Process32First = kernel.MustFindProc("Process32FirstW") + Process32Next = kernel.MustFindProc("Process32NextW") +) + +// ProcessEntry32 structure defined by the Win32 API +type processEntry32 struct { + dwSize uint32 + cntUsage uint32 + th32ProcessID uint32 + th32DefaultHeapID int + th32ModuleID uint32 + cntThreads uint32 + th32ParentProcessID uint32 + pcPriClassBase int32 + dwFlags uint32 + szExeFile [syscall.MAX_PATH]uint16 +} + +func getProcessEntry(pid int) (pe *processEntry32, err error) { + snapshot, _, e1 := CreateToolhelp32Snapshot.Call(th32cs_snapprocess, uintptr(0)) + if snapshot == uintptr(syscall.InvalidHandle) { + err = fmt.Errorf("CreateToolhelp32Snapshot: %v", e1) + return + } + defer syscall.CloseHandle(syscall.Handle(snapshot)) + + var processEntry processEntry32 + processEntry.dwSize = uint32(unsafe.Sizeof(processEntry)) + ok, _, e1 := Process32First.Call(snapshot, uintptr(unsafe.Pointer(&processEntry))) + if ok == 0 { + err = fmt.Errorf("Process32First: %v", e1) + return + } + + for { + if processEntry.th32ProcessID == uint32(pid) { + pe = &processEntry + return + } + + ok, _, e1 = Process32Next.Call(snapshot, uintptr(unsafe.Pointer(&processEntry))) + if ok == 0 { + err = fmt.Errorf("Process32Next: %v", e1) + return + } + } +} + +func getppid() (pid int, err error) { + pe, err := getProcessEntry(os.Getpid()) + if err != nil { + return + } + + pid = int(pe.th32ParentProcessID) + return +} + +// StartedByExplorer returns true if the program was invoked by the user double-clicking +// on the executable from explorer.exe +// +// It is conservative and returns false if any of the internal calls fail. +// It does not guarantee that the program was run from a terminal. It only can tell you +// whether it was launched from explorer.exe +func StartedByExplorer() bool { + ppid, err := getppid() + if err != nil { + return false + } + + pe, err := getProcessEntry(ppid) + if err != nil { + return false + } + + name := syscall.UTF16ToString(pe.szExeFile[:]) + return name == "explorer.exe" +} diff --git a/vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go b/vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go new file mode 100644 index 000000000..9a28e57c3 --- /dev/null +++ b/vendor/github.com/inconshreveable/mousetrap/trap_windows_1.4.go @@ -0,0 +1,46 @@ +// +build windows +// +build go1.4 + +package mousetrap + +import ( + "os" + "syscall" + "unsafe" +) + +func getProcessEntry(pid int) (*syscall.ProcessEntry32, error) { + snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0) + if err != nil { + return nil, err + } + defer syscall.CloseHandle(snapshot) + var procEntry syscall.ProcessEntry32 + procEntry.Size = uint32(unsafe.Sizeof(procEntry)) + if err = syscall.Process32First(snapshot, &procEntry); err != nil { + return nil, err + } + for { + if procEntry.ProcessID == uint32(pid) { + return &procEntry, nil + } + err = syscall.Process32Next(snapshot, &procEntry) + if err != nil { + return nil, err + } + } +} + +// StartedByExplorer returns true if the program was invoked by the user double-clicking +// on the executable from explorer.exe +// +// It is conservative and returns false if any of the internal calls fail. +// It does not guarantee that the program was run from a terminal. It only can tell you +// whether it was launched from explorer.exe +func StartedByExplorer() bool { + pe, err := getProcessEntry(os.Getppid()) + if err != nil { + return false + } + return "explorer.exe" == syscall.UTF16ToString(pe.ExeFile[:]) +} diff --git a/vendor/github.com/spf13/cobra/LICENSE.txt b/vendor/github.com/spf13/cobra/LICENSE.txt new file mode 100644 index 000000000..298f0e266 --- /dev/null +++ b/vendor/github.com/spf13/cobra/LICENSE.txt @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/github.com/spf13/cobra/README.md b/vendor/github.com/spf13/cobra/README.md new file mode 100644 index 000000000..851fcc087 --- /dev/null +++ b/vendor/github.com/spf13/cobra/README.md @@ -0,0 +1,736 @@ +![cobra logo](https://cloud.githubusercontent.com/assets/173412/10886352/ad566232-814f-11e5-9cd0-aa101788c117.png) + +Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files. + +Many of the most widely used Go projects are built using Cobra including: + +* [Kubernetes](http://kubernetes.io/) +* [Hugo](http://gohugo.io) +* [rkt](https://github.com/coreos/rkt) +* [etcd](https://github.com/coreos/etcd) +* [Moby (former Docker)](https://github.com/moby/moby) +* [Docker (distribution)](https://github.com/docker/distribution) +* [OpenShift](https://www.openshift.com/) +* [Delve](https://github.com/derekparker/delve) +* [GopherJS](http://www.gopherjs.org/) +* [CockroachDB](http://www.cockroachlabs.com/) +* [Bleve](http://www.blevesearch.com/) +* [ProjectAtomic (enterprise)](http://www.projectatomic.io/) +* [GiantSwarm's swarm](https://github.com/giantswarm/cli) +* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) +* [rclone](http://rclone.org/) +* [nehm](https://github.com/bogem/nehm) +* [Pouch](https://github.com/alibaba/pouch) + +[![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) +[![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) +[![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) + +# Table of Contents + +- [Overview](#overview) +- [Concepts](#concepts) + * [Commands](#commands) + * [Flags](#flags) +- [Installing](#installing) +- [Getting Started](#getting-started) + * [Using the Cobra Generator](#using-the-cobra-generator) + * [Using the Cobra Library](#using-the-cobra-library) + * [Working with Flags](#working-with-flags) + * [Positional and Custom Arguments](#positional-and-custom-arguments) + * [Example](#example) + * [Help Command](#help-command) + * [Usage Message](#usage-message) + * [PreRun and PostRun Hooks](#prerun-and-postrun-hooks) + * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens) + * [Generating documentation for your command](#generating-documentation-for-your-command) + * [Generating bash completions](#generating-bash-completions) +- [Contributing](#contributing) +- [License](#license) + +# Overview + +Cobra is a library providing a simple interface to create powerful modern CLI +interfaces similar to git & go tools. + +Cobra is also an application that will generate your application scaffolding to rapidly +develop a Cobra-based application. + +Cobra provides: +* Easy subcommand-based CLIs: `app server`, `app fetch`, etc. +* Fully POSIX-compliant flags (including short & long versions) +* Nested subcommands +* Global, local and cascading flags +* Easy generation of applications & commands with `cobra init appname` & `cobra add cmdname` +* Intelligent suggestions (`app srver`... did you mean `app server`?) +* Automatic help generation for commands and flags +* Automatic help flag recognition of `-h`, `--help`, etc. +* Automatically generated bash autocomplete for your application +* Automatically generated man pages for your application +* Command aliases so you can change things without breaking them +* The flexibility to define your own help, usage, etc. +* Optional tight integration with [viper](http://github.com/spf13/viper) for 12-factor apps + +# Concepts + +Cobra is built on a structure of commands, arguments & flags. + +**Commands** represent actions, **Args** are things and **Flags** are modifiers for those actions. + +The best applications will read like sentences when used. Users will know how +to use the application because they will natively understand how to use it. + +The pattern to follow is +`APPNAME VERB NOUN --ADJECTIVE.` + or +`APPNAME COMMAND ARG --FLAG` + +A few good real world examples may better illustrate this point. + +In the following example, 'server' is a command, and 'port' is a flag: + + hugo server --port=1313 + +In this command we are telling Git to clone the url bare. + + git clone URL --bare + +## Commands + +Command is the central point of the application. Each interaction that +the application supports will be contained in a Command. A command can +have children commands and optionally run an action. + +In the example above, 'server' is the command. + +[More about cobra.Command](https://godoc.org/github.com/spf13/cobra#Command) + +## Flags + +A flag is a way to modify the behavior of a command. Cobra supports +fully POSIX-compliant flags as well as the Go [flag package](https://golang.org/pkg/flag/). +A Cobra command can define flags that persist through to children commands +and flags that are only available to that command. + +In the example above, 'port' is the flag. + +Flag functionality is provided by the [pflag +library](https://github.com/spf13/pflag), a fork of the flag standard library +which maintains the same interface while adding POSIX compliance. + +# Installing +Using Cobra is easy. First, use `go get` to install the latest version +of the library. This command will install the `cobra` generator executable +along with the library and its dependencies: + + go get -u github.com/spf13/cobra/cobra + +Next, include Cobra in your application: + +```go +import "github.com/spf13/cobra" +``` + +# Getting Started + +While you are welcome to provide your own organization, typically a Cobra-based +application will follow the following organizational structure: + +``` + ▾ appName/ + ▾ cmd/ + add.go + your.go + commands.go + here.go + main.go +``` + +In a Cobra app, typically the main.go file is very bare. It serves one purpose: initializing Cobra. + +```go +package main + +import ( + "fmt" + "os" + + "{pathToYourApp}/cmd" +) + +func main() { + cmd.Execute() +} +``` + +## Using the Cobra Generator + +Cobra provides its own program that will create your application and add any +commands you want. It's the easiest way to incorporate Cobra into your application. + +[Here](https://github.com/spf13/cobra/blob/master/cobra/README.md) you can find more information about it. + +## Using the Cobra Library + +To manually implement Cobra you need to create a bare main.go file and a rootCmd file. +You will optionally provide additional commands as you see fit. + +### Create rootCmd + +Cobra doesn't require any special constructors. Simply create your commands. + +Ideally you place this in app/cmd/root.go: + +```go +var rootCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + Long: `A Fast and Flexible Static Site Generator built with + love by spf13 and friends in Go. + Complete documentation is available at http://hugo.spf13.com`, + Run: func(cmd *cobra.Command, args []string) { + // Do Stuff Here + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} +``` + +You will additionally define flags and handle configuration in your init() function. + +For example cmd/root.go: + +```go +import ( + "fmt" + "os" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func init() { + cobra.OnInitialize(initConfig) + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") + rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") + rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") + rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") + rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) + viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase")) + viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) + viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>") + viper.SetDefault("license", "apache") +} + +func initConfig() { + // Don't forget to read config either from cfgFile or from home directory! + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Search config in home directory with name ".cobra" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".cobra") + } + + if err := viper.ReadInConfig(); err != nil { + fmt.Println("Can't read config:", err) + os.Exit(1) + } +} +``` + +### Create your main.go + +With the root command you need to have your main function execute it. +Execute should be run on the root for clarity, though it can be called on any command. + +In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra. + +```go +package main + +import ( + "fmt" + "os" + + "{pathToYourApp}/cmd" +) + +func main() { + cmd.Execute() +} +``` + +### Create additional commands + +Additional commands can be defined and typically are each given their own file +inside of the cmd/ directory. + +If you wanted to create a version command you would create cmd/version.go and +populate it with the following: + +```go +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of Hugo", + Long: `All software has versions. This is Hugo's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Hugo Static Site Generator v0.9 -- HEAD") + }, +} +``` + +## Working with Flags + +Flags provide modifiers to control how the action command operates. + +### Assign flags to a command + +Since the flags are defined and used in different locations, we need to +define a variable outside with the correct scope to assign the flag to +work with. + +```go +var Verbose bool +var Source string +``` + +There are two different approaches to assign a flag. + +### Persistent Flags + +A flag can be 'persistent' meaning that this flag will be available to the +command it's assigned to as well as every command under that command. For +global flags, assign a flag as a persistent flag on the root. + +```go +rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") +``` + +### Local Flags + +A flag can also be assigned locally which will only apply to that specific command. + +```go +rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +``` + +### Local Flag on Parent Commands + +By default Cobra only parses local flags on the target command, any local flags on +parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will +parse local flags on each command before executing the target command. + +```go +command := cobra.Command{ + Use: "print [OPTIONS] [COMMANDS]", + TraverseChildren: true, +} +``` + +### Bind Flags with Config + +You can also bind your flags with [viper](https://github.com/spf13/viper): +```go +var author string + +func init() { + rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) +} +``` + +In this example the persistent flag `author` is bound with `viper`. +**Note**, that the variable `author` will not be set to the value from config, +when the `--author` flag is not provided by user. + +More in [viper documentation](https://github.com/spf13/viper#working-with-flags). + +### Required flags + +Flags are optional by default. If instead you wish your command to report an error +when a flag has not been set, mark it as required: +```go +rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") +rootCmd.MarkFlagRequired("region") +``` + +## Positional and Custom Arguments + +Validation of positional arguments can be specified using the `Args` field +of `Command`. + +The following validators are built in: + +- `NoArgs` - the command will report an error if there are any positional args. +- `ArbitraryArgs` - the command will accept any args. +- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the `ValidArgs` field of `Command`. +- `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args. +- `MaximumNArgs(int)` - the command will report an error if there are more than N positional args. +- `ExactArgs(int)` - the command will report an error if there are not exactly N positional args. +- `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args. + +An example of setting the custom validator: + +```go +var cmd = &cobra.Command{ + Short: "hello", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires at least one arg") + } + if myapp.IsValidColor(args[0]) { + return nil + } + return fmt.Errorf("invalid color specified: %s", args[0]) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Hello, World!") + }, +} +``` + +## Example + +In the example below, we have defined three commands. Two are at the top level +and one (cmdTimes) is a child of one of the top commands. In this case the root +is not executable meaning that a subcommand is required. This is accomplished +by not providing a 'Run' for the 'rootCmd'. + +We have only defined one flag for a single command. + +More documentation about flags is available at https://github.com/spf13/pflag + +```go +package main + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +func main() { + var echoTimes int + + var cmdPrint = &cobra.Command{ + Use: "print [string to print]", + Short: "Print anything to the screen", + Long: `print is for printing anything back to the screen. +For many years people have printed back to the screen.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Print: " + strings.Join(args, " ")) + }, + } + + var cmdEcho = &cobra.Command{ + Use: "echo [string to echo]", + Short: "Echo anything to the screen", + Long: `echo is for echoing anything back. +Echo works a lot like print, except it has a child command.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Print: " + strings.Join(args, " ")) + }, + } + + var cmdTimes = &cobra.Command{ + Use: "times [# times] [string to echo]", + Short: "Echo anything to the screen more times", + Long: `echo things multiple times back to the user by providing +a count and a string.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + for i := 0; i < echoTimes; i++ { + fmt.Println("Echo: " + strings.Join(args, " ")) + } + }, + } + + cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") + + var rootCmd = &cobra.Command{Use: "app"} + rootCmd.AddCommand(cmdPrint, cmdEcho) + cmdEcho.AddCommand(cmdTimes) + rootCmd.Execute() +} +``` + +For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/). + +## Help Command + +Cobra automatically adds a help command to your application when you have subcommands. +This will be called when a user runs 'app help'. Additionally, help will also +support all other commands as input. Say, for instance, you have a command called +'create' without any additional configuration; Cobra will work when 'app help +create' is called. Every command will automatically have the '--help' flag added. + +### Example + +The following output is automatically generated by Cobra. Nothing beyond the +command and flag definitions are needed. + + $ cobra help + + Cobra is a CLI library for Go that empowers applications. + This application is a tool to generate the needed files + to quickly create a Cobra application. + + Usage: + cobra [command] + + Available Commands: + add Add a command to a Cobra Application + help Help about any command + init Initialize a Cobra Application + + Flags: + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra + -l, --license string name of license for the project + --viper use Viper for configuration (default true) + + Use "cobra [command] --help" for more information about a command. + + +Help is just a command like any other. There is no special logic or behavior +around it. In fact, you can provide your own if you want. + +### Defining your own help + +You can provide your own Help command or your own template for the default command to use +with following functions: + +```go +cmd.SetHelpCommand(cmd *Command) +cmd.SetHelpFunc(f func(*Command, []string)) +cmd.SetHelpTemplate(s string) +``` + +The latter two will also apply to any children commands. + +## Usage Message + +When the user provides an invalid flag or invalid command, Cobra responds by +showing the user the 'usage'. + +### Example +You may recognize this from the help above. That's because the default help +embeds the usage as part of its output. + + $ cobra --invalid + Error: unknown flag: --invalid + Usage: + cobra [command] + + Available Commands: + add Add a command to a Cobra Application + help Help about any command + init Initialize a Cobra Application + + Flags: + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra + -l, --license string name of license for the project + --viper use Viper for configuration (default true) + + Use "cobra [command] --help" for more information about a command. + +### Defining your own usage +You can provide your own usage function or template for Cobra to use. +Like help, the function and template are overridable through public methods: + +```go +cmd.SetUsageFunc(f func(*Command) error) +cmd.SetUsageTemplate(s string) +``` + +## Version Flag + +Cobra adds a top-level '--version' flag if the Version field is set on the root command. +Running an application with the '--version' flag will print the version to stdout using +the version template. The template can be customized using the +`cmd.SetVersionTemplate(s string)` function. + +## PreRun and PostRun Hooks + +It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: + +- `PersistentPreRun` +- `PreRun` +- `Run` +- `PostRun` +- `PersistentPostRun` + +An example of two commands which use all of these features is below. When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`: + +```go +package main + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func main() { + + var rootCmd = &cobra.Command{ + Use: "root [sub]", + Short: "My root command", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) + }, + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) + }, + } + + var subCmd = &cobra.Command{ + Use: "sub [no options!]", + Short: "My subcommand", + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args) + }, + } + + rootCmd.AddCommand(subCmd) + + rootCmd.SetArgs([]string{""}) + rootCmd.Execute() + fmt.Println() + rootCmd.SetArgs([]string{"sub", "arg1", "arg2"}) + rootCmd.Execute() +} +``` + +Output: +``` +Inside rootCmd PersistentPreRun with args: [] +Inside rootCmd PreRun with args: [] +Inside rootCmd Run with args: [] +Inside rootCmd PostRun with args: [] +Inside rootCmd PersistentPostRun with args: [] + +Inside rootCmd PersistentPreRun with args: [arg1 arg2] +Inside subCmd PreRun with args: [arg1 arg2] +Inside subCmd Run with args: [arg1 arg2] +Inside subCmd PostRun with args: [arg1 arg2] +Inside subCmd PersistentPostRun with args: [arg1 arg2] +``` + +## Suggestions when "unknown command" happens + +Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example: + +``` +$ hugo srever +Error: unknown command "srever" for "hugo" + +Did you mean this? + server + +Run 'hugo --help' for usage. +``` + +Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion. + +If you need to disable suggestions or tweak the string distance in your command, use: + +```go +command.DisableSuggestions = true +``` + +or + +```go +command.SuggestionsMinimumDistance = 1 +``` + +You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example: + +``` +$ kubectl remove +Error: unknown command "remove" for "kubectl" + +Did you mean this? + delete + +Run 'kubectl help' for usage. +``` + +## Generating documentation for your command + +Cobra can generate documentation based on subcommands, flags, etc. in the following formats: + +- [Markdown](doc/md_docs.md) +- [ReStructured Text](doc/rest_docs.md) +- [Man Page](doc/man_docs.md) + +## Generating bash completions + +Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md). + +# Contributing + +1. Fork it +2. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`) +3. Create your feature branch (`git checkout -b my-new-feature`) +4. Make changes and add them (`git add .`) +5. Commit your changes (`git commit -m 'Add some feature'`) +6. Push to the branch (`git push origin my-new-feature`) +7. Create new pull request + +# License + +Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt) diff --git a/vendor/github.com/spf13/cobra/args.go b/vendor/github.com/spf13/cobra/args.go new file mode 100644 index 000000000..a5d8a9273 --- /dev/null +++ b/vendor/github.com/spf13/cobra/args.go @@ -0,0 +1,89 @@ +package cobra + +import ( + "fmt" +) + +type PositionalArgs func(cmd *Command, args []string) error + +// Legacy arg validation has the following behaviour: +// - root commands with no subcommands can take arbitrary arguments +// - root commands with subcommands will do subcommand validity checking +// - subcommands will always accept arbitrary arguments +func legacyArgs(cmd *Command, args []string) error { + // no subcommand, always take args + if !cmd.HasSubCommands() { + return nil + } + + // root command with subcommands, do subcommand checking. + if !cmd.HasParent() && len(args) > 0 { + return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0])) + } + return nil +} + +// NoArgs returns an error if any args are included. +func NoArgs(cmd *Command, args []string) error { + if len(args) > 0 { + return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath()) + } + return nil +} + +// OnlyValidArgs returns an error if any args are not in the list of ValidArgs. +func OnlyValidArgs(cmd *Command, args []string) error { + if len(cmd.ValidArgs) > 0 { + for _, v := range args { + if !stringInSlice(v, cmd.ValidArgs) { + return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0])) + } + } + } + return nil +} + +// ArbitraryArgs never returns an error. +func ArbitraryArgs(cmd *Command, args []string) error { + return nil +} + +// MinimumNArgs returns an error if there is not at least N args. +func MinimumNArgs(n int) PositionalArgs { + return func(cmd *Command, args []string) error { + if len(args) < n { + return fmt.Errorf("requires at least %d arg(s), only received %d", n, len(args)) + } + return nil + } +} + +// MaximumNArgs returns an error if there are more than N args. +func MaximumNArgs(n int) PositionalArgs { + return func(cmd *Command, args []string) error { + if len(args) > n { + return fmt.Errorf("accepts at most %d arg(s), received %d", n, len(args)) + } + return nil + } +} + +// ExactArgs returns an error if there are not exactly n args. +func ExactArgs(n int) PositionalArgs { + return func(cmd *Command, args []string) error { + if len(args) != n { + return fmt.Errorf("accepts %d arg(s), received %d", n, len(args)) + } + return nil + } +} + +// RangeArgs returns an error if the number of args is not within the expected range. +func RangeArgs(min int, max int) PositionalArgs { + return func(cmd *Command, args []string) error { + if len(args) < min || len(args) > max { + return fmt.Errorf("accepts between %d and %d arg(s), received %d", min, max, len(args)) + } + return nil + } +} diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go new file mode 100644 index 000000000..8fa8f486f --- /dev/null +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -0,0 +1,584 @@ +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" + "sort" + "strings" + + "github.com/spf13/pflag" +) + +// Annotations for Bash completion. +const ( + BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions" + BashCompCustom = "cobra_annotation_bash_completion_custom" + BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag" + BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" +) + +func writePreamble(buf *bytes.Buffer, name string) { + buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) + buf.WriteString(fmt.Sprintf(` +__%[1]s_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__%[1]s_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__%[1]s_index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__%[1]s_contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__%[1]s_handle_reply() +{ + __%[1]s_debug "${FUNCNAME[0]}" + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%%=*}" + __%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}" + COMPREPLY=() + if [[ ${index} -ge 0 ]]; then + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zsh completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + declare -F __custom_func >/dev/null && __custom_func + fi + + # available in bash-completion >= 2, not always present on macOS + if declare -F __ltrim_colon_completions >/dev/null; then + __ltrim_colon_completions "$cur" + fi + + # If there is only 1 completion and it is a flag with an = it will be completed + # but we don't want a space after the = + if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then + compopt -o nospace + fi +} + +# The arguments should be in the form "ext1|ext2|extn" +__%[1]s_handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__%[1]s_handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 +} + +__%[1]s_handle_flag() +{ + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}" + if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + # flaghash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + fi + + # skip the argument to a two word flag + if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__%[1]s_handle_noun() +{ + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__%[1]s_handle_command() +{ + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_%[1]s_root_command" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F "$next_command" >/dev/null && $next_command +} + +__%[1]s_handle_word() +{ + if [[ $c -ge $cword ]]; then + __%[1]s_handle_reply + return + fi + __%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __%[1]s_handle_flag + elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then + __%[1]s_handle_command + elif [[ $c -eq 0 ]]; then + __%[1]s_handle_command + elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then + # aliashash variable is an associative array which is only supported in bash > 3. + if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then + words[c]=${aliashash[${words[c]}]} + __%[1]s_handle_command + else + __%[1]s_handle_noun + fi + else + __%[1]s_handle_noun + fi + __%[1]s_handle_word +} + +`, name)) +} + +func writePostscript(buf *bytes.Buffer, name string) { + name = strings.Replace(name, ":", "__", -1) + buf.WriteString(fmt.Sprintf("__start_%s()\n", name)) + buf.WriteString(fmt.Sprintf(`{ + local cur prev words cword + declare -A flaghash 2>/dev/null || : + declare -A aliashash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __%[1]s_init_completion -n "=" || return + fi + + local c=0 + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("%[1]s") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __%[1]s_handle_word +} + +`, name)) + buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_%s %s +else + complete -o default -o nospace -F __start_%s %s +fi + +`, name, name, name, name)) + buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n") +} + +func writeCommands(buf *bytes.Buffer, cmd *Command) { + buf.WriteString(" commands=()\n") + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c == cmd.helpCommand { + continue + } + buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) + writeCmdAliases(buf, c) + } + buf.WriteString("\n") +} + +func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) { + for key, value := range annotations { + switch key { + case BashCompFilenameExt: + buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) + + var ext string + if len(value) > 0 { + ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|") + } else { + ext = "_filedir" + } + buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) + case BashCompCustom: + buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) + if len(value) > 0 { + handlers := strings.Join(value, "; ") + buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) + } else { + buf.WriteString(" flags_completion+=(:)\n") + } + case BashCompSubdirsInDir: + buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) + + var ext string + if len(value) == 1 { + ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0] + } else { + ext = "_filedir -d" + } + buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) + } + } +} + +func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { + name := flag.Shorthand + format := " " + if len(flag.NoOptDefVal) == 0 { + format += "two_word_" + } + format += "flags+=(\"-%s\")\n" + buf.WriteString(fmt.Sprintf(format, name)) + writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) +} + +func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { + name := flag.Name + format := " flags+=(\"--%s" + if len(flag.NoOptDefVal) == 0 { + format += "=" + } + format += "\")\n" + buf.WriteString(fmt.Sprintf(format, name)) + writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) +} + +func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { + name := flag.Name + format := " local_nonpersistent_flags+=(\"--%s" + if len(flag.NoOptDefVal) == 0 { + format += "=" + } + format += "\")\n" + buf.WriteString(fmt.Sprintf(format, name)) +} + +func writeFlags(buf *bytes.Buffer, cmd *Command) { + buf.WriteString(` flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + +`) + localNonPersistentFlags := cmd.LocalNonPersistentFlags() + cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + if nonCompletableFlag(flag) { + return + } + writeFlag(buf, flag, cmd) + if len(flag.Shorthand) > 0 { + writeShortFlag(buf, flag, cmd) + } + if localNonPersistentFlags.Lookup(flag.Name) != nil { + writeLocalNonPersistentFlag(buf, flag) + } + }) + cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { + if nonCompletableFlag(flag) { + return + } + writeFlag(buf, flag, cmd) + if len(flag.Shorthand) > 0 { + writeShortFlag(buf, flag, cmd) + } + }) + + buf.WriteString("\n") +} + +func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) { + buf.WriteString(" must_have_one_flag=()\n") + flags := cmd.NonInheritedFlags() + flags.VisitAll(func(flag *pflag.Flag) { + if nonCompletableFlag(flag) { + return + } + for key := range flag.Annotations { + switch key { + case BashCompOneRequiredFlag: + format := " must_have_one_flag+=(\"--%s" + if flag.Value.Type() != "bool" { + format += "=" + } + format += "\")\n" + buf.WriteString(fmt.Sprintf(format, flag.Name)) + + if len(flag.Shorthand) > 0 { + buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)) + } + } + } + }) +} + +func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { + buf.WriteString(" must_have_one_noun=()\n") + sort.Sort(sort.StringSlice(cmd.ValidArgs)) + for _, value := range cmd.ValidArgs { + buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) + } +} + +func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { + if len(cmd.Aliases) == 0 { + return + } + + sort.Sort(sort.StringSlice(cmd.Aliases)) + + buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) + for _, value := range cmd.Aliases { + buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value)) + buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) + } + buf.WriteString(` fi`) + buf.WriteString("\n") +} +func writeArgAliases(buf *bytes.Buffer, cmd *Command) { + buf.WriteString(" noun_aliases=()\n") + sort.Sort(sort.StringSlice(cmd.ArgAliases)) + for _, value := range cmd.ArgAliases { + buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value)) + } +} + +func gen(buf *bytes.Buffer, cmd *Command) { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c == cmd.helpCommand { + continue + } + gen(buf, c) + } + commandName := cmd.CommandPath() + commandName = strings.Replace(commandName, " ", "_", -1) + commandName = strings.Replace(commandName, ":", "__", -1) + + if cmd.Root() == cmd { + buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName)) + } else { + buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName)) + } + + buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) + buf.WriteString("\n") + buf.WriteString(" command_aliases=()\n") + buf.WriteString("\n") + + writeCommands(buf, cmd) + writeFlags(buf, cmd) + writeRequiredFlag(buf, cmd) + writeRequiredNouns(buf, cmd) + writeArgAliases(buf, cmd) + buf.WriteString("}\n\n") +} + +// GenBashCompletion generates bash completion file and writes to the passed writer. +func (c *Command) GenBashCompletion(w io.Writer) error { + buf := new(bytes.Buffer) + writePreamble(buf, c.Name()) + if len(c.BashCompletionFunction) > 0 { + buf.WriteString(c.BashCompletionFunction + "\n") + } + gen(buf, c) + writePostscript(buf, c.Name()) + + _, err := buf.WriteTo(w) + return err +} + +func nonCompletableFlag(flag *pflag.Flag) bool { + return flag.Hidden || len(flag.Deprecated) > 0 +} + +// GenBashCompletionFile generates bash completion file. +func (c *Command) GenBashCompletionFile(filename string) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenBashCompletion(outFile) +} + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. +func (c *Command) MarkFlagRequired(name string) error { + return MarkFlagRequired(c.Flags(), name) +} + +// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists, +// and causes your command to report an error if invoked without the flag. +func (c *Command) MarkPersistentFlagRequired(name string) error { + return MarkFlagRequired(c.PersistentFlags(), name) +} + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. +func MarkFlagRequired(flags *pflag.FlagSet, name string) error { + return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) +} + +// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func (c *Command) MarkFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.Flags(), name, extensions...) +} + +// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func (c *Command) MarkFlagCustom(name string, f string) error { + return MarkFlagCustom(c.Flags(), name, f) +} + +// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.PersistentFlags(), name, extensions...) +} + +// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { + return flags.SetAnnotation(name, BashCompFilenameExt, extensions) +} + +// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { + return flags.SetAnnotation(name, BashCompCustom, []string{f}) +} diff --git a/vendor/github.com/spf13/cobra/cobra.go b/vendor/github.com/spf13/cobra/cobra.go new file mode 100644 index 000000000..7010fd15b --- /dev/null +++ b/vendor/github.com/spf13/cobra/cobra.go @@ -0,0 +1,200 @@ +// Copyright © 2013 Steve Francia <spf@spf13.com>. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Commands similar to git, go tools and other modern CLI tools +// inspired by go, go-Commander, gh and subcommand + +package cobra + +import ( + "fmt" + "io" + "reflect" + "strconv" + "strings" + "text/template" + "unicode" +) + +var templateFuncs = template.FuncMap{ + "trim": strings.TrimSpace, + "trimRightSpace": trimRightSpace, + "trimTrailingWhitespaces": trimRightSpace, + "appendIfNotPresent": appendIfNotPresent, + "rpad": rpad, + "gt": Gt, + "eq": Eq, +} + +var initializers []func() + +// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing +// to automatically enable in CLI tools. +// Set this to true to enable it. +var EnablePrefixMatching = false + +// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default. +// To disable sorting, set it to false. +var EnableCommandSorting = true + +// MousetrapHelpText enables an information splash screen on Windows +// if the CLI is started from explorer.exe. +// To disable the mousetrap, just set this variable to blank string (""). +// Works only on Microsoft Windows. +var MousetrapHelpText string = `This is a command line tool. + +You need to open cmd.exe and run it from there. +` + +// AddTemplateFunc adds a template function that's available to Usage and Help +// template generation. +func AddTemplateFunc(name string, tmplFunc interface{}) { + templateFuncs[name] = tmplFunc +} + +// AddTemplateFuncs adds multiple template functions that are available to Usage and +// Help template generation. +func AddTemplateFuncs(tmplFuncs template.FuncMap) { + for k, v := range tmplFuncs { + templateFuncs[k] = v + } +} + +// OnInitialize sets the passed functions to be run when each command's +// Execute method is called. +func OnInitialize(y ...func()) { + initializers = append(initializers, y...) +} + +// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. + +// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans, +// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as +// ints and then compared. +func Gt(a interface{}, b interface{}) bool { + var left, right int64 + av := reflect.ValueOf(a) + + switch av.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + left = int64(av.Len()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + left = av.Int() + case reflect.String: + left, _ = strconv.ParseInt(av.String(), 10, 64) + } + + bv := reflect.ValueOf(b) + + switch bv.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + right = int64(bv.Len()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + right = bv.Int() + case reflect.String: + right, _ = strconv.ParseInt(bv.String(), 10, 64) + } + + return left > right +} + +// FIXME Eq is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. + +// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic. +func Eq(a interface{}, b interface{}) bool { + av := reflect.ValueOf(a) + bv := reflect.ValueOf(b) + + switch av.Kind() { + case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + panic("Eq called on unsupported type") + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return av.Int() == bv.Int() + case reflect.String: + return av.String() == bv.String() + } + return false +} + +func trimRightSpace(s string) string { + return strings.TrimRightFunc(s, unicode.IsSpace) +} + +// FIXME appendIfNotPresent is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra. + +// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s. +func appendIfNotPresent(s, stringToAppend string) string { + if strings.Contains(s, stringToAppend) { + return s + } + return s + " " + stringToAppend +} + +// rpad adds padding to the right of a string. +func rpad(s string, padding int) string { + template := fmt.Sprintf("%%-%ds", padding) + return fmt.Sprintf(template, s) +} + +// tmpl executes the given template text on data, writing the result to w. +func tmpl(w io.Writer, text string, data interface{}) error { + t := template.New("top") + t.Funcs(templateFuncs) + template.Must(t.Parse(text)) + return t.Execute(w, data) +} + +// ld compares two strings and returns the levenshtein distance between them. +func ld(s, t string, ignoreCase bool) int { + if ignoreCase { + s = strings.ToLower(s) + t = strings.ToLower(t) + } + d := make([][]int, len(s)+1) + for i := range d { + d[i] = make([]int, len(t)+1) + } + for i := range d { + d[i][0] = i + } + for j := range d[0] { + d[0][j] = j + } + for j := 1; j <= len(t); j++ { + for i := 1; i <= len(s); i++ { + if s[i-1] == t[j-1] { + d[i][j] = d[i-1][j-1] + } else { + min := d[i-1][j] + if d[i][j-1] < min { + min = d[i][j-1] + } + if d[i-1][j-1] < min { + min = d[i-1][j-1] + } + d[i][j] = min + 1 + } + } + + } + return d[len(s)][len(t)] +} + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go new file mode 100644 index 000000000..34d1bf367 --- /dev/null +++ b/vendor/github.com/spf13/cobra/command.go @@ -0,0 +1,1517 @@ +// Copyright © 2013 Steve Francia <spf@spf13.com>. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cobra is a commander providing a simple interface to create powerful modern CLI interfaces. +// In addition to providing an interface, Cobra simultaneously provides a controller to organize your application code. +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + flag "github.com/spf13/pflag" +) + +// FParseErrWhitelist configures Flag parse errors to be ignored +type FParseErrWhitelist flag.ParseErrorsWhitelist + +// Command is just that, a command for your application. +// E.g. 'go run ...' - 'run' is the command. Cobra requires +// you to define the usage and description as part of your command +// definition to ensure usability. +type Command struct { + // Use is the one-line usage message. + Use string + + // Aliases is an array of aliases that can be used instead of the first word in Use. + Aliases []string + + // SuggestFor is an array of command names for which this command will be suggested - + // similar to aliases but only suggests. + SuggestFor []string + + // Short is the short description shown in the 'help' output. + Short string + + // Long is the long message shown in the 'help <this-command>' output. + Long string + + // Example is examples of how to use the command. + Example string + + // ValidArgs is list of all valid non-flag arguments that are accepted in bash completions + ValidArgs []string + + // Expected arguments + Args PositionalArgs + + // ArgAliases is List of aliases for ValidArgs. + // These are not suggested to the user in the bash completion, + // but accepted if entered manually. + ArgAliases []string + + // BashCompletionFunction is custom functions used by the bash autocompletion generator. + BashCompletionFunction string + + // Deprecated defines, if this command is deprecated and should print this string when used. + Deprecated string + + // Hidden defines, if this command is hidden and should NOT show up in the list of available commands. + Hidden bool + + // Annotations are key/value pairs that can be used by applications to identify or + // group commands. + Annotations map[string]string + + // Version defines the version for this command. If this value is non-empty and the command does not + // define a "version" flag, a "version" boolean flag will be added to the command and, if specified, + // will print content of the "Version" variable. + Version string + + // The *Run functions are executed in the following order: + // * PersistentPreRun() + // * PreRun() + // * Run() + // * PostRun() + // * PersistentPostRun() + // All functions get the same args, the arguments after the command name. + // + // PersistentPreRun: children of this command will inherit and execute. + PersistentPreRun func(cmd *Command, args []string) + // PersistentPreRunE: PersistentPreRun but returns an error. + PersistentPreRunE func(cmd *Command, args []string) error + // PreRun: children of this command will not inherit. + PreRun func(cmd *Command, args []string) + // PreRunE: PreRun but returns an error. + PreRunE func(cmd *Command, args []string) error + // Run: Typically the actual work function. Most commands will only implement this. + Run func(cmd *Command, args []string) + // RunE: Run but returns an error. + RunE func(cmd *Command, args []string) error + // PostRun: run after the Run command. + PostRun func(cmd *Command, args []string) + // PostRunE: PostRun but returns an error. + PostRunE func(cmd *Command, args []string) error + // PersistentPostRun: children of this command will inherit and execute after PostRun. + PersistentPostRun func(cmd *Command, args []string) + // PersistentPostRunE: PersistentPostRun but returns an error. + PersistentPostRunE func(cmd *Command, args []string) error + + // SilenceErrors is an option to quiet errors down stream. + SilenceErrors bool + + // SilenceUsage is an option to silence usage when an error occurs. + SilenceUsage bool + + // DisableFlagParsing disables the flag parsing. + // If this is true all flags will be passed to the command as arguments. + DisableFlagParsing bool + + // DisableAutoGenTag defines, if gen tag ("Auto generated by spf13/cobra...") + // will be printed by generating docs for this command. + DisableAutoGenTag bool + + // DisableFlagsInUseLine will disable the addition of [flags] to the usage + // line of a command when printing help or generating docs + DisableFlagsInUseLine bool + + // DisableSuggestions disables the suggestions based on Levenshtein distance + // that go along with 'unknown command' messages. + DisableSuggestions bool + // SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions. + // Must be > 0. + SuggestionsMinimumDistance int + + // TraverseChildren parses flags on all parents before executing child command. + TraverseChildren bool + + //FParseErrWhitelist flag parse errors to be ignored + FParseErrWhitelist FParseErrWhitelist + + // commands is the list of commands supported by this program. + commands []*Command + // parent is a parent command for this command. + parent *Command + // Max lengths of commands' string lengths for use in padding. + commandsMaxUseLen int + commandsMaxCommandPathLen int + commandsMaxNameLen int + // commandsAreSorted defines, if command slice are sorted or not. + commandsAreSorted bool + // commandCalledAs is the name or alias value used to call this command. + commandCalledAs struct { + name string + called bool + } + + // args is actual args parsed from flags. + args []string + // flagErrorBuf contains all error messages from pflag. + flagErrorBuf *bytes.Buffer + // flags is full set of flags. + flags *flag.FlagSet + // pflags contains persistent flags. + pflags *flag.FlagSet + // lflags contains local flags. + lflags *flag.FlagSet + // iflags contains inherited flags. + iflags *flag.FlagSet + // parentsPflags is all persistent flags of cmd's parents. + parentsPflags *flag.FlagSet + // globNormFunc is the global normalization function + // that we can use on every pflag set and children commands + globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName + + // output is an output writer defined by user. + output io.Writer + // usageFunc is usage func defined by user. + usageFunc func(*Command) error + // usageTemplate is usage template defined by user. + usageTemplate string + // flagErrorFunc is func defined by user and it's called when the parsing of + // flags returns an error. + flagErrorFunc func(*Command, error) error + // helpTemplate is help template defined by user. + helpTemplate string + // helpFunc is help func defined by user. + helpFunc func(*Command, []string) + // helpCommand is command with usage 'help'. If it's not defined by user, + // cobra uses default help command. + helpCommand *Command + // versionTemplate is the version template defined by user. + versionTemplate string +} + +// SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden +// particularly useful when testing. +func (c *Command) SetArgs(a []string) { + c.args = a +} + +// SetOutput sets the destination for usage and error messages. +// If output is nil, os.Stderr is used. +func (c *Command) SetOutput(output io.Writer) { + c.output = output +} + +// SetUsageFunc sets usage function. Usage can be defined by application. +func (c *Command) SetUsageFunc(f func(*Command) error) { + c.usageFunc = f +} + +// SetUsageTemplate sets usage template. Can be defined by Application. +func (c *Command) SetUsageTemplate(s string) { + c.usageTemplate = s +} + +// SetFlagErrorFunc sets a function to generate an error when flag parsing +// fails. +func (c *Command) SetFlagErrorFunc(f func(*Command, error) error) { + c.flagErrorFunc = f +} + +// SetHelpFunc sets help function. Can be defined by Application. +func (c *Command) SetHelpFunc(f func(*Command, []string)) { + c.helpFunc = f +} + +// SetHelpCommand sets help command. +func (c *Command) SetHelpCommand(cmd *Command) { + c.helpCommand = cmd +} + +// SetHelpTemplate sets help template to be used. Application can use it to set custom template. +func (c *Command) SetHelpTemplate(s string) { + c.helpTemplate = s +} + +// SetVersionTemplate sets version template to be used. Application can use it to set custom template. +func (c *Command) SetVersionTemplate(s string) { + c.versionTemplate = s +} + +// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands. +// The user should not have a cyclic dependency on commands. +func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) { + c.Flags().SetNormalizeFunc(n) + c.PersistentFlags().SetNormalizeFunc(n) + c.globNormFunc = n + + for _, command := range c.commands { + command.SetGlobalNormalizationFunc(n) + } +} + +// OutOrStdout returns output to stdout. +func (c *Command) OutOrStdout() io.Writer { + return c.getOut(os.Stdout) +} + +// OutOrStderr returns output to stderr +func (c *Command) OutOrStderr() io.Writer { + return c.getOut(os.Stderr) +} + +func (c *Command) getOut(def io.Writer) io.Writer { + if c.output != nil { + return c.output + } + if c.HasParent() { + return c.parent.getOut(def) + } + return def +} + +// UsageFunc returns either the function set by SetUsageFunc for this command +// or a parent, or it returns a default usage function. +func (c *Command) UsageFunc() (f func(*Command) error) { + if c.usageFunc != nil { + return c.usageFunc + } + if c.HasParent() { + return c.Parent().UsageFunc() + } + return func(c *Command) error { + c.mergePersistentFlags() + err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c) + if err != nil { + c.Println(err) + } + return err + } +} + +// Usage puts out the usage for the command. +// Used when a user provides invalid input. +// Can be defined by user by overriding UsageFunc. +func (c *Command) Usage() error { + return c.UsageFunc()(c) +} + +// HelpFunc returns either the function set by SetHelpFunc for this command +// or a parent, or it returns a function with default help behavior. +func (c *Command) HelpFunc() func(*Command, []string) { + if c.helpFunc != nil { + return c.helpFunc + } + if c.HasParent() { + return c.Parent().HelpFunc() + } + return func(c *Command, a []string) { + c.mergePersistentFlags() + err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c) + if err != nil { + c.Println(err) + } + } +} + +// Help puts out the help for the command. +// Used when a user calls help [command]. +// Can be defined by user by overriding HelpFunc. +func (c *Command) Help() error { + c.HelpFunc()(c, []string{}) + return nil +} + +// UsageString return usage string. +func (c *Command) UsageString() string { + tmpOutput := c.output + bb := new(bytes.Buffer) + c.SetOutput(bb) + c.Usage() + c.output = tmpOutput + return bb.String() +} + +// FlagErrorFunc returns either the function set by SetFlagErrorFunc for this +// command or a parent, or it returns a function which returns the original +// error. +func (c *Command) FlagErrorFunc() (f func(*Command, error) error) { + if c.flagErrorFunc != nil { + return c.flagErrorFunc + } + + if c.HasParent() { + return c.parent.FlagErrorFunc() + } + return func(c *Command, err error) error { + return err + } +} + +var minUsagePadding = 25 + +// UsagePadding return padding for the usage. +func (c *Command) UsagePadding() int { + if c.parent == nil || minUsagePadding > c.parent.commandsMaxUseLen { + return minUsagePadding + } + return c.parent.commandsMaxUseLen +} + +var minCommandPathPadding = 11 + +// CommandPathPadding return padding for the command path. +func (c *Command) CommandPathPadding() int { + if c.parent == nil || minCommandPathPadding > c.parent.commandsMaxCommandPathLen { + return minCommandPathPadding + } + return c.parent.commandsMaxCommandPathLen +} + +var minNamePadding = 11 + +// NamePadding returns padding for the name. +func (c *Command) NamePadding() int { + if c.parent == nil || minNamePadding > c.parent.commandsMaxNameLen { + return minNamePadding + } + return c.parent.commandsMaxNameLen +} + +// UsageTemplate returns usage template for the command. +func (c *Command) UsageTemplate() string { + if c.usageTemplate != "" { + return c.usageTemplate + } + + if c.HasParent() { + return c.parent.UsageTemplate() + } + return `Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: +{{.Example}}{{end}}{{if .HasAvailableSubCommands}} + +Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +Global Flags: +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` +} + +// HelpTemplate return help template for the command. +func (c *Command) HelpTemplate() string { + if c.helpTemplate != "" { + return c.helpTemplate + } + + if c.HasParent() { + return c.parent.HelpTemplate() + } + return `{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}} + +{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` +} + +// VersionTemplate return version template for the command. +func (c *Command) VersionTemplate() string { + if c.versionTemplate != "" { + return c.versionTemplate + } + + if c.HasParent() { + return c.parent.VersionTemplate() + } + return `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}} +` +} + +func hasNoOptDefVal(name string, fs *flag.FlagSet) bool { + flag := fs.Lookup(name) + if flag == nil { + return false + } + return flag.NoOptDefVal != "" +} + +func shortHasNoOptDefVal(name string, fs *flag.FlagSet) bool { + if len(name) == 0 { + return false + } + + flag := fs.ShorthandLookup(name[:1]) + if flag == nil { + return false + } + return flag.NoOptDefVal != "" +} + +func stripFlags(args []string, c *Command) []string { + if len(args) == 0 { + return args + } + c.mergePersistentFlags() + + commands := []string{} + flags := c.Flags() + +Loop: + for len(args) > 0 { + s := args[0] + args = args[1:] + switch { + case s == "--": + // "--" terminates the flags + break Loop + case strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags): + // If '--flag arg' then + // delete arg from args. + fallthrough // (do the same as below) + case strings.HasPrefix(s, "-") && !strings.Contains(s, "=") && len(s) == 2 && !shortHasNoOptDefVal(s[1:], flags): + // If '-f arg' then + // delete 'arg' from args or break the loop if len(args) <= 1. + if len(args) <= 1 { + break Loop + } else { + args = args[1:] + continue + } + case s != "" && !strings.HasPrefix(s, "-"): + commands = append(commands, s) + } + } + + return commands +} + +// argsMinusFirstX removes only the first x from args. Otherwise, commands that look like +// openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]). +func argsMinusFirstX(args []string, x string) []string { + for i, y := range args { + if x == y { + ret := []string{} + ret = append(ret, args[:i]...) + ret = append(ret, args[i+1:]...) + return ret + } + } + return args +} + +func isFlagArg(arg string) bool { + return ((len(arg) >= 3 && arg[1] == '-') || + (len(arg) >= 2 && arg[0] == '-' && arg[1] != '-')) +} + +// Find the target command given the args and command tree +// Meant to be run on the highest node. Only searches down. +func (c *Command) Find(args []string) (*Command, []string, error) { + var innerfind func(*Command, []string) (*Command, []string) + + innerfind = func(c *Command, innerArgs []string) (*Command, []string) { + argsWOflags := stripFlags(innerArgs, c) + if len(argsWOflags) == 0 { + return c, innerArgs + } + nextSubCmd := argsWOflags[0] + + cmd := c.findNext(nextSubCmd) + if cmd != nil { + return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd)) + } + return c, innerArgs + } + + commandFound, a := innerfind(c, args) + if commandFound.Args == nil { + return commandFound, a, legacyArgs(commandFound, stripFlags(a, commandFound)) + } + return commandFound, a, nil +} + +func (c *Command) findSuggestions(arg string) string { + if c.DisableSuggestions { + return "" + } + if c.SuggestionsMinimumDistance <= 0 { + c.SuggestionsMinimumDistance = 2 + } + suggestionsString := "" + if suggestions := c.SuggestionsFor(arg); len(suggestions) > 0 { + suggestionsString += "\n\nDid you mean this?\n" + for _, s := range suggestions { + suggestionsString += fmt.Sprintf("\t%v\n", s) + } + } + return suggestionsString +} + +func (c *Command) findNext(next string) *Command { + matches := make([]*Command, 0) + for _, cmd := range c.commands { + if cmd.Name() == next || cmd.HasAlias(next) { + cmd.commandCalledAs.name = next + return cmd + } + if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) { + matches = append(matches, cmd) + } + } + + if len(matches) == 1 { + return matches[0] + } + + return nil +} + +// Traverse the command tree to find the command, and parse args for +// each parent. +func (c *Command) Traverse(args []string) (*Command, []string, error) { + flags := []string{} + inFlag := false + + for i, arg := range args { + switch { + // A long flag with a space separated value + case strings.HasPrefix(arg, "--") && !strings.Contains(arg, "="): + // TODO: this isn't quite right, we should really check ahead for 'true' or 'false' + inFlag = !hasNoOptDefVal(arg[2:], c.Flags()) + flags = append(flags, arg) + continue + // A short flag with a space separated value + case strings.HasPrefix(arg, "-") && !strings.Contains(arg, "=") && len(arg) == 2 && !shortHasNoOptDefVal(arg[1:], c.Flags()): + inFlag = true + flags = append(flags, arg) + continue + // The value for a flag + case inFlag: + inFlag = false + flags = append(flags, arg) + continue + // A flag without a value, or with an `=` separated value + case isFlagArg(arg): + flags = append(flags, arg) + continue + } + + cmd := c.findNext(arg) + if cmd == nil { + return c, args, nil + } + + if err := c.ParseFlags(flags); err != nil { + return nil, args, err + } + return cmd.Traverse(args[i+1:]) + } + return c, args, nil +} + +// SuggestionsFor provides suggestions for the typedName. +func (c *Command) SuggestionsFor(typedName string) []string { + suggestions := []string{} + for _, cmd := range c.commands { + if cmd.IsAvailableCommand() { + levenshteinDistance := ld(typedName, cmd.Name(), true) + suggestByLevenshtein := levenshteinDistance <= c.SuggestionsMinimumDistance + suggestByPrefix := strings.HasPrefix(strings.ToLower(cmd.Name()), strings.ToLower(typedName)) + if suggestByLevenshtein || suggestByPrefix { + suggestions = append(suggestions, cmd.Name()) + } + for _, explicitSuggestion := range cmd.SuggestFor { + if strings.EqualFold(typedName, explicitSuggestion) { + suggestions = append(suggestions, cmd.Name()) + } + } + } + } + return suggestions +} + +// VisitParents visits all parents of the command and invokes fn on each parent. +func (c *Command) VisitParents(fn func(*Command)) { + if c.HasParent() { + fn(c.Parent()) + c.Parent().VisitParents(fn) + } +} + +// Root finds root command. +func (c *Command) Root() *Command { + if c.HasParent() { + return c.Parent().Root() + } + return c +} + +// ArgsLenAtDash will return the length of c.Flags().Args at the moment +// when a -- was found during args parsing. +func (c *Command) ArgsLenAtDash() int { + return c.Flags().ArgsLenAtDash() +} + +func (c *Command) execute(a []string) (err error) { + if c == nil { + return fmt.Errorf("Called Execute() on a nil Command") + } + + if len(c.Deprecated) > 0 { + c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated) + } + + // initialize help and version flag at the last point possible to allow for user + // overriding + c.InitDefaultHelpFlag() + c.InitDefaultVersionFlag() + + err = c.ParseFlags(a) + if err != nil { + return c.FlagErrorFunc()(c, err) + } + + // If help is called, regardless of other flags, return we want help. + // Also say we need help if the command isn't runnable. + helpVal, err := c.Flags().GetBool("help") + if err != nil { + // should be impossible to get here as we always declare a help + // flag in InitDefaultHelpFlag() + c.Println("\"help\" flag declared as non-bool. Please correct your code") + return err + } + + if helpVal { + return flag.ErrHelp + } + + // for back-compat, only add version flag behavior if version is defined + if c.Version != "" { + versionVal, err := c.Flags().GetBool("version") + if err != nil { + c.Println("\"version\" flag declared as non-bool. Please correct your code") + return err + } + if versionVal { + err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c) + if err != nil { + c.Println(err) + } + return err + } + } + + if !c.Runnable() { + return flag.ErrHelp + } + + c.preRun() + + argWoFlags := c.Flags().Args() + if c.DisableFlagParsing { + argWoFlags = a + } + + if err := c.ValidateArgs(argWoFlags); err != nil { + return err + } + + for p := c; p != nil; p = p.Parent() { + if p.PersistentPreRunE != nil { + if err := p.PersistentPreRunE(c, argWoFlags); err != nil { + return err + } + break + } else if p.PersistentPreRun != nil { + p.PersistentPreRun(c, argWoFlags) + break + } + } + if c.PreRunE != nil { + if err := c.PreRunE(c, argWoFlags); err != nil { + return err + } + } else if c.PreRun != nil { + c.PreRun(c, argWoFlags) + } + + if err := c.validateRequiredFlags(); err != nil { + return err + } + if c.RunE != nil { + if err := c.RunE(c, argWoFlags); err != nil { + return err + } + } else { + c.Run(c, argWoFlags) + } + if c.PostRunE != nil { + if err := c.PostRunE(c, argWoFlags); err != nil { + return err + } + } else if c.PostRun != nil { + c.PostRun(c, argWoFlags) + } + for p := c; p != nil; p = p.Parent() { + if p.PersistentPostRunE != nil { + if err := p.PersistentPostRunE(c, argWoFlags); err != nil { + return err + } + break + } else if p.PersistentPostRun != nil { + p.PersistentPostRun(c, argWoFlags) + break + } + } + + return nil +} + +func (c *Command) preRun() { + for _, x := range initializers { + x() + } +} + +// Execute uses the args (os.Args[1:] by default) +// and run through the command tree finding appropriate matches +// for commands and then corresponding flags. +func (c *Command) Execute() error { + _, err := c.ExecuteC() + return err +} + +// ExecuteC executes the command. +func (c *Command) ExecuteC() (cmd *Command, err error) { + // Regardless of what command execute is called on, run on Root only + if c.HasParent() { + return c.Root().ExecuteC() + } + + // windows hook + if preExecHookFn != nil { + preExecHookFn(c) + } + + // initialize help as the last point possible to allow for user + // overriding + c.InitDefaultHelpCmd() + + var args []string + + // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155 + if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" { + args = os.Args[1:] + } else { + args = c.args + } + + var flags []string + if c.TraverseChildren { + cmd, flags, err = c.Traverse(args) + } else { + cmd, flags, err = c.Find(args) + } + if err != nil { + // If found parse to a subcommand and then failed, talk about the subcommand + if cmd != nil { + c = cmd + } + if !c.SilenceErrors { + c.Println("Error:", err.Error()) + c.Printf("Run '%v --help' for usage.\n", c.CommandPath()) + } + return c, err + } + + cmd.commandCalledAs.called = true + if cmd.commandCalledAs.name == "" { + cmd.commandCalledAs.name = cmd.Name() + } + + err = cmd.execute(flags) + if err != nil { + // Always show help if requested, even if SilenceErrors is in + // effect + if err == flag.ErrHelp { + cmd.HelpFunc()(cmd, args) + return cmd, nil + } + + // If root command has SilentErrors flagged, + // all subcommands should respect it + if !cmd.SilenceErrors && !c.SilenceErrors { + c.Println("Error:", err.Error()) + } + + // If root command has SilentUsage flagged, + // all subcommands should respect it + if !cmd.SilenceUsage && !c.SilenceUsage { + c.Println(cmd.UsageString()) + } + } + return cmd, err +} + +func (c *Command) ValidateArgs(args []string) error { + if c.Args == nil { + return nil + } + return c.Args(c, args) +} + +func (c *Command) validateRequiredFlags() error { + flags := c.Flags() + missingFlagNames := []string{} + flags.VisitAll(func(pflag *flag.Flag) { + requiredAnnotation, found := pflag.Annotations[BashCompOneRequiredFlag] + if !found { + return + } + if (requiredAnnotation[0] == "true") && !pflag.Changed { + missingFlagNames = append(missingFlagNames, pflag.Name) + } + }) + + if len(missingFlagNames) > 0 { + return fmt.Errorf(`required flag(s) "%s" not set`, strings.Join(missingFlagNames, `", "`)) + } + return nil +} + +// InitDefaultHelpFlag adds default help flag to c. +// It is called automatically by executing the c or by calling help and usage. +// If c already has help flag, it will do nothing. +func (c *Command) InitDefaultHelpFlag() { + c.mergePersistentFlags() + if c.Flags().Lookup("help") == nil { + usage := "help for " + if c.Name() == "" { + usage += "this command" + } else { + usage += c.Name() + } + c.Flags().BoolP("help", "h", false, usage) + } +} + +// InitDefaultVersionFlag adds default version flag to c. +// It is called automatically by executing the c. +// If c already has a version flag, it will do nothing. +// If c.Version is empty, it will do nothing. +func (c *Command) InitDefaultVersionFlag() { + if c.Version == "" { + return + } + + c.mergePersistentFlags() + if c.Flags().Lookup("version") == nil { + usage := "version for " + if c.Name() == "" { + usage += "this command" + } else { + usage += c.Name() + } + c.Flags().Bool("version", false, usage) + } +} + +// InitDefaultHelpCmd adds default help command to c. +// It is called automatically by executing the c or by calling help and usage. +// If c already has help command or c has no subcommands, it will do nothing. +func (c *Command) InitDefaultHelpCmd() { + if !c.HasSubCommands() { + return + } + + if c.helpCommand == nil { + c.helpCommand = &Command{ + Use: "help [command]", + Short: "Help about any command", + Long: `Help provides help for any command in the application. +Simply type ` + c.Name() + ` help [path to command] for full details.`, + + Run: func(c *Command, args []string) { + cmd, _, e := c.Root().Find(args) + if cmd == nil || e != nil { + c.Printf("Unknown help topic %#q\n", args) + c.Root().Usage() + } else { + cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown + cmd.Help() + } + }, + } + } + c.RemoveCommand(c.helpCommand) + c.AddCommand(c.helpCommand) +} + +// ResetCommands delete parent, subcommand and help command from c. +func (c *Command) ResetCommands() { + c.parent = nil + c.commands = nil + c.helpCommand = nil + c.parentsPflags = nil +} + +// Sorts commands by their names. +type commandSorterByName []*Command + +func (c commandSorterByName) Len() int { return len(c) } +func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c commandSorterByName) Less(i, j int) bool { return c[i].Name() < c[j].Name() } + +// Commands returns a sorted slice of child commands. +func (c *Command) Commands() []*Command { + // do not sort commands if it already sorted or sorting was disabled + if EnableCommandSorting && !c.commandsAreSorted { + sort.Sort(commandSorterByName(c.commands)) + c.commandsAreSorted = true + } + return c.commands +} + +// AddCommand adds one or more commands to this parent command. +func (c *Command) AddCommand(cmds ...*Command) { + for i, x := range cmds { + if cmds[i] == c { + panic("Command can't be a child of itself") + } + cmds[i].parent = c + // update max lengths + usageLen := len(x.Use) + if usageLen > c.commandsMaxUseLen { + c.commandsMaxUseLen = usageLen + } + commandPathLen := len(x.CommandPath()) + if commandPathLen > c.commandsMaxCommandPathLen { + c.commandsMaxCommandPathLen = commandPathLen + } + nameLen := len(x.Name()) + if nameLen > c.commandsMaxNameLen { + c.commandsMaxNameLen = nameLen + } + // If global normalization function exists, update all children + if c.globNormFunc != nil { + x.SetGlobalNormalizationFunc(c.globNormFunc) + } + c.commands = append(c.commands, x) + c.commandsAreSorted = false + } +} + +// RemoveCommand removes one or more commands from a parent command. +func (c *Command) RemoveCommand(cmds ...*Command) { + commands := []*Command{} +main: + for _, command := range c.commands { + for _, cmd := range cmds { + if command == cmd { + command.parent = nil + continue main + } + } + commands = append(commands, command) + } + c.commands = commands + // recompute all lengths + c.commandsMaxUseLen = 0 + c.commandsMaxCommandPathLen = 0 + c.commandsMaxNameLen = 0 + for _, command := range c.commands { + usageLen := len(command.Use) + if usageLen > c.commandsMaxUseLen { + c.commandsMaxUseLen = usageLen + } + commandPathLen := len(command.CommandPath()) + if commandPathLen > c.commandsMaxCommandPathLen { + c.commandsMaxCommandPathLen = commandPathLen + } + nameLen := len(command.Name()) + if nameLen > c.commandsMaxNameLen { + c.commandsMaxNameLen = nameLen + } + } +} + +// Print is a convenience method to Print to the defined output, fallback to Stderr if not set. +func (c *Command) Print(i ...interface{}) { + fmt.Fprint(c.OutOrStderr(), i...) +} + +// Println is a convenience method to Println to the defined output, fallback to Stderr if not set. +func (c *Command) Println(i ...interface{}) { + c.Print(fmt.Sprintln(i...)) +} + +// Printf is a convenience method to Printf to the defined output, fallback to Stderr if not set. +func (c *Command) Printf(format string, i ...interface{}) { + c.Print(fmt.Sprintf(format, i...)) +} + +// CommandPath returns the full path to this command. +func (c *Command) CommandPath() string { + if c.HasParent() { + return c.Parent().CommandPath() + " " + c.Name() + } + return c.Name() +} + +// UseLine puts out the full usage for a given command (including parents). +func (c *Command) UseLine() string { + var useline string + if c.HasParent() { + useline = c.parent.CommandPath() + " " + c.Use + } else { + useline = c.Use + } + if c.DisableFlagsInUseLine { + return useline + } + if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") { + useline += " [flags]" + } + return useline +} + +// DebugFlags used to determine which flags have been assigned to which commands +// and which persist. +func (c *Command) DebugFlags() { + c.Println("DebugFlags called on", c.Name()) + var debugflags func(*Command) + + debugflags = func(x *Command) { + if x.HasFlags() || x.HasPersistentFlags() { + c.Println(x.Name()) + } + if x.HasFlags() { + x.flags.VisitAll(func(f *flag.Flag) { + if x.HasPersistentFlags() && x.persistentFlag(f.Name) != nil { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [LP]") + } else { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [L]") + } + }) + } + if x.HasPersistentFlags() { + x.pflags.VisitAll(func(f *flag.Flag) { + if x.HasFlags() { + if x.flags.Lookup(f.Name) == nil { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [P]") + } + } else { + c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [P]") + } + }) + } + c.Println(x.flagErrorBuf) + if x.HasSubCommands() { + for _, y := range x.commands { + debugflags(y) + } + } + } + + debugflags(c) +} + +// Name returns the command's name: the first word in the use line. +func (c *Command) Name() string { + name := c.Use + i := strings.Index(name, " ") + if i >= 0 { + name = name[:i] + } + return name +} + +// HasAlias determines if a given string is an alias of the command. +func (c *Command) HasAlias(s string) bool { + for _, a := range c.Aliases { + if a == s { + return true + } + } + return false +} + +// CalledAs returns the command name or alias that was used to invoke +// this command or an empty string if the command has not been called. +func (c *Command) CalledAs() string { + if c.commandCalledAs.called { + return c.commandCalledAs.name + } + return "" +} + +// hasNameOrAliasPrefix returns true if the Name or any of aliases start +// with prefix +func (c *Command) hasNameOrAliasPrefix(prefix string) bool { + if strings.HasPrefix(c.Name(), prefix) { + c.commandCalledAs.name = c.Name() + return true + } + for _, alias := range c.Aliases { + if strings.HasPrefix(alias, prefix) { + c.commandCalledAs.name = alias + return true + } + } + return false +} + +// NameAndAliases returns a list of the command name and all aliases +func (c *Command) NameAndAliases() string { + return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ") +} + +// HasExample determines if the command has example. +func (c *Command) HasExample() bool { + return len(c.Example) > 0 +} + +// Runnable determines if the command is itself runnable. +func (c *Command) Runnable() bool { + return c.Run != nil || c.RunE != nil +} + +// HasSubCommands determines if the command has children commands. +func (c *Command) HasSubCommands() bool { + return len(c.commands) > 0 +} + +// IsAvailableCommand determines if a command is available as a non-help command +// (this includes all non deprecated/hidden commands). +func (c *Command) IsAvailableCommand() bool { + if len(c.Deprecated) != 0 || c.Hidden { + return false + } + + if c.HasParent() && c.Parent().helpCommand == c { + return false + } + + if c.Runnable() || c.HasAvailableSubCommands() { + return true + } + + return false +} + +// IsAdditionalHelpTopicCommand determines if a command is an additional +// help topic command; additional help topic command is determined by the +// fact that it is NOT runnable/hidden/deprecated, and has no sub commands that +// are runnable/hidden/deprecated. +// Concrete example: https://github.com/spf13/cobra/issues/393#issuecomment-282741924. +func (c *Command) IsAdditionalHelpTopicCommand() bool { + // if a command is runnable, deprecated, or hidden it is not a 'help' command + if c.Runnable() || len(c.Deprecated) != 0 || c.Hidden { + return false + } + + // if any non-help sub commands are found, the command is not a 'help' command + for _, sub := range c.commands { + if !sub.IsAdditionalHelpTopicCommand() { + return false + } + } + + // the command either has no sub commands, or no non-help sub commands + return true +} + +// HasHelpSubCommands determines if a command has any available 'help' sub commands +// that need to be shown in the usage/help default template under 'additional help +// topics'. +func (c *Command) HasHelpSubCommands() bool { + // return true on the first found available 'help' sub command + for _, sub := range c.commands { + if sub.IsAdditionalHelpTopicCommand() { + return true + } + } + + // the command either has no sub commands, or no available 'help' sub commands + return false +} + +// HasAvailableSubCommands determines if a command has available sub commands that +// need to be shown in the usage/help default template under 'available commands'. +func (c *Command) HasAvailableSubCommands() bool { + // return true on the first found available (non deprecated/help/hidden) + // sub command + for _, sub := range c.commands { + if sub.IsAvailableCommand() { + return true + } + } + + // the command either has no sub commands, or no available (non deprecated/help/hidden) + // sub commands + return false +} + +// HasParent determines if the command is a child command. +func (c *Command) HasParent() bool { + return c.parent != nil +} + +// GlobalNormalizationFunc returns the global normalization function or nil if it doesn't exist. +func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName { + return c.globNormFunc +} + +// Flags returns the complete FlagSet that applies +// to this command (local and persistent declared here and by all parents). +func (c *Command) Flags() *flag.FlagSet { + if c.flags == nil { + c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + c.flags.SetOutput(c.flagErrorBuf) + } + + return c.flags +} + +// LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands. +func (c *Command) LocalNonPersistentFlags() *flag.FlagSet { + persistentFlags := c.PersistentFlags() + + out := flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.LocalFlags().VisitAll(func(f *flag.Flag) { + if persistentFlags.Lookup(f.Name) == nil { + out.AddFlag(f) + } + }) + return out +} + +// LocalFlags returns the local FlagSet specifically set in the current command. +func (c *Command) LocalFlags() *flag.FlagSet { + c.mergePersistentFlags() + + if c.lflags == nil { + c.lflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + c.lflags.SetOutput(c.flagErrorBuf) + } + c.lflags.SortFlags = c.Flags().SortFlags + if c.globNormFunc != nil { + c.lflags.SetNormalizeFunc(c.globNormFunc) + } + + addToLocal := func(f *flag.Flag) { + if c.lflags.Lookup(f.Name) == nil && c.parentsPflags.Lookup(f.Name) == nil { + c.lflags.AddFlag(f) + } + } + c.Flags().VisitAll(addToLocal) + c.PersistentFlags().VisitAll(addToLocal) + return c.lflags +} + +// InheritedFlags returns all flags which were inherited from parents commands. +func (c *Command) InheritedFlags() *flag.FlagSet { + c.mergePersistentFlags() + + if c.iflags == nil { + c.iflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + c.iflags.SetOutput(c.flagErrorBuf) + } + + local := c.LocalFlags() + if c.globNormFunc != nil { + c.iflags.SetNormalizeFunc(c.globNormFunc) + } + + c.parentsPflags.VisitAll(func(f *flag.Flag) { + if c.iflags.Lookup(f.Name) == nil && local.Lookup(f.Name) == nil { + c.iflags.AddFlag(f) + } + }) + return c.iflags +} + +// NonInheritedFlags returns all flags which were not inherited from parent commands. +func (c *Command) NonInheritedFlags() *flag.FlagSet { + return c.LocalFlags() +} + +// PersistentFlags returns the persistent FlagSet specifically set in the current command. +func (c *Command) PersistentFlags() *flag.FlagSet { + if c.pflags == nil { + c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + c.pflags.SetOutput(c.flagErrorBuf) + } + return c.pflags +} + +// ResetFlags deletes all flags from command. +func (c *Command) ResetFlags() { + c.flagErrorBuf = new(bytes.Buffer) + c.flagErrorBuf.Reset() + c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.flags.SetOutput(c.flagErrorBuf) + c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.pflags.SetOutput(c.flagErrorBuf) + + c.lflags = nil + c.iflags = nil + c.parentsPflags = nil +} + +// HasFlags checks if the command contains any flags (local plus persistent from the entire structure). +func (c *Command) HasFlags() bool { + return c.Flags().HasFlags() +} + +// HasPersistentFlags checks if the command contains persistent flags. +func (c *Command) HasPersistentFlags() bool { + return c.PersistentFlags().HasFlags() +} + +// HasLocalFlags checks if the command has flags specifically declared locally. +func (c *Command) HasLocalFlags() bool { + return c.LocalFlags().HasFlags() +} + +// HasInheritedFlags checks if the command has flags inherited from its parent command. +func (c *Command) HasInheritedFlags() bool { + return c.InheritedFlags().HasFlags() +} + +// HasAvailableFlags checks if the command contains any flags (local plus persistent from the entire +// structure) which are not hidden or deprecated. +func (c *Command) HasAvailableFlags() bool { + return c.Flags().HasAvailableFlags() +} + +// HasAvailablePersistentFlags checks if the command contains persistent flags which are not hidden or deprecated. +func (c *Command) HasAvailablePersistentFlags() bool { + return c.PersistentFlags().HasAvailableFlags() +} + +// HasAvailableLocalFlags checks if the command has flags specifically declared locally which are not hidden +// or deprecated. +func (c *Command) HasAvailableLocalFlags() bool { + return c.LocalFlags().HasAvailableFlags() +} + +// HasAvailableInheritedFlags checks if the command has flags inherited from its parent command which are +// not hidden or deprecated. +func (c *Command) HasAvailableInheritedFlags() bool { + return c.InheritedFlags().HasAvailableFlags() +} + +// Flag climbs up the command tree looking for matching flag. +func (c *Command) Flag(name string) (flag *flag.Flag) { + flag = c.Flags().Lookup(name) + + if flag == nil { + flag = c.persistentFlag(name) + } + + return +} + +// Recursively find matching persistent flag. +func (c *Command) persistentFlag(name string) (flag *flag.Flag) { + if c.HasPersistentFlags() { + flag = c.PersistentFlags().Lookup(name) + } + + if flag == nil { + c.updateParentsPflags() + flag = c.parentsPflags.Lookup(name) + } + return +} + +// ParseFlags parses persistent flag tree and local flags. +func (c *Command) ParseFlags(args []string) error { + if c.DisableFlagParsing { + return nil + } + + if c.flagErrorBuf == nil { + c.flagErrorBuf = new(bytes.Buffer) + } + beforeErrorBufLen := c.flagErrorBuf.Len() + c.mergePersistentFlags() + + //do it here after merging all flags and just before parse + c.Flags().ParseErrorsWhitelist = flag.ParseErrorsWhitelist(c.FParseErrWhitelist) + + err := c.Flags().Parse(args) + // Print warnings if they occurred (e.g. deprecated flag messages). + if c.flagErrorBuf.Len()-beforeErrorBufLen > 0 && err == nil { + c.Print(c.flagErrorBuf.String()) + } + + return err +} + +// Parent returns a commands parent command. +func (c *Command) Parent() *Command { + return c.parent +} + +// mergePersistentFlags merges c.PersistentFlags() to c.Flags() +// and adds missing persistent flags of all parents. +func (c *Command) mergePersistentFlags() { + c.updateParentsPflags() + c.Flags().AddFlagSet(c.PersistentFlags()) + c.Flags().AddFlagSet(c.parentsPflags) +} + +// updateParentsPflags updates c.parentsPflags by adding +// new persistent flags of all parents. +// If c.parentsPflags == nil, it makes new. +func (c *Command) updateParentsPflags() { + if c.parentsPflags == nil { + c.parentsPflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError) + c.parentsPflags.SetOutput(c.flagErrorBuf) + c.parentsPflags.SortFlags = false + } + + if c.globNormFunc != nil { + c.parentsPflags.SetNormalizeFunc(c.globNormFunc) + } + + c.Root().PersistentFlags().AddFlagSet(flag.CommandLine) + + c.VisitParents(func(parent *Command) { + c.parentsPflags.AddFlagSet(parent.PersistentFlags()) + }) +} diff --git a/vendor/github.com/spf13/cobra/command_notwin.go b/vendor/github.com/spf13/cobra/command_notwin.go new file mode 100644 index 000000000..6159c1cc1 --- /dev/null +++ b/vendor/github.com/spf13/cobra/command_notwin.go @@ -0,0 +1,5 @@ +// +build !windows + +package cobra + +var preExecHookFn func(*Command) diff --git a/vendor/github.com/spf13/cobra/command_win.go b/vendor/github.com/spf13/cobra/command_win.go new file mode 100644 index 000000000..edec728e4 --- /dev/null +++ b/vendor/github.com/spf13/cobra/command_win.go @@ -0,0 +1,20 @@ +// +build windows + +package cobra + +import ( + "os" + "time" + + "github.com/inconshreveable/mousetrap" +) + +var preExecHookFn = preExecHook + +func preExecHook(c *Command) { + if MousetrapHelpText != "" && mousetrap.StartedByExplorer() { + c.Print(MousetrapHelpText) + time.Sleep(5 * time.Second) + os.Exit(1) + } +} diff --git a/vendor/github.com/spf13/cobra/zsh_completions.go b/vendor/github.com/spf13/cobra/zsh_completions.go new file mode 100644 index 000000000..889c22e27 --- /dev/null +++ b/vendor/github.com/spf13/cobra/zsh_completions.go @@ -0,0 +1,126 @@ +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" +) + +// GenZshCompletionFile generates zsh completion file. +func (c *Command) GenZshCompletionFile(filename string) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenZshCompletion(outFile) +} + +// GenZshCompletion generates a zsh completion file and writes to the passed writer. +func (c *Command) GenZshCompletion(w io.Writer) error { + buf := new(bytes.Buffer) + + writeHeader(buf, c) + maxDepth := maxDepth(c) + writeLevelMapping(buf, maxDepth) + writeLevelCases(buf, maxDepth, c) + + _, err := buf.WriteTo(w) + return err +} + +func writeHeader(w io.Writer, cmd *Command) { + fmt.Fprintf(w, "#compdef %s\n\n", cmd.Name()) +} + +func maxDepth(c *Command) int { + if len(c.Commands()) == 0 { + return 0 + } + maxDepthSub := 0 + for _, s := range c.Commands() { + subDepth := maxDepth(s) + if subDepth > maxDepthSub { + maxDepthSub = subDepth + } + } + return 1 + maxDepthSub +} + +func writeLevelMapping(w io.Writer, numLevels int) { + fmt.Fprintln(w, `_arguments \`) + for i := 1; i <= numLevels; i++ { + fmt.Fprintf(w, ` '%d: :->level%d' \`, i, i) + fmt.Fprintln(w) + } + fmt.Fprintf(w, ` '%d: :%s'`, numLevels+1, "_files") + fmt.Fprintln(w) +} + +func writeLevelCases(w io.Writer, maxDepth int, root *Command) { + fmt.Fprintln(w, "case $state in") + defer fmt.Fprintln(w, "esac") + + for i := 1; i <= maxDepth; i++ { + fmt.Fprintf(w, " level%d)\n", i) + writeLevel(w, root, i) + fmt.Fprintln(w, " ;;") + } + fmt.Fprintln(w, " *)") + fmt.Fprintln(w, " _arguments '*: :_files'") + fmt.Fprintln(w, " ;;") +} + +func writeLevel(w io.Writer, root *Command, i int) { + fmt.Fprintf(w, " case $words[%d] in\n", i) + defer fmt.Fprintln(w, " esac") + + commands := filterByLevel(root, i) + byParent := groupByParent(commands) + + for p, c := range byParent { + names := names(c) + fmt.Fprintf(w, " %s)\n", p) + fmt.Fprintf(w, " _arguments '%d: :(%s)'\n", i, strings.Join(names, " ")) + fmt.Fprintln(w, " ;;") + } + fmt.Fprintln(w, " *)") + fmt.Fprintln(w, " _arguments '*: :_files'") + fmt.Fprintln(w, " ;;") + +} + +func filterByLevel(c *Command, l int) []*Command { + cs := make([]*Command, 0) + if l == 0 { + cs = append(cs, c) + return cs + } + for _, s := range c.Commands() { + cs = append(cs, filterByLevel(s, l-1)...) + } + return cs +} + +func groupByParent(commands []*Command) map[string][]*Command { + m := make(map[string][]*Command) + for _, c := range commands { + parent := c.Parent() + if parent == nil { + continue + } + m[parent.Name()] = append(m[parent.Name()], c) + } + return m +} + +func names(commands []*Command) []string { + ns := make([]string, len(commands)) + for i, c := range commands { + ns[i] = c.Name() + } + return ns +} diff --git a/vendor/gopkg.in/fsnotify.v1/.editorconfig b/vendor/gopkg.in/fsnotify.v1/.editorconfig new file mode 100644 index 000000000..ba49e3c23 --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 diff --git a/vendor/gopkg.in/fsnotify.v1/.github/ISSUE_TEMPLATE.md b/vendor/gopkg.in/fsnotify.v1/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..4ad1aed8f --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,11 @@ +Before reporting an issue, please ensure you are using the latest release of fsnotify. + +### Which operating system (GOOS) and version are you using? + +Linux: lsb_release -a +macOS: sw_vers +Windows: systeminfo | findstr /B /C:OS + +### Please describe the issue that occurred. + +### Are you able to reproduce the issue? Please provide steps to reproduce and a code sample if possible. diff --git a/vendor/gopkg.in/fsnotify.v1/.github/PULL_REQUEST_TEMPLATE.md b/vendor/gopkg.in/fsnotify.v1/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..64ddf7cef --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +#### What does this pull request do? + + +#### Where should the reviewer start? + + +#### How should this be manually tested? + diff --git a/vendor/gopkg.in/fsnotify.v1/.gitignore b/vendor/gopkg.in/fsnotify.v1/.gitignore new file mode 100644 index 000000000..4cd0cbaf4 --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/.gitignore @@ -0,0 +1,6 @@ +# Setup a Global .gitignore for OS and editor generated files: +# https://help.github.com/articles/ignoring-files +# git config --global core.excludesfile ~/.gitignore_global + +.vagrant +*.sublime-project diff --git a/vendor/gopkg.in/fsnotify.v1/.travis.yml b/vendor/gopkg.in/fsnotify.v1/.travis.yml new file mode 100644 index 000000000..981d1bb81 --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/.travis.yml @@ -0,0 +1,30 @@ +sudo: false +language: go + +go: + - 1.8.x + - 1.9.x + - tip + +matrix: + allow_failures: + - go: tip + fast_finish: true + +before_script: + - go get -u github.com/golang/lint/golint + +script: + - go test -v --race ./... + +after_script: + - test -z "$(gofmt -s -l -w . | tee /dev/stderr)" + - test -z "$(golint ./... | tee /dev/stderr)" + - go vet ./... + +os: + - linux + - osx + +notifications: + email: false diff --git a/vendor/gopkg.in/fsnotify.v1/AUTHORS b/vendor/gopkg.in/fsnotify.v1/AUTHORS new file mode 100644 index 000000000..5ab5d41c5 --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/AUTHORS @@ -0,0 +1,52 @@ +# Names should be added to this file as +# Name or Organization <email address> +# The email address is not required for organizations. + +# You can update this list using the following command: +# +# $ git shortlog -se | awk '{print $2 " " $3 " " $4}' + +# Please keep the list sorted. + +Aaron L <aaron@bettercoder.net> +Adrien Bustany <adrien@bustany.org> +Amit Krishnan <amit.krishnan@oracle.com> +Anmol Sethi <me@anmol.io> +Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> +Bruno Bigras <bigras.bruno@gmail.com> +Caleb Spare <cespare@gmail.com> +Case Nelson <case@teammating.com> +Chris Howey <chris@howey.me> <howeyc@gmail.com> +Christoffer Buchholz <christoffer.buchholz@gmail.com> +Daniel Wagner-Hall <dawagner@gmail.com> +Dave Cheney <dave@cheney.net> +Evan Phoenix <evan@fallingsnow.net> +Francisco Souza <f@souza.cc> +Hari haran <hariharan.uno@gmail.com> +John C Barstow +Kelvin Fo <vmirage@gmail.com> +Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp> +Matt Layher <mdlayher@gmail.com> +Nathan Youngman <git@nathany.com> +Nickolai Zeldovich <nickolai@csail.mit.edu> +Patrick <patrick@dropbox.com> +Paul Hammond <paul@paulhammond.org> +Pawel Knap <pawelknap88@gmail.com> +Pieter Droogendijk <pieter@binky.org.uk> +Pursuit92 <JoshChase@techpursuit.net> +Riku Voipio <riku.voipio@linaro.org> +Rob Figueiredo <robfig@gmail.com> +Rodrigo Chiossi <rodrigochiossi@gmail.com> +Slawek Ligus <root@ooz.ie> +Soge Zhang <zhssoge@gmail.com> +Tiffany Jernigan <tiffany.jernigan@intel.com> +Tilak Sharma <tilaks@google.com> +Tom Payne <twpayne@gmail.com> +Travis Cline <travis.cline@gmail.com> +Tudor Golubenco <tudor.g@gmail.com> +Vahe Khachikyan <vahe@live.ca> +Yukang <moorekang@gmail.com> +bronze1man <bronze1man@gmail.com> +debrando <denis.brandolini@gmail.com> +henrikedwards <henrik.edwards@gmail.com> +铁哥 <guotie.9@gmail.com> diff --git a/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md b/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md new file mode 100644 index 000000000..be4d7ea2c --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md @@ -0,0 +1,317 @@ +# Changelog + +## v1.4.7 / 2018-01-09 + +* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine) +* Tests: Fix missing verb on format string (thanks @rchiossi) +* Linux: Fix deadlock in Remove (thanks @aarondl) +* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne) +* Docs: Moved FAQ into the README (thanks @vahe) +* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich) +* Docs: replace references to OS X with macOS + +## v1.4.2 / 2016-10-10 + +* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack) + +## v1.4.1 / 2016-10-04 + +* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack) + +## v1.4.0 / 2016-10-01 + +* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie) + +## v1.3.1 / 2016-06-28 + +* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc) + +## v1.3.0 / 2016-04-19 + +* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135) + +## v1.2.10 / 2016-03-02 + +* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj) + +## v1.2.9 / 2016-01-13 + +kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep) + +## v1.2.8 / 2015-12-17 + +* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test) +* inotify: fix race in test +* enable race detection for continuous integration (Linux, Mac, Windows) + +## v1.2.5 / 2015-10-17 + +* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki) +* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken) +* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie) +* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion) + +## v1.2.1 / 2015-10-14 + +* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx) + +## v1.2.0 / 2015-02-08 + +* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD) +* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD) +* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59) + +## v1.1.1 / 2015-02-05 + +* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD) + +## v1.1.0 / 2014-12-12 + +* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43) + * add low-level functions + * only need to store flags on directories + * less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13) + * done can be an unbuffered channel + * remove calls to os.NewSyscallError +* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher) +* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48) +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## v1.0.4 / 2014-09-07 + +* kqueue: add dragonfly to the build tags. +* Rename source code files, rearrange code so exported APIs are at the top. +* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang) + +## v1.0.3 / 2014-08-19 + +* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36) + +## v1.0.2 / 2014-08-17 + +* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) +* [Fix] Make ./path and path equivalent. (thanks @zhsso) + +## v1.0.0 / 2014-08-15 + +* [API] Remove AddWatch on Windows, use Add. +* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30) +* Minor updates based on feedback from golint. + +## dev / 2014-07-09 + +* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify). +* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno) + +## dev / 2014-07-04 + +* kqueue: fix incorrect mutex used in Close() +* Update example to demonstrate usage of Op. + +## dev / 2014-06-28 + +* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4) +* Fix for String() method on Event (thanks Alex Brainman) +* Don't build on Plan 9 or Solaris (thanks @4ad) + +## dev / 2014-06-21 + +* Events channel of type Event rather than *Event. +* [internal] use syscall constants directly for inotify and kqueue. +* [internal] kqueue: rename events to kevents and fileEvent to event. + +## dev / 2014-06-19 + +* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally). +* [internal] remove cookie from Event struct (unused). +* [internal] Event struct has the same definition across every OS. +* [internal] remove internal watch and removeWatch methods. + +## dev / 2014-06-12 + +* [API] Renamed Watch() to Add() and RemoveWatch() to Remove(). +* [API] Pluralized channel names: Events and Errors. +* [API] Renamed FileEvent struct to Event. +* [API] Op constants replace methods like IsCreate(). + +## dev / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## dev / 2014-05-23 + +* [API] Remove current implementation of WatchFlags. + * current implementation doesn't take advantage of OS for efficiency + * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes + * no tests for the current implementation + * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) + +## v0.9.3 / 2014-12-31 + +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51) + +## v0.9.2 / 2014-08-17 + +* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso) + +## v0.9.1 / 2014-06-12 + +* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98) + +## v0.9.0 / 2014-01-17 + +* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany) +* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare) +* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library. + +## v0.8.12 / 2013-11-13 + +* [API] Remove FD_SET and friends from Linux adapter + +## v0.8.11 / 2013-11-02 + +* [Doc] Add Changelog [#72][] (thanks @nathany) +* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond) + +## v0.8.10 / 2013-10-19 + +* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott) +* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer) +* [Doc] specify OS-specific limits in README (thanks @debrando) + +## v0.8.9 / 2013-09-08 + +* [Doc] Contributing (thanks @nathany) +* [Doc] update package path in example code [#63][] (thanks @paulhammond) +* [Doc] GoCI badge in README (Linux only) [#60][] +* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany) + +## v0.8.8 / 2013-06-17 + +* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie) + +## v0.8.7 / 2013-06-03 + +* [API] Make syscall flags internal +* [Fix] inotify: ignore event changes +* [Fix] race in symlink test [#45][] (reported by @srid) +* [Fix] tests on Windows +* lower case error messages + +## v0.8.6 / 2013-05-23 + +* kqueue: Use EVT_ONLY flag on Darwin +* [Doc] Update README with full example + +## v0.8.5 / 2013-05-09 + +* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg) + +## v0.8.4 / 2013-04-07 + +* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz) + +## v0.8.3 / 2013-03-13 + +* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin) +* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin) + +## v0.8.2 / 2013-02-07 + +* [Doc] add Authors +* [Fix] fix data races for map access [#29][] (thanks @fsouza) + +## v0.8.1 / 2013-01-09 + +* [Fix] Windows path separators +* [Doc] BSD License + +## v0.8.0 / 2012-11-09 + +* kqueue: directory watching improvements (thanks @vmirage) +* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto) +* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr) + +## v0.7.4 / 2012-10-09 + +* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji) +* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig) +* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig) +* [Fix] kqueue: modify after recreation of file + +## v0.7.3 / 2012-09-27 + +* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage) +* [Fix] kqueue: no longer get duplicate CREATE events + +## v0.7.2 / 2012-09-01 + +* kqueue: events for created directories + +## v0.7.1 / 2012-07-14 + +* [Fix] for renaming files + +## v0.7.0 / 2012-07-02 + +* [Feature] FSNotify flags +* [Fix] inotify: Added file name back to event path + +## v0.6.0 / 2012-06-06 + +* kqueue: watch files after directory created (thanks @tmc) + +## v0.5.1 / 2012-05-22 + +* [Fix] inotify: remove all watches before Close() + +## v0.5.0 / 2012-05-03 + +* [API] kqueue: return errors during watch instead of sending over channel +* kqueue: match symlink behavior on Linux +* inotify: add `DELETE_SELF` (requested by @taralx) +* [Fix] kqueue: handle EINTR (reported by @robfig) +* [Doc] Godoc example [#1][] (thanks @davecheney) + +## v0.4.0 / 2012-03-30 + +* Go 1 released: build with go tool +* [Feature] Windows support using winfsnotify +* Windows does not have attribute change notifications +* Roll attribute notifications into IsModify + +## v0.3.0 / 2012-02-19 + +* kqueue: add files when watch directory + +## v0.2.0 / 2011-12-30 + +* update to latest Go weekly code + +## v0.1.0 / 2011-10-19 + +* kqueue: add watch on file creation to match inotify +* kqueue: create file event +* inotify: ignore `IN_IGNORED` events +* event String() +* linux: common FileEvent functions +* initial commit + +[#79]: https://github.com/howeyc/fsnotify/pull/79 +[#77]: https://github.com/howeyc/fsnotify/pull/77 +[#72]: https://github.com/howeyc/fsnotify/issues/72 +[#71]: https://github.com/howeyc/fsnotify/issues/71 +[#70]: https://github.com/howeyc/fsnotify/issues/70 +[#63]: https://github.com/howeyc/fsnotify/issues/63 +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#60]: https://github.com/howeyc/fsnotify/issues/60 +[#59]: https://github.com/howeyc/fsnotify/issues/59 +[#49]: https://github.com/howeyc/fsnotify/issues/49 +[#45]: https://github.com/howeyc/fsnotify/issues/45 +[#40]: https://github.com/howeyc/fsnotify/issues/40 +[#36]: https://github.com/howeyc/fsnotify/issues/36 +[#33]: https://github.com/howeyc/fsnotify/issues/33 +[#29]: https://github.com/howeyc/fsnotify/issues/29 +[#25]: https://github.com/howeyc/fsnotify/issues/25 +[#24]: https://github.com/howeyc/fsnotify/issues/24 +[#21]: https://github.com/howeyc/fsnotify/issues/21 diff --git a/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md b/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md new file mode 100644 index 000000000..828a60b24 --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/CONTRIBUTING.md @@ -0,0 +1,77 @@ +# Contributing + +## Issues + +* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues). +* Please indicate the platform you are using fsnotify on. +* A code example to reproduce the problem is appreciated. + +## Pull Requests + +### Contributor License Agreement + +fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). + +Please indicate that you have signed the CLA in your pull request. + +### How fsnotify is Developed + +* Development is done on feature branches. +* Tests are run on BSD, Linux, macOS and Windows. +* Pull requests are reviewed and [applied to master][am] using [hub][]. + * Maintainers may modify or squash commits rather than asking contributors to. +* To issue a new release, the maintainers will: + * Update the CHANGELOG + * Tag a version, which will become available through gopkg.in. + +### How to Fork + +For smooth sailing, always use the original import path. Installing with `go get` makes this easy. + +1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Ensure everything works and the tests pass (see below) +4. Commit your changes (`git commit -am 'Add some feature'`) + +Contribute upstream: + +1. Fork fsnotify on GitHub +2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) +3. Push to the branch (`git push fork my-new-feature`) +4. Create a new Pull Request on GitHub + +This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/). + +### Testing + +fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows. + +Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. + +To aid in cross-platform testing there is a Vagrantfile for Linux and BSD. + +* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) +* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder. +* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password) +* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`. +* When you're done, you will want to halt or destroy the Vagrant boxes. + +Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory. + +Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). + +### Maintainers + +Help maintaining fsnotify is welcome. To be a maintainer: + +* Submit a pull request and sign the CLA as above. +* You must be able to run the test suite on Mac, Windows, Linux and BSD. + +To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][]. + +All code changes should be internal pull requests. + +Releases are tagged using [Semantic Versioning](http://semver.org/). + +[hub]: https://github.com/github/hub +[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs diff --git a/vendor/gopkg.in/fsnotify.v1/README.md b/vendor/gopkg.in/fsnotify.v1/README.md index 3c891e349..399320741 100644 --- a/vendor/gopkg.in/fsnotify.v1/README.md +++ b/vendor/gopkg.in/fsnotify.v1/README.md @@ -8,14 +8,14 @@ fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather go get -u golang.org/x/sys/... ``` -Cross platform: Windows, Linux, BSD and OS X. +Cross platform: Windows, Linux, BSD and macOS. |Adapter |OS |Status | |----------|----------|----------| |inotify |Linux 2.6.27 or later, Android\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)| -|kqueue |BSD, OS X, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)| +|kqueue |BSD, macOS, iOS\*|Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify)| |ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| -|FSEvents |OS X |[Planned](https://github.com/fsnotify/fsnotify/issues/11)| +|FSEvents |macOS |[Planned](https://github.com/fsnotify/fsnotify/issues/11)| |FEN |Solaris 11 |[In Progress](https://github.com/fsnotify/fsnotify/issues/12)| |fanotify |Linux 2.6.37+ | | |USN Journals |Windows |[Maybe](https://github.com/fsnotify/fsnotify/issues/53)| @@ -23,7 +23,7 @@ Cross platform: Windows, Linux, BSD and OS X. \* Android and iOS are untested. -Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) for usage. Consult the [Wiki](https://github.com/fsnotify/fsnotify/wiki) for the FAQ and further information. +Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information. ## API stability @@ -41,6 +41,35 @@ Please refer to [CONTRIBUTING][] before opening an issue or pull request. See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go). +## FAQ + +**When a file is moved to another directory is it still being watched?** + +No (it shouldn't be, unless you are watching where it was moved to). + +**When I watch a directory, are all subdirectories watched as well?** + +No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]). + +**Do I have to watch the Error and Event channels in a separate goroutine?** + +As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7]) + +**Why am I receiving multiple events for the same file on OS X?** + +Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]). + +**How many files can be watched at once?** + +There are OS-specific limits as to how many watches can be created: +* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error. +* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error. + +[#62]: https://github.com/howeyc/fsnotify/issues/62 +[#18]: https://github.com/fsnotify/fsnotify/issues/18 +[#11]: https://github.com/fsnotify/fsnotify/issues/11 +[#7]: https://github.com/howeyc/fsnotify/issues/7 + [contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md ## Related Projects diff --git a/vendor/gopkg.in/fsnotify.v1/example_test.go b/vendor/gopkg.in/fsnotify.v1/example_test.go new file mode 100644 index 000000000..700502cb3 --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/example_test.go @@ -0,0 +1,42 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !plan9 + +package fsnotify_test + +import ( + "log" + + "github.com/fsnotify/fsnotify" +) + +func ExampleNewWatcher() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event := <-watcher.Events: + log.Println("event:", event) + if event.Op&fsnotify.Write == fsnotify.Write { + log.Println("modified file:", event.Name) + } + case err := <-watcher.Errors: + log.Println("error:", err) + } + } + }() + + err = watcher.Add("/tmp/foo") + if err != nil { + log.Fatal(err) + } + <-done +} diff --git a/vendor/gopkg.in/fsnotify.v1/fsnotify.go b/vendor/gopkg.in/fsnotify.v1/fsnotify.go index e7f55fee7..190bf0de5 100644 --- a/vendor/gopkg.in/fsnotify.v1/fsnotify.go +++ b/vendor/gopkg.in/fsnotify.v1/fsnotify.go @@ -9,6 +9,7 @@ package fsnotify import ( "bytes" + "errors" "fmt" ) @@ -60,3 +61,6 @@ func (op Op) String() string { func (e Event) String() string { return fmt.Sprintf("%q: %s", e.Name, e.Op.String()) } + +// Common errors that can be reported by a watcher +var ErrEventOverflow = errors.New("fsnotify queue overflow") diff --git a/vendor/gopkg.in/fsnotify.v1/fsnotify_test.go b/vendor/gopkg.in/fsnotify.v1/fsnotify_test.go new file mode 100644 index 000000000..f9771d9df --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/fsnotify_test.go @@ -0,0 +1,70 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !plan9 + +package fsnotify + +import ( + "os" + "testing" + "time" +) + +func TestEventStringWithValue(t *testing.T) { + for opMask, expectedString := range map[Op]string{ + Chmod | Create: `"/usr/someFile": CREATE|CHMOD`, + Rename: `"/usr/someFile": RENAME`, + Remove: `"/usr/someFile": REMOVE`, + Write | Chmod: `"/usr/someFile": WRITE|CHMOD`, + } { + event := Event{Name: "/usr/someFile", Op: opMask} + if event.String() != expectedString { + t.Fatalf("Expected %s, got: %v", expectedString, event.String()) + } + + } +} + +func TestEventOpStringWithValue(t *testing.T) { + expectedOpString := "WRITE|CHMOD" + event := Event{Name: "someFile", Op: Write | Chmod} + if event.Op.String() != expectedOpString { + t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String()) + } +} + +func TestEventOpStringWithNoValue(t *testing.T) { + expectedOpString := "" + event := Event{Name: "testFile", Op: 0} + if event.Op.String() != expectedOpString { + t.Fatalf("Expected %s, got: %v", expectedOpString, event.Op.String()) + } +} + +// TestWatcherClose tests that the goroutine started by creating the watcher can be +// signalled to return at any time, even if there is no goroutine listening on the events +// or errors channels. +func TestWatcherClose(t *testing.T) { + t.Parallel() + + name := tempMkFile(t, "") + w := newWatcher(t) + err := w.Add(name) + if err != nil { + t.Fatal(err) + } + + err = os.Remove(name) + if err != nil { + t.Fatal(err) + } + // Allow the watcher to receive the event. + time.Sleep(time.Millisecond * 100) + + err = w.Close() + if err != nil { + t.Fatal(err) + } +} diff --git a/vendor/gopkg.in/fsnotify.v1/inotify.go b/vendor/gopkg.in/fsnotify.v1/inotify.go index f3b74c51f..d9fd1b88a 100644 --- a/vendor/gopkg.in/fsnotify.v1/inotify.go +++ b/vendor/gopkg.in/fsnotify.v1/inotify.go @@ -24,7 +24,6 @@ type Watcher struct { Events chan Event Errors chan error mu sync.Mutex // Map access - cv *sync.Cond // sync removing on rm_watch with IN_IGNORE fd int poller *fdPoller watches map[string]*watch // Map of inotify watches (key: path) @@ -56,7 +55,6 @@ func NewWatcher() (*Watcher, error) { done: make(chan struct{}), doneResp: make(chan struct{}), } - w.cv = sync.NewCond(&w.mu) go w.readEvents() return w, nil @@ -103,21 +101,23 @@ func (w *Watcher) Add(name string) error { var flags uint32 = agnosticEvents w.mu.Lock() - watchEntry, found := w.watches[name] - w.mu.Unlock() - if found { - watchEntry.flags |= flags - flags |= unix.IN_MASK_ADD + defer w.mu.Unlock() + watchEntry := w.watches[name] + if watchEntry != nil { + flags |= watchEntry.flags | unix.IN_MASK_ADD } wd, errno := unix.InotifyAddWatch(w.fd, name, flags) if wd == -1 { return errno } - w.mu.Lock() - w.watches[name] = &watch{wd: uint32(wd), flags: flags} - w.paths[wd] = name - w.mu.Unlock() + if watchEntry == nil { + w.watches[name] = &watch{wd: uint32(wd), flags: flags} + w.paths[wd] = name + } else { + watchEntry.wd = uint32(wd) + watchEntry.flags = flags + } return nil } @@ -135,6 +135,13 @@ func (w *Watcher) Remove(name string) error { if !ok { return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) } + + // We successfully removed the watch if InotifyRmWatch doesn't return an + // error, we need to clean up our internal state to ensure it matches + // inotify's kernel state. + delete(w.paths, int(watch.wd)) + delete(w.watches, name) + // inotify_rm_watch will return EINVAL if the file has been deleted; // the inotify will already have been removed. // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously @@ -152,13 +159,6 @@ func (w *Watcher) Remove(name string) error { return errno } - // wait until ignoreLinux() deleting maps - exists := true - for exists { - w.cv.Wait() - _, exists = w.watches[name] - } - return nil } @@ -245,13 +245,31 @@ func (w *Watcher) readEvents() { mask := uint32(raw.Mask) nameLen := uint32(raw.Len) + + if mask&unix.IN_Q_OVERFLOW != 0 { + select { + case w.Errors <- ErrEventOverflow: + case <-w.done: + return + } + } + // If the event happened to the watched directory or the watched file, the kernel // doesn't append the filename to the event, but we would like to always fill the // the "Name" field with a valid filename. We retrieve the path of the watch from // the "paths" map. w.mu.Lock() - name := w.paths[int(raw.Wd)] + name, ok := w.paths[int(raw.Wd)] + // IN_DELETE_SELF occurs when the file/directory being watched is removed. + // This is a sign to clean up the maps, otherwise we are no longer in sync + // with the inotify kernel state which has already deleted the watch + // automatically. + if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { + delete(w.paths, int(raw.Wd)) + delete(w.watches, name) + } w.mu.Unlock() + if nameLen > 0 { // Point "bytes" at the first byte of the filename bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent])) @@ -262,7 +280,7 @@ func (w *Watcher) readEvents() { event := newEvent(name, mask) // Send the events that are not ignored on the events channel - if !event.ignoreLinux(w, raw.Wd, mask) { + if !event.ignoreLinux(mask) { select { case w.Events <- event: case <-w.done: @@ -279,15 +297,9 @@ func (w *Watcher) readEvents() { // Certain types of events can be "ignored" and not sent over the Events // channel. Such as events marked ignore by the kernel, or MODIFY events // against files that do not exist. -func (e *Event) ignoreLinux(w *Watcher, wd int32, mask uint32) bool { +func (e *Event) ignoreLinux(mask uint32) bool { // Ignore anything the inotify API says to ignore if mask&unix.IN_IGNORED == unix.IN_IGNORED { - w.mu.Lock() - defer w.mu.Unlock() - name := w.paths[int(wd)] - delete(w.paths, int(wd)) - delete(w.watches, name) - w.cv.Broadcast() return true } diff --git a/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go b/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go new file mode 100644 index 000000000..26623efef --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/inotify_poller_test.go @@ -0,0 +1,229 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "testing" + "time" + + "golang.org/x/sys/unix" +) + +type testFd [2]int + +func makeTestFd(t *testing.T) testFd { + var tfd testFd + errno := unix.Pipe(tfd[:]) + if errno != nil { + t.Fatalf("Failed to create pipe: %v", errno) + } + return tfd +} + +func (tfd testFd) fd() int { + return tfd[0] +} + +func (tfd testFd) closeWrite(t *testing.T) { + errno := unix.Close(tfd[1]) + if errno != nil { + t.Fatalf("Failed to close write end of pipe: %v", errno) + } +} + +func (tfd testFd) put(t *testing.T) { + buf := make([]byte, 10) + _, errno := unix.Write(tfd[1], buf) + if errno != nil { + t.Fatalf("Failed to write to pipe: %v", errno) + } +} + +func (tfd testFd) get(t *testing.T) { + buf := make([]byte, 10) + _, errno := unix.Read(tfd[0], buf) + if errno != nil { + t.Fatalf("Failed to read from pipe: %v", errno) + } +} + +func (tfd testFd) close() { + unix.Close(tfd[1]) + unix.Close(tfd[0]) +} + +func makePoller(t *testing.T) (testFd, *fdPoller) { + tfd := makeTestFd(t) + poller, err := newFdPoller(tfd.fd()) + if err != nil { + t.Fatalf("Failed to create poller: %v", err) + } + return tfd, poller +} + +func TestPollerWithBadFd(t *testing.T) { + _, err := newFdPoller(-1) + if err != unix.EBADF { + t.Fatalf("Expected EBADF, got: %v", err) + } +} + +func TestPollerWithData(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.put(t) + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + tfd.get(t) +} + +func TestPollerWithWakeup(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if ok { + t.Fatalf("expected poller to return false") + } +} + +func TestPollerWithClose(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.closeWrite(t) + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } +} + +func TestPollerWithWakeupAndData(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.put(t) + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + + // both data and wakeup + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + + // data is still in the buffer, wakeup is cleared + ok, err = poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + + tfd.get(t) + // data is gone, only wakeup now + err = poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + ok, err = poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if ok { + t.Fatalf("expected poller to return false") + } +} + +func TestPollerConcurrent(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + oks := make(chan bool) + live := make(chan bool) + defer close(live) + go func() { + defer close(oks) + for { + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + oks <- ok + if !<-live { + return + } + } + }() + + // Try a write + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + tfd.put(t) + if !<-oks { + t.Fatalf("expected true") + } + tfd.get(t) + live <- true + + // Try a wakeup + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + if <-oks { + t.Fatalf("expected false") + } + live <- true + + // Try a close + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + tfd.closeWrite(t) + if !<-oks { + t.Fatalf("expected true") + } + tfd.get(t) +} diff --git a/vendor/gopkg.in/fsnotify.v1/inotify_test.go b/vendor/gopkg.in/fsnotify.v1/inotify_test.go new file mode 100644 index 000000000..54f3f00eb --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/inotify_test.go @@ -0,0 +1,449 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestInotifyCloseRightAway(t *testing.T) { + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + // Close immediately; it won't even reach the first unix.Read. + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseSlightlyLater(t *testing.T) { + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + // Wait until readEvents has reached unix.Read, and Close. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + w.Add(testDir) + + // Wait until readEvents has reached unix.Read, and Close. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseAfterRead(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add .") + } + + // Generate an event. + os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING")) + + // Wait for readEvents to read the event, then close the watcher. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func isWatcherReallyClosed(t *testing.T, w *Watcher) { + select { + case err, ok := <-w.Errors: + if ok { + t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err) + } + default: + t.Fatalf("w.Errors would have blocked; readEvents is still alive!") + } + + select { + case _, ok := <-w.Events: + if ok { + t.Fatalf("w.Events is not closed; readEvents is still alive after closing") + } + default: + t.Fatalf("w.Events would have blocked; readEvents is still alive!") + } +} + +func TestInotifyCloseCreate(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add testDir: %v", err) + } + h, err := os.Create(filepath.Join(testDir, "testfile")) + if err != nil { + t.Fatalf("Failed to create file in testdir: %v", err) + } + h.Close() + select { + case _ = <-w.Events: + case err := <-w.Errors: + t.Fatalf("Error from watcher: %v", err) + case <-time.After(50 * time.Millisecond): + t.Fatalf("Took too long to wait for event") + } + + // At this point, we've received one event, so the goroutine is ready. + // It's also blocking on unix.Read. + // Now we try to swap the file descriptor under its nose. + w.Close() + w, err = NewWatcher() + defer w.Close() + if err != nil { + t.Fatalf("Failed to create second watcher: %v", err) + } + + <-time.After(50 * time.Millisecond) + err = w.Add(testDir) + if err != nil { + t.Fatalf("Error adding testDir again: %v", err) + } +} + +// This test verifies the watcher can keep up with file creations/deletions +// when under load. +func TestInotifyStress(t *testing.T) { + maxNumToCreate := 1000 + + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFilePrefix := filepath.Join(testDir, "testfile") + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add testDir: %v", err) + } + + doneChan := make(chan struct{}) + // The buffer ensures that the file generation goroutine is never blocked. + errChan := make(chan error, 2*maxNumToCreate) + + go func() { + for i := 0; i < maxNumToCreate; i++ { + testFile := fmt.Sprintf("%s%d", testFilePrefix, i) + + handle, err := os.Create(testFile) + if err != nil { + errChan <- fmt.Errorf("Create failed: %v", err) + continue + } + + err = handle.Close() + if err != nil { + errChan <- fmt.Errorf("Close failed: %v", err) + continue + } + } + + // If we delete a newly created file too quickly, inotify will skip the + // create event and only send the delete event. + time.Sleep(100 * time.Millisecond) + + for i := 0; i < maxNumToCreate; i++ { + testFile := fmt.Sprintf("%s%d", testFilePrefix, i) + err = os.Remove(testFile) + if err != nil { + errChan <- fmt.Errorf("Remove failed: %v", err) + } + } + + close(doneChan) + }() + + creates := 0 + removes := 0 + + finished := false + after := time.After(10 * time.Second) + for !finished { + select { + case <-after: + t.Fatalf("Not done") + case <-doneChan: + finished = true + case err := <-errChan: + t.Fatalf("Got an error from file creator goroutine: %v", err) + case err := <-w.Errors: + t.Fatalf("Got an error from watcher: %v", err) + case evt := <-w.Events: + if !strings.HasPrefix(evt.Name, testFilePrefix) { + t.Fatalf("Got an event for an unknown file: %s", evt.Name) + } + if evt.Op == Create { + creates++ + } + if evt.Op == Remove { + removes++ + } + } + } + + // Drain remaining events from channels + count := 0 + for count < 10 { + select { + case err := <-errChan: + t.Fatalf("Got an error from file creator goroutine: %v", err) + case err := <-w.Errors: + t.Fatalf("Got an error from watcher: %v", err) + case evt := <-w.Events: + if !strings.HasPrefix(evt.Name, testFilePrefix) { + t.Fatalf("Got an event for an unknown file: %s", evt.Name) + } + if evt.Op == Create { + creates++ + } + if evt.Op == Remove { + removes++ + } + count = 0 + default: + count++ + // Give the watcher chances to fill the channels. + time.Sleep(time.Millisecond) + } + } + + if creates-removes > 1 || creates-removes < -1 { + t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes) + } + if creates < 50 { + t.Fatalf("Expected at least 50 creates, got %d", creates) + } +} + +func TestInotifyRemoveTwice(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFile := filepath.Join(testDir, "testfile") + + handle, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + handle.Close() + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testFile) + if err != nil { + t.Fatalf("Failed to add testFile: %v", err) + } + + err = w.Remove(testFile) + if err != nil { + t.Fatalf("wanted successful remove but got: %v", err) + } + + err = w.Remove(testFile) + if err == nil { + t.Fatalf("no error on removing invalid file") + } + + w.mu.Lock() + defer w.mu.Unlock() + if len(w.watches) != 0 { + t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches) + } + if len(w.paths) != 0 { + t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths) + } +} + +func TestInotifyInnerMapLength(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFile := filepath.Join(testDir, "testfile") + + handle, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + handle.Close() + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testFile) + if err != nil { + t.Fatalf("Failed to add testFile: %v", err) + } + go func() { + for err := range w.Errors { + t.Fatalf("error received: %s", err) + } + }() + + err = os.Remove(testFile) + if err != nil { + t.Fatalf("Failed to remove testFile: %v", err) + } + _ = <-w.Events // consume Remove event + <-time.After(50 * time.Millisecond) // wait IN_IGNORE propagated + + w.mu.Lock() + defer w.mu.Unlock() + if len(w.watches) != 0 { + t.Fatalf("Expected watches len is 0, but got: %d, %v", len(w.watches), w.watches) + } + if len(w.paths) != 0 { + t.Fatalf("Expected paths len is 0, but got: %d, %v", len(w.paths), w.paths) + } +} + +func TestInotifyOverflow(t *testing.T) { + // We need to generate many more events than the + // fs.inotify.max_queued_events sysctl setting. + // We use multiple goroutines (one per directory) + // to speed up file creation. + numDirs := 128 + numFiles := 1024 + + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + for dn := 0; dn < numDirs; dn++ { + testSubdir := fmt.Sprintf("%s/%d", testDir, dn) + + err := os.Mkdir(testSubdir, 0777) + if err != nil { + t.Fatalf("Cannot create subdir: %v", err) + } + + err = w.Add(testSubdir) + if err != nil { + t.Fatalf("Failed to add subdir: %v", err) + } + } + + errChan := make(chan error, numDirs*numFiles) + + for dn := 0; dn < numDirs; dn++ { + testSubdir := fmt.Sprintf("%s/%d", testDir, dn) + + go func() { + for fn := 0; fn < numFiles; fn++ { + testFile := fmt.Sprintf("%s/%d", testSubdir, fn) + + handle, err := os.Create(testFile) + if err != nil { + errChan <- fmt.Errorf("Create failed: %v", err) + continue + } + + err = handle.Close() + if err != nil { + errChan <- fmt.Errorf("Close failed: %v", err) + continue + } + } + }() + } + + creates := 0 + overflows := 0 + + after := time.After(10 * time.Second) + for overflows == 0 && creates < numDirs*numFiles { + select { + case <-after: + t.Fatalf("Not done") + case err := <-errChan: + t.Fatalf("Got an error from file creator goroutine: %v", err) + case err := <-w.Errors: + if err == ErrEventOverflow { + overflows++ + } else { + t.Fatalf("Got an error from watcher: %v", err) + } + case evt := <-w.Events: + if !strings.HasPrefix(evt.Name, testDir) { + t.Fatalf("Got an event for an unknown file: %s", evt.Name) + } + if evt.Op == Create { + creates++ + } + } + } + + if creates == numDirs*numFiles { + t.Fatalf("Could not trigger overflow") + } + + if overflows == 0 { + t.Fatalf("No overflow and not enough creates (expected %d, got %d)", + numDirs*numFiles, creates) + } +} diff --git a/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go b/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go new file mode 100644 index 000000000..cd6adc273 --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/integration_darwin_test.go @@ -0,0 +1,147 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fsnotify + +import ( + "os" + "path/filepath" + "testing" + "time" + + "golang.org/x/sys/unix" +) + +// testExchangedataForWatcher tests the watcher with the exchangedata operation on macOS. +// +// This is widely used for atomic saves on macOS, e.g. TextMate and in Apple's NSDocument. +// +// See https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/exchangedata.2.html +// Also see: https://github.com/textmate/textmate/blob/cd016be29489eba5f3c09b7b70b06da134dda550/Frameworks/io/src/swap_file_data.cc#L20 +func testExchangedataForWatcher(t *testing.T, watchDir bool) { + // Create directory to watch + testDir1 := tempMkdir(t) + + // For the intermediate file + testDir2 := tempMkdir(t) + + defer os.RemoveAll(testDir1) + defer os.RemoveAll(testDir2) + + resolvedFilename := "TestFsnotifyEvents.file" + + // TextMate does: + // + // 1. exchangedata (intermediate, resolved) + // 2. unlink intermediate + // + // Let's try to simulate that: + resolved := filepath.Join(testDir1, resolvedFilename) + intermediate := filepath.Join(testDir2, resolvedFilename+"~") + + // Make sure we create the file before we start watching + createAndSyncFile(t, resolved) + + watcher := newWatcher(t) + + // Test both variants in isolation + if watchDir { + addWatch(t, watcher, testDir1) + } else { + addWatch(t, watcher, resolved) + } + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var removeReceived counter + var createReceived counter + + done := make(chan bool) + + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(resolved) { + if event.Op&Remove == Remove { + removeReceived.increment() + } + if event.Op&Create == Create { + createReceived.increment() + } + } + t.Logf("event received: %s", event) + } + done <- true + }() + + // Repeat to make sure the watched file/directory "survives" the REMOVE/CREATE loop. + for i := 1; i <= 3; i++ { + // The intermediate file is created in a folder outside the watcher + createAndSyncFile(t, intermediate) + + // 1. Swap + if err := unix.Exchangedata(intermediate, resolved, 0); err != nil { + t.Fatalf("[%d] exchangedata failed: %s", i, err) + } + + time.Sleep(50 * time.Millisecond) + + // 2. Delete the intermediate file + err := os.Remove(intermediate) + + if err != nil { + t.Fatalf("[%d] remove %s failed: %s", i, intermediate, err) + } + + time.Sleep(50 * time.Millisecond) + + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + + // The events will be (CHMOD + REMOVE + CREATE) X 2. Let's focus on the last two: + if removeReceived.value() < 3 { + t.Fatal("fsnotify remove events have not been received after 500 ms") + } + + if createReceived.value() < 3 { + t.Fatal("fsnotify create events have not been received after 500 ms") + } + + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } +} + +// TestExchangedataInWatchedDir test exchangedata operation on file in watched dir. +func TestExchangedataInWatchedDir(t *testing.T) { + testExchangedataForWatcher(t, true) +} + +// TestExchangedataInWatchedDir test exchangedata operation on watched file. +func TestExchangedataInWatchedFile(t *testing.T) { + testExchangedataForWatcher(t, false) +} + +func createAndSyncFile(t *testing.T, filepath string) { + f1, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating %s failed: %s", filepath, err) + } + f1.Sync() + f1.Close() +} diff --git a/vendor/gopkg.in/fsnotify.v1/integration_test.go b/vendor/gopkg.in/fsnotify.v1/integration_test.go new file mode 100644 index 000000000..8b7e9d3ec --- /dev/null +++ b/vendor/gopkg.in/fsnotify.v1/integration_test.go @@ -0,0 +1,1237 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !plan9,!solaris + +package fsnotify + +import ( + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "sync/atomic" + "testing" + "time" +) + +// An atomic counter +type counter struct { + val int32 +} + +func (c *counter) increment() { + atomic.AddInt32(&c.val, 1) +} + +func (c *counter) value() int32 { + return atomic.LoadInt32(&c.val) +} + +func (c *counter) reset() { + atomic.StoreInt32(&c.val, 0) +} + +// tempMkdir makes a temporary directory +func tempMkdir(t *testing.T) string { + dir, err := ioutil.TempDir("", "fsnotify") + if err != nil { + t.Fatalf("failed to create test directory: %s", err) + } + return dir +} + +// tempMkFile makes a temporary file. +func tempMkFile(t *testing.T, dir string) string { + f, err := ioutil.TempFile(dir, "fsnotify") + if err != nil { + t.Fatalf("failed to create test file: %v", err) + } + defer f.Close() + return f.Name() +} + +// newWatcher initializes an fsnotify Watcher instance. +func newWatcher(t *testing.T) *Watcher { + watcher, err := NewWatcher() + if err != nil { + t.Fatalf("NewWatcher() failed: %s", err) + } + return watcher +} + +// addWatch adds a watch for a directory +func addWatch(t *testing.T, watcher *Watcher, dir string) { + if err := watcher.Add(dir); err != nil { + t.Fatalf("watcher.Add(%q) failed: %s", dir, err) + } +} + +func TestFsnotifyMultipleOperations(t *testing.T) { + watcher := newWatcher(t) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Create directory that's not watched + testDirToMoveFiles := tempMkdir(t) + defer os.RemoveAll(testDirToMoveFiles) + + testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile") + testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile") + + addWatch(t, watcher, testDir) + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var createReceived, modifyReceived, deleteReceived, renameReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { + t.Logf("event received: %s", event) + if event.Op&Remove == Remove { + deleteReceived.increment() + } + if event.Op&Write == Write { + modifyReceived.increment() + } + if event.Op&Create == Create { + createReceived.increment() + } + if event.Op&Rename == Rename { + renameReceived.increment() + } + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the fsnotify event queue + var f *os.File + f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + + time.Sleep(time.Millisecond) + f.WriteString("data") + f.Sync() + f.Close() + + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + if err := testRename(testFile, testFileRenamed); err != nil { + t.Fatalf("rename failed: %s", err) + } + + // Modify the file outside of the watched dir + f, err = os.Open(testFileRenamed) + if err != nil { + t.Fatalf("open test renamed file failed: %s", err) + } + f.WriteString("data") + f.Sync() + f.Close() + + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + // Recreate the file that was moved + f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Close() + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + cReceived := createReceived.value() + if cReceived != 2 { + t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) + } + mReceived := modifyReceived.value() + if mReceived != 1 { + t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1) + } + dReceived := deleteReceived.value() + rReceived := renameReceived.value() + if dReceived+rReceived != 1 { + t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1) + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } +} + +func TestFsnotifyMultipleCreates(t *testing.T) { + watcher := newWatcher(t) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile") + + addWatch(t, watcher, testDir) + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var createReceived, modifyReceived, deleteReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { + t.Logf("event received: %s", event) + if event.Op&Remove == Remove { + deleteReceived.increment() + } + if event.Op&Create == Create { + createReceived.increment() + } + if event.Op&Write == Write { + modifyReceived.increment() + } + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the fsnotify event queue + var f *os.File + f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + + time.Sleep(time.Millisecond) + f.WriteString("data") + f.Sync() + f.Close() + + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + os.Remove(testFile) + + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + // Recreate the file + f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Close() + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + // Modify + f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + + time.Sleep(time.Millisecond) + f.WriteString("data") + f.Sync() + f.Close() + + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + // Modify + f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + + time.Sleep(time.Millisecond) + f.WriteString("data") + f.Sync() + f.Close() + + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + cReceived := createReceived.value() + if cReceived != 2 { + t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) + } + mReceived := modifyReceived.value() + if mReceived < 3 { + t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3) + } + dReceived := deleteReceived.value() + if dReceived != 1 { + t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1) + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } +} + +func TestFsnotifyDirOnly(t *testing.T) { + watcher := newWatcher(t) + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Create a file before watching directory + // This should NOT add any events to the fsnotify event queue + testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") + { + var f *os.File + f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + f.Close() + } + + addWatch(t, watcher, testDir) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile") + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var createReceived, modifyReceived, deleteReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) { + t.Logf("event received: %s", event) + if event.Op&Remove == Remove { + deleteReceived.increment() + } + if event.Op&Write == Write { + modifyReceived.increment() + } + if event.Op&Create == Create { + createReceived.increment() + } + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the fsnotify event queue + var f *os.File + f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + + time.Sleep(time.Millisecond) + f.WriteString("data") + f.Sync() + f.Close() + + time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete + + os.Remove(testFile) + os.Remove(testFileAlreadyExists) + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + cReceived := createReceived.value() + if cReceived != 1 { + t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1) + } + mReceived := modifyReceived.value() + if mReceived != 1 { + t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1) + } + dReceived := deleteReceived.value() + if dReceived != 2 { + t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2) + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } +} + +func TestFsnotifyDeleteWatchedDir(t *testing.T) { + watcher := newWatcher(t) + defer watcher.Close() + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Create a file before watching directory + testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") + { + var f *os.File + f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + f.Close() + } + + addWatch(t, watcher, testDir) + + // Add a watch for testFile + addWatch(t, watcher, testFileAlreadyExists) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var deleteReceived counter + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) { + t.Logf("event received: %s", event) + if event.Op&Remove == Remove { + deleteReceived.increment() + } + } else { + t.Logf("unexpected event received: %s", event) + } + } + }() + + os.RemoveAll(testDir) + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + dReceived := deleteReceived.value() + if dReceived < 2 { + t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived) + } +} + +func TestFsnotifySubDir(t *testing.T) { + watcher := newWatcher(t) + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile") + testSubDir := filepath.Join(testDir, "sub") + testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile") + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var createReceived, deleteReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) { + t.Logf("event received: %s", event) + if event.Op&Create == Create { + createReceived.increment() + } + if event.Op&Remove == Remove { + deleteReceived.increment() + } + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + addWatch(t, watcher, testDir) + + // Create sub-directory + if err := os.Mkdir(testSubDir, 0777); err != nil { + t.Fatalf("failed to create test sub-directory: %s", err) + } + + // Create a file + var f *os.File + f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + f.Close() + + // Create a file (Should not see this! we are not watching subdir) + var fs *os.File + fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + fs.Sync() + fs.Close() + + time.Sleep(200 * time.Millisecond) + + // Make sure receive deletes for both file and sub-directory + os.RemoveAll(testSubDir) + os.Remove(testFile1) + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + cReceived := createReceived.value() + if cReceived != 2 { + t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) + } + dReceived := deleteReceived.value() + if dReceived != 2 { + t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2) + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } +} + +func TestFsnotifyRename(t *testing.T) { + watcher := newWatcher(t) + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + addWatch(t, watcher, testDir) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile") + testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var renameReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) { + if event.Op&Rename == Rename { + renameReceived.increment() + } + t.Logf("event received: %s", event) + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the fsnotify event queue + var f *os.File + f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + + f.WriteString("data") + f.Sync() + f.Close() + + // Add a watch for testFile + addWatch(t, watcher, testFile) + + if err := testRename(testFile, testFileRenamed); err != nil { + t.Fatalf("rename failed: %s", err) + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + if renameReceived.value() == 0 { + t.Fatal("fsnotify rename events have not been received after 500 ms") + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } + + os.Remove(testFileRenamed) +} + +func TestFsnotifyRenameToCreate(t *testing.T) { + watcher := newWatcher(t) + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Create directory to get file + testDirFrom := tempMkdir(t) + defer os.RemoveAll(testDirFrom) + + addWatch(t, watcher, testDir) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile") + testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var createReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) { + if event.Op&Create == Create { + createReceived.increment() + } + t.Logf("event received: %s", event) + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the fsnotify event queue + var f *os.File + f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + f.Close() + + if err := testRename(testFile, testFileRenamed); err != nil { + t.Fatalf("rename failed: %s", err) + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + if createReceived.value() == 0 { + t.Fatal("fsnotify create events have not been received after 500 ms") + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } + + os.Remove(testFileRenamed) +} + +func TestFsnotifyRenameToOverwrite(t *testing.T) { + switch runtime.GOOS { + case "plan9", "windows": + t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS) + } + + watcher := newWatcher(t) + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Create directory to get file + testDirFrom := tempMkdir(t) + defer os.RemoveAll(testDirFrom) + + testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile") + testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") + + // Create a file + var fr *os.File + fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + fr.Sync() + fr.Close() + + addWatch(t, watcher, testDir) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + var eventReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testFileRenamed) { + eventReceived.increment() + t.Logf("event received: %s", event) + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the fsnotify event queue + var f *os.File + f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + f.Close() + + if err := testRename(testFile, testFileRenamed); err != nil { + t.Fatalf("rename failed: %s", err) + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + if eventReceived.value() == 0 { + t.Fatal("fsnotify events have not been received after 500 ms") + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(2 * time.Second): + t.Fatal("event stream was not closed after 2 seconds") + } + + os.Remove(testFileRenamed) +} + +func TestRemovalOfWatch(t *testing.T) { + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Create a file before watching directory + testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") + { + var f *os.File + f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + f.Close() + } + + watcher := newWatcher(t) + defer watcher.Close() + + addWatch(t, watcher, testDir) + if err := watcher.Remove(testDir); err != nil { + t.Fatalf("Could not remove the watch: %v\n", err) + } + + go func() { + select { + case ev := <-watcher.Events: + t.Fatalf("We received event: %v\n", ev) + case <-time.After(500 * time.Millisecond): + t.Log("No event received, as expected.") + } + }() + + time.Sleep(200 * time.Millisecond) + // Modify the file outside of the watched dir + f, err := os.Open(testFileAlreadyExists) + if err != nil { + t.Fatalf("Open test file failed: %s", err) + } + f.WriteString("data") + f.Sync() + f.Close() + if err := os.Chmod(testFileAlreadyExists, 0700); err != nil { + t.Fatalf("chmod failed: %s", err) + } + time.Sleep(400 * time.Millisecond) +} + +func TestFsnotifyAttrib(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("attributes don't work on Windows.") + } + + watcher := newWatcher(t) + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Errors { + t.Fatalf("error received: %s", err) + } + }() + + testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile") + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Events + // The modifyReceived counter counts IsModify events that are not IsAttrib, + // and the attribReceived counts IsAttrib events (which are also IsModify as + // a consequence). + var modifyReceived counter + var attribReceived counter + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { + if event.Op&Write == Write { + modifyReceived.increment() + } + if event.Op&Chmod == Chmod { + attribReceived.increment() + } + t.Logf("event received: %s", event) + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the fsnotify event queue + var f *os.File + f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + + f.WriteString("data") + f.Sync() + f.Close() + + // Add a watch for testFile + addWatch(t, watcher, testFile) + + if err := os.Chmod(testFile, 0700); err != nil { + t.Fatalf("chmod failed: %s", err) + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + // Creating/writing a file changes also the mtime, so IsAttrib should be set to true here + time.Sleep(500 * time.Millisecond) + if modifyReceived.value() != 0 { + t.Fatal("received an unexpected modify event when creating a test file") + } + if attribReceived.value() == 0 { + t.Fatal("fsnotify attribute events have not received after 500 ms") + } + + // Modifying the contents of the file does not set the attrib flag (although eg. the mtime + // might have been modified). + modifyReceived.reset() + attribReceived.reset() + + f, err = os.OpenFile(testFile, os.O_WRONLY, 0) + if err != nil { + t.Fatalf("reopening test file failed: %s", err) + } + + f.WriteString("more data") + f.Sync() + f.Close() + + time.Sleep(500 * time.Millisecond) + + if modifyReceived.value() != 1 { + t.Fatal("didn't receive a modify event after changing test file contents") + } + + if attribReceived.value() != 0 { + t.Fatal("did receive an unexpected attrib event after changing test file contents") + } + + modifyReceived.reset() + attribReceived.reset() + + // Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents + // of the file are not changed though) + if err := os.Chmod(testFile, 0600); err != nil { + t.Fatalf("chmod failed: %s", err) + } + + time.Sleep(500 * time.Millisecond) + + if attribReceived.value() != 1 { + t.Fatal("didn't receive an attribute change after 500ms") + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(1e9): + t.Fatal("event stream was not closed after 1 second") + } + + os.Remove(testFile) +} + +func TestFsnotifyClose(t *testing.T) { + watcher := newWatcher(t) + watcher.Close() + + var done int32 + go func() { + watcher.Close() + atomic.StoreInt32(&done, 1) + }() + + time.Sleep(50e6) // 50 ms + if atomic.LoadInt32(&done) == 0 { + t.Fatal("double Close() test failed: second Close() call didn't return") + } + + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + if err := watcher.Add(testDir); err == nil { + t.Fatal("expected error on Watch() after Close(), got nil") + } +} + +func TestFsnotifyFakeSymlink(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("symlinks don't work on Windows.") + } + + watcher := newWatcher(t) + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + var errorsReceived counter + // Receive errors on the error channel on a separate goroutine + go func() { + for errors := range watcher.Errors { + t.Logf("Received error: %s", errors) + errorsReceived.increment() + } + }() + + // Count the CREATE events received + var createEventsReceived, otherEventsReceived counter + go func() { + for ev := range watcher.Events { + t.Logf("event received: %s", ev) + if ev.Op&Create == Create { + createEventsReceived.increment() + } else { + otherEventsReceived.increment() + } + } + }() + + addWatch(t, watcher, testDir) + + if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil { + t.Fatalf("Failed to create bogus symlink: %s", err) + } + t.Logf("Created bogus symlink") + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + + // Should not be error, just no events for broken links (watching nothing) + if errorsReceived.value() > 0 { + t.Fatal("fsnotify errors have been received.") + } + if otherEventsReceived.value() > 0 { + t.Fatal("fsnotify other events received on the broken link") + } + + // Except for 1 create event (for the link itself) + if createEventsReceived.value() == 0 { + t.Fatal("fsnotify create events were not received after 500 ms") + } + if createEventsReceived.value() > 1 { + t.Fatal("fsnotify more create events received than expected") + } + + // Try closing the fsnotify instance + t.Log("calling Close()") + watcher.Close() +} + +func TestCyclicSymlink(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("symlinks don't work on Windows.") + } + + watcher := newWatcher(t) + + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + link := path.Join(testDir, "link") + if err := os.Symlink(".", link); err != nil { + t.Fatalf("could not make symlink: %v", err) + } + addWatch(t, watcher, testDir) + + var createEventsReceived counter + go func() { + for ev := range watcher.Events { + if ev.Op&Create == Create { + createEventsReceived.increment() + } + } + }() + + if err := os.Remove(link); err != nil { + t.Fatalf("Error removing link: %v", err) + } + + // It would be nice to be able to expect a delete event here, but kqueue has + // no way for us to get events on symlinks themselves, because opening them + // opens an fd to the file to which they point. + + if err := ioutil.WriteFile(link, []byte("foo"), 0700); err != nil { + t.Fatalf("could not make symlink: %v", err) + } + + // We expect this event to be received almost immediately, but let's wait 500 ms to be sure + time.Sleep(500 * time.Millisecond) + + if got := createEventsReceived.value(); got == 0 { + t.Errorf("want at least 1 create event got %v", got) + } + + watcher.Close() +} + +// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race. +// See https://codereview.appspot.com/103300045/ +// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race +func TestConcurrentRemovalOfWatch(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip("regression test for race only present on darwin") + } + + // Create directory to watch + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + // Create a file before watching directory + testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") + { + var f *os.File + f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + f.Sync() + f.Close() + } + + watcher := newWatcher(t) + defer watcher.Close() + + addWatch(t, watcher, testDir) + + // Test that RemoveWatch can be invoked concurrently, with no data races. + removed1 := make(chan struct{}) + go func() { + defer close(removed1) + watcher.Remove(testDir) + }() + removed2 := make(chan struct{}) + go func() { + close(removed2) + watcher.Remove(testDir) + }() + <-removed1 + <-removed2 +} + +func TestClose(t *testing.T) { + // Regression test for #59 bad file descriptor from Close + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + watcher := newWatcher(t) + if err := watcher.Add(testDir); err != nil { + t.Fatalf("Expected no error on Add, got %v", err) + } + err := watcher.Close() + if err != nil { + t.Fatalf("Expected no error on Close, got %v.", err) + } +} + +// TestRemoveWithClose tests if one can handle Remove events and, at the same +// time, close Watcher object without any data races. +func TestRemoveWithClose(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + const fileN = 200 + tempFiles := make([]string, 0, fileN) + for i := 0; i < fileN; i++ { + tempFiles = append(tempFiles, tempMkFile(t, testDir)) + } + watcher := newWatcher(t) + if err := watcher.Add(testDir); err != nil { + t.Fatalf("Expected no error on Add, got %v", err) + } + startC, stopC := make(chan struct{}), make(chan struct{}) + errC := make(chan error) + go func() { + for { + select { + case <-watcher.Errors: + case <-watcher.Events: + case <-stopC: + return + } + } + }() + go func() { + <-startC + for _, fileName := range tempFiles { + os.Remove(fileName) + } + }() + go func() { + <-startC + errC <- watcher.Close() + }() + close(startC) + defer close(stopC) + if err := <-errC; err != nil { + t.Fatalf("Expected no error on Close, got %v.", err) + } +} + +func testRename(file1, file2 string) error { + switch runtime.GOOS { + case "windows", "plan9": + return os.Rename(file1, file2) + default: + cmd := exec.Command("mv", file1, file2) + return cmd.Run() + } +} diff --git a/vendor/gopkg.in/fsnotify.v1/kqueue.go b/vendor/gopkg.in/fsnotify.v1/kqueue.go index c2b4acb18..86e76a3d6 100644 --- a/vendor/gopkg.in/fsnotify.v1/kqueue.go +++ b/vendor/gopkg.in/fsnotify.v1/kqueue.go @@ -22,7 +22,7 @@ import ( type Watcher struct { Events chan Event Errors chan error - done chan bool // Channel for sending a "quit message" to the reader goroutine + done chan struct{} // Channel for sending a "quit message" to the reader goroutine kq int // File descriptor (as returned by the kqueue() syscall). @@ -56,7 +56,7 @@ func NewWatcher() (*Watcher, error) { externalWatches: make(map[string]bool), Events: make(chan Event), Errors: make(chan error), - done: make(chan bool), + done: make(chan struct{}), } go w.readEvents() @@ -71,10 +71,8 @@ func (w *Watcher) Close() error { return nil } w.isClosed = true - w.mu.Unlock() // copy paths to remove while locked - w.mu.Lock() var pathsToRemove = make([]string, 0, len(w.watches)) for name := range w.watches { pathsToRemove = append(pathsToRemove, name) @@ -82,15 +80,12 @@ func (w *Watcher) Close() error { w.mu.Unlock() // unlock before calling Remove, which also locks - var err error for _, name := range pathsToRemove { - if e := w.Remove(name); e != nil && err == nil { - err = e - } + w.Remove(name) } - // Send "quit" message to the reader goroutine: - w.done <- true + // send a "quit" message to the reader goroutine + close(w.done) return nil } @@ -266,17 +261,12 @@ func (w *Watcher) addWatch(name string, flags uint32) (string, error) { func (w *Watcher) readEvents() { eventBuffer := make([]unix.Kevent_t, 10) +loop: for { // See if there is a message on the "done" channel select { case <-w.done: - err := unix.Close(w.kq) - if err != nil { - w.Errors <- err - } - close(w.Events) - close(w.Errors) - return + break loop default: } @@ -284,7 +274,11 @@ func (w *Watcher) readEvents() { kevents, err := read(w.kq, eventBuffer, &keventWaitTime) // EINTR is okay, the syscall was interrupted before timeout expired. if err != nil && err != unix.EINTR { - w.Errors <- err + select { + case w.Errors <- err: + case <-w.done: + break loop + } continue } @@ -319,8 +313,12 @@ func (w *Watcher) readEvents() { if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { w.sendDirectoryChangeEvents(event.Name) } else { - // Send the event on the Events channel - w.Events <- event + // Send the event on the Events channel. + select { + case w.Events <- event: + case <-w.done: + break loop + } } if event.Op&Remove == Remove { @@ -352,6 +350,18 @@ func (w *Watcher) readEvents() { kevents = kevents[1:] } } + + // cleanup + err := unix.Close(w.kq) + if err != nil { + // only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors. + select { + case w.Errors <- err: + default: + } + } + close(w.Events) + close(w.Errors) } // newEvent returns an platform-independent Event based on kqueue Fflags. @@ -407,7 +417,11 @@ func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { // Get all files files, err := ioutil.ReadDir(dirPath) if err != nil { - w.Errors <- err + select { + case w.Errors <- err: + case <-w.done: + return + } } // Search for new files @@ -428,7 +442,11 @@ func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInf w.mu.Unlock() if !doesExist { // Send create event - w.Events <- newCreateEvent(filePath) + select { + case w.Events <- newCreateEvent(filePath): + case <-w.done: + return + } } // like watchDirectoryFiles (but without doing another ReadDir) |