diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/podman/commands.go | 150 | ||||
-rw-r--r-- | cmd/podman/commands_remoteclient.go | 21 | ||||
-rw-r--r-- | cmd/podman/container.go | 43 | ||||
-rw-r--r-- | cmd/podman/create.go | 20 | ||||
-rw-r--r-- | cmd/podman/exists.go | 10 | ||||
-rw-r--r-- | cmd/podman/formats/formats.go | 13 | ||||
-rw-r--r-- | cmd/podman/history.go | 8 | ||||
-rw-r--r-- | cmd/podman/image.go | 18 | ||||
-rw-r--r-- | cmd/podman/images.go | 21 | ||||
-rw-r--r-- | cmd/podman/images_prune.go | 29 | ||||
-rw-r--r-- | cmd/podman/info.go | 29 | ||||
-rw-r--r-- | cmd/podman/inspect.go | 49 | ||||
-rw-r--r-- | cmd/podman/main.go | 119 | ||||
-rw-r--r-- | cmd/podman/mount.go | 34 | ||||
-rw-r--r-- | cmd/podman/pause.go | 2 | ||||
-rw-r--r-- | cmd/podman/ps.go | 41 | ||||
-rw-r--r-- | cmd/podman/pull.go | 8 | ||||
-rw-r--r-- | cmd/podman/rmi.go | 17 | ||||
-rw-r--r-- | cmd/podman/shared/container.go | 16 | ||||
-rw-r--r-- | cmd/podman/tag.go | 6 | ||||
-rw-r--r-- | cmd/podman/umount.go | 67 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 37 | ||||
-rw-r--r-- | cmd/podman/version.go | 20 |
23 files changed, 467 insertions, 311 deletions
diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go new file mode 100644 index 000000000..718717009 --- /dev/null +++ b/cmd/podman/commands.go @@ -0,0 +1,150 @@ +// +build !remoteclient + +package main + +import "github.com/urfave/cli" + +func getAppCommands() []cli.Command { + return []cli.Command{ + attachCommand, + commitCommand, + buildCommand, + createCommand, + diffCommand, + execCommand, + exportCommand, + importCommand, + 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() []cli.Command { + return []cli.Command{ + buildCommand, + importCommand, + loadCommand, + pullCommand, + saveCommand, + trustCommand, + signCommand, + } +} + +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 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", + }, + } +} diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go new file mode 100644 index 000000000..6701e14a1 --- /dev/null +++ b/cmd/podman/commands_remoteclient.go @@ -0,0 +1,21 @@ +// +build remoteclient + +package main + +import "github.com/urfave/cli" + +func getAppCommands() []cli.Command { + return []cli.Command{} +} + +func getImageSubCommands() []cli.Command { + return []cli.Command{} +} + +func getContainerSubCommands() []cli.Command { + return []cli.Command{} +} + +func getMainAppFlags() []cli.Flag { + return []cli.Flag{} +} diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 4bb6f287a..acbcbb644 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -1,52 +1,29 @@ package main import ( + "sort" + "github.com/urfave/cli" ) var ( - subCommands = []cli.Command{ - attachCommand, - checkpointCommand, - cleanupCommand, - containerExistsCommand, - commitCommand, - createCommand, - diffCommand, - execCommand, - exportCommand, + containerSubCommands = []cli.Command{ inspectCommand, - killCommand, - logsCommand, - psCommand, - mountCommand, - pauseCommand, - portCommand, - pruneContainersCommand, - refreshCommand, - restartCommand, - restoreCommand, - rmCommand, - runCommand, - runlabelCommand, - startCommand, - statsCommand, - stopCommand, - topCommand, - umountCommand, - unpauseCommand, - // updateCommand, - waitCommand, } - containerDescription = "Manage containers" containerCommand = cli.Command{ Name: "container", Usage: "Manage Containers", Description: containerDescription, ArgsUsage: "", - Subcommands: subCommands, + Subcommands: getContainerSubCommandsSorted(), UseShortOptionHandling: true, OnUsageError: usageErrorHandler, } ) + +func getContainerSubCommandsSorted() []cli.Command { + containerSubCommands = append(containerSubCommands, getContainerSubCommands()...) + sort.Sort(commandSortedAlpha{containerSubCommands}) + return containerSubCommands +} diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 1aa3425a5..c56efa153 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -128,7 +129,12 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container var data *inspect.ImageData = nil if rootfs == "" && !rootless.SkipStorageSetup() { - newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false) + var writer io.Writer + if !c.Bool("quiet") { + writer = os.Stderr + } + + newImage, err := runtime.ImageRuntime().New(ctx, c.Args()[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false) if err != nil { return nil, nil, err } @@ -173,7 +179,11 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { if err != nil { return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) } if config.IpcMode.IsHost() { @@ -183,7 +193,11 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { if err != nil { return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) } for _, opt := range securityOpts { diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go index bd1bc24ec..a7601aaa2 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -67,12 +67,12 @@ func imageExistsCmd(c *cli.Context) error { if len(args) > 1 || len(args) < 1 { return errors.New("you may only check for the existence of one image at a time") } - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } - defer localRuntime.Runtime.Shutdown(false) - if _, err := localRuntime.NewImageFromLocal(args[0]); err != nil { + defer runtime.Shutdown(false) + if _, err := runtime.NewImageFromLocal(args[0]); err != nil { //TODO we need to ask about having varlink defined errors exposed //so we can reuse them if errors.Cause(err) == image.ErrNoSuchImage || err.Error() == "io.podman.ImageNotFound" { @@ -88,13 +88,13 @@ func containerExistsCmd(c *cli.Context) error { 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 := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) if _, err := runtime.LookupContainer(args[0]); err != nil { - if errors.Cause(err) == libpod.ErrNoSuchCtr { + if errors.Cause(err) == libpod.ErrNoSuchCtr || err.Error() == "io.podman.ContainerNotFound" { os.Exit(1) } return err diff --git a/cmd/podman/formats/formats.go b/cmd/podman/formats/formats.go index 3da0ea385..c454c39bd 100644 --- a/cmd/podman/formats/formats.go +++ b/cmd/podman/formats/formats.go @@ -20,6 +20,8 @@ const ( JSONString = "json" // IDString const to save on duplicates for Go templates IDString = "{{.ID}}" + + parsingErrorStr = "Template parsing error" ) // Writer interface for outputs @@ -96,7 +98,7 @@ func (t StdoutTemplateArray) Out() error { t.Template = strings.Replace(strings.TrimSpace(t.Template[5:]), " ", "\t", -1) headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template) if err != nil { - return errors.Wrapf(err, "Template parsing error") + return errors.Wrapf(err, parsingErrorStr) } err = headerTmpl.Execute(w, t.Fields) if err != nil { @@ -107,13 +109,12 @@ func (t StdoutTemplateArray) Out() error { t.Template = strings.Replace(t.Template, " ", "\t", -1) tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template) if err != nil { - return errors.Wrapf(err, "Template parsing error") + return errors.Wrapf(err, parsingErrorStr) } - for i, img := range t.Output { + for i, raw := range t.Output { basicTmpl := tmpl.Funcs(basicFunctions) - err = basicTmpl.Execute(w, img) - if err != nil { - return err + if err := basicTmpl.Execute(w, raw); err != nil { + return errors.Wrapf(err, parsingErrorStr) } if i != len(t.Output)-1 { fmt.Fprintln(w, "") diff --git a/cmd/podman/history.go b/cmd/podman/history.go index 7c8c619c8..8a9b6cd94 100644 --- a/cmd/podman/history.go +++ b/cmd/podman/history.go @@ -7,9 +7,9 @@ import ( "time" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" - units "github.com/docker/go-units" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -72,7 +72,7 @@ func historyCmd(c *cli.Context) error { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -88,7 +88,7 @@ func historyCmd(c *cli.Context) error { return errors.Errorf("podman history takes at most 1 argument") } - image, err := runtime.ImageRuntime().NewFromLocal(args[0]) + image, err := runtime.NewImageFromLocal(args[0]) if err != nil { return err } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index 557fc1056..a51a90b0e 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -1,36 +1,36 @@ package main import ( + "sort" + "github.com/urfave/cli" ) var ( imageSubCommands = []cli.Command{ - buildCommand, historyCommand, - importCommand, imageExistsCommand, inspectCommand, - loadCommand, lsImagesCommand, pruneImagesCommand, pullCommand, - pushCommand, rmImageCommand, - saveCommand, tagCommand, - trustCommand, - signCommand, } - imageDescription = "Manage images" imageCommand = cli.Command{ Name: "image", Usage: "Manage images", Description: imageDescription, ArgsUsage: "", - Subcommands: imageSubCommands, + Subcommands: getImageSubCommandsSorted(), UseShortOptionHandling: true, OnUsageError: usageErrorHandler, } ) + +func getImageSubCommandsSorted() []cli.Command { + imageSubCommands = append(imageSubCommands, getImageSubCommands()...) + sort.Sort(commandSortedAlpha{imageSubCommands}) + return imageSubCommands +} diff --git a/cmd/podman/images.go b/cmd/podman/images.go index 8b8ce78bd..d4f405975 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -2,8 +2,6 @@ package main import ( "context" - "github.com/containers/libpod/cmd/podman/imagefilters" - "github.com/containers/libpod/libpod/adapter" "reflect" "sort" "strings" @@ -11,6 +9,8 @@ import ( "unicode" "github.com/containers/libpod/cmd/podman/formats" + "github.com/containers/libpod/cmd/podman/imagefilters" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/libpod/libpod/image" "github.com/docker/go-units" "github.com/opencontainers/go-digest" @@ -152,13 +152,13 @@ func imagesCmd(c *cli.Context) error { return err } - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "Could not get runtime") } - defer localRuntime.Runtime.Shutdown(false) + defer runtime.Shutdown(false) if len(c.Args()) == 1 { - newImage, err = localRuntime.NewImageFromLocal(c.Args().Get(0)) + newImage, err = runtime.NewImageFromLocal(c.Args().Get(0)) if err != nil { return err } @@ -171,7 +171,7 @@ func imagesCmd(c *cli.Context) error { ctx := getContext() if len(c.StringSlice("filter")) > 0 || newImage != nil { - filterFuncs, err = CreateFilterFuncs(ctx, localRuntime, c, newImage) + filterFuncs, err = CreateFilterFuncs(ctx, runtime, c, newImage) if err != nil { return err } @@ -188,14 +188,7 @@ func imagesCmd(c *cli.Context) error { } opts.outputformat = opts.setOutputFormat() - /* - podman does not implement --all for images - - intermediate images are only generated during the build process. they are - children to the image once built. until buildah supports caching builds, - it will not generate these intermediate images. - */ - images, err := localRuntime.GetImages() + images, err := runtime.GetImages() if err != nil { return errors.Wrapf(err, "unable to get images") } diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 06879e02d..aef387732 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -13,33 +13,36 @@ var ( 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 = cli.Command{ Name: "prune", Usage: "Remove unused images", Description: pruneImagesDescription, Action: pruneImagesCmd, OnUsageError: usageErrorHandler, + Flags: pruneImageFlags, } ) func pruneImagesCmd(c *cli.Context) error { - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - pruneImages, err := runtime.ImageRuntime().GetPruneImages() - if err != nil { - return err - } - - for _, i := range pruneImages { - if err := i.Remove(true); err != nil { - return errors.Wrapf(err, "failed to remove %s", i.ID()) + // 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")) + if len(pruneCids) > 0 { + for _, cid := range pruneCids { + fmt.Println(cid) } - fmt.Println(i.ID()) } - return nil + return err } diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 4b80f94db..f5f91b603 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -1,11 +1,13 @@ package main import ( - "github.com/containers/libpod/libpod/adapter" - "runtime" + "fmt" + rt "runtime" "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" ) @@ -27,7 +29,7 @@ var ( Usage: "display additional debug information", }, cli.StringFlag{ - Name: "format", + Name: "format, f", Usage: "Change the output format to JSON or a Go template", }, } @@ -38,21 +40,26 @@ func infoCmd(c *cli.Context) error { return err } info := map[string]interface{}{} + remoteClientInfo := map[string]interface{}{} - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } - defer localRuntime.Runtime.Shutdown(false) + defer runtime.Shutdown(false) - infoArr, err := localRuntime.Runtime.Info() + infoArr, err := runtime.Info() if err != nil { return errors.Wrapf(err, "error getting info") } + if runtime.Remote { + remoteClientInfo["RemoteAPI Version"] = version.RemoteAPIVersion + remoteClientInfo["Podman Version"] = version.Version + remoteClientInfo["OS Arch"] = fmt.Sprintf("%s/%s", rt.GOOS, rt.GOARCH) + infoArr = append(infoArr, libpod.InfoData{Type: "client", Data: remoteClientInfo}) + } - // TODO This is no a problem child because we don't know if we should add information - // TODO about the client or the backend. Only do for traditional podman for now. - if !localRuntime.Remote && c.Bool("debug") { + if !runtime.Remote && c.Bool("debug") { debugInfo := debugInfo(c) infoArr = append(infoArr, libpod.InfoData{Type: "debug", Data: debugInfo}) } @@ -80,8 +87,8 @@ func infoCmd(c *cli.Context) error { // top-level "debug" info func debugInfo(c *cli.Context) map[string]interface{} { info := map[string]interface{}{} - info["compiler"] = runtime.Compiler - info["go version"] = runtime.Version() + info["compiler"] = rt.Compiler + info["go version"] = rt.Version() info["podman version"] = c.App.Version version, _ := libpod.GetVersion() info["git commit"] = version.GitCommit diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 6ffcde55f..1346da9fb 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -2,12 +2,13 @@ package main import ( "context" + "encoding/json" "strings" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" + "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" @@ -31,7 +32,7 @@ var ( Usage: "Change the output format to a Go template", }, cli.BoolFlag{ - Name: "size", + Name: "size, s", Usage: "Display total file size if the type is container", }, LatestFlag, @@ -39,7 +40,7 @@ var ( 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: "Displays the configuration of a container or image", + Usage: "Display the configuration of a container or image", Description: inspectDescription, Flags: sortFlags(inspectFlags), Action: inspectCmd, @@ -63,7 +64,7 @@ func inspectCmd(c *cli.Context) error { return err } - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -87,6 +88,9 @@ func inspectCmd(c *cli.Context) error { } inspectedObjects, iterateErr := iterateInput(getContext(), c, args, runtime, inspectType) + if iterateErr != nil { + return iterateErr + } var out formats.Writer if outputFormat != "" && outputFormat != formats.JSONString { @@ -97,12 +101,11 @@ func inspectCmd(c *cli.Context) error { out = formats.JSONStructArray{Output: inspectedObjects} } - formats.Writer(out).Out() - return iterateErr + return formats.Writer(out).Out() } // 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 *libpod.Runtime, inspectType string) ([]interface{}, error) { +func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *adapter.LocalRuntime, inspectType string) ([]interface{}, error) { var ( data interface{} inspectedItems []interface{} @@ -122,13 +125,18 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } - data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) + artifact, err := getArtifact(ctr) + if inspectError != nil { + inspectError = err + break + } + data, err = shared.GetCtrInspectInfo(ctr.Config(), libpodInspectData, artifact) if err != nil { inspectError = errors.Wrapf(err, "error parsing container data %q", ctr.ID()) break } case inspectTypeImage: - image, err := runtime.ImageRuntime().NewFromLocal(input) + image, err := runtime.NewImageFromLocal(input) if err != nil { inspectError = errors.Wrapf(err, "error getting image %q", input) break @@ -141,7 +149,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l case inspectAll: ctr, err := runtime.LookupContainer(input) if err != nil { - image, err := runtime.ImageRuntime().NewFromLocal(input) + image, err := runtime.NewImageFromLocal(input) if err != nil { inspectError = errors.Wrapf(err, "error getting image %q", input) break @@ -157,7 +165,12 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID()) break } - data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData) + artifact, inspectError := getArtifact(ctr) + if inspectError != nil { + inspectError = err + break + } + data, err = shared.GetCtrInspectInfo(ctr.Config(), libpodInspectData, artifact) if err != nil { inspectError = errors.Wrapf(err, "error parsing container data %s", ctr.ID()) break @@ -170,3 +183,15 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l } return inspectedItems, inspectError } + +func getArtifact(ctr *adapter.Container) (*cc.CreateConfig, error) { + var createArtifact cc.CreateConfig + artifact, err := ctr.GetArtifact("create-config") + if err != nil { + return nil, err + } + if err := json.Unmarshal(artifact, &createArtifact); err != nil { + return nil, err + } + return &createArtifact, nil +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index ce60bbfb7..c10590006 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "runtime/pprof" + "sort" "syscall" "github.com/containers/libpod/libpod" @@ -47,6 +48,28 @@ var cmdsNotRequiringRootless = map[string]bool{ "top": 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 +} + +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] } + +type flagSortedAlpha struct{ flagSorted } + +func (a flagSortedAlpha) Less(i, j int) bool { + return a.flagSorted[i].GetName() < a.flagSorted[j].GetName() +} + func main() { debug := false cpuProfile := false @@ -64,52 +87,21 @@ func main() { app.Version = version.Version app.Commands = []cli.Command{ - attachCommand, - commitCommand, containerCommand, - buildCommand, - createCommand, - diffCommand, - execCommand, - exportCommand, historyCommand, imageCommand, imagesCommand, - importCommand, infoCommand, inspectCommand, - killCommand, - kubeCommand, - loadCommand, - loginCommand, - logoutCommand, - logsCommand, - mountCommand, - pauseCommand, - psCommand, - podCommand, - portCommand, pullCommand, - pushCommand, - playCommand, - restartCommand, - rmCommand, rmiCommand, - runCommand, - saveCommand, - searchCommand, - startCommand, - statsCommand, - stopCommand, tagCommand, - topCommand, - umountCommand, - unpauseCommand, versionCommand, - volumeCommand, - waitCommand, } + app.Commands = append(app.Commands, getAppCommands()...) + sort.Sort(commandSortedAlpha{app.Commands}) + if varlinkCommand != nil { app.Commands = append(app.Commands, *varlinkCommand) } @@ -192,79 +184,28 @@ func main() { } app.Flags = []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: "config, c", Usage: "path of a libpod config file detailing container server configuration options", Hidden: true, }, cli.StringFlag{ - Name: "conmon", - Usage: "path of the conmon binary", - }, - cli.StringFlag{ Name: "cpu-profile", Usage: "path for the cpu profiling results", }, 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: "log-level", Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic", Value: "error", }, 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: "tmpdir", Usage: "path to the tmp directory", }, - 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", - }, } + + app.Flags = append(app.Flags, getMainAppFlags()...) + sort.Sort(flagSortedAlpha{app.Flags}) + // Check if /etc/containers/registries.conf exists when running in // in a local environment. CheckForRegistries() diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index c91115597..86a6b2ad1 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -24,13 +24,18 @@ var ( mountFlags = []cli.Flag{ cli.BoolFlag{ - Name: "notruncate", - Usage: "do not truncate output", + Name: "all, a", + Usage: "Mount all containers", }, cli.StringFlag{ Name: "format", Usage: "Change the output format to Go template", }, + cli.BoolFlag{ + Name: "notruncate", + Usage: "do not truncate output", + }, + LatestFlag, } mountCommand = cli.Command{ Name: "mount", @@ -80,20 +85,31 @@ func mountCmd(c *cli.Context) error { } } + if c.Bool("all") && c.Bool("latest") { + return errors.Errorf("--all and --latest cannot be used together") + } + + mountContainers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(mountContainers) == 0 { + return err + } + fmt.Println(err.Error()) + } + formats := map[string]bool{ "": true, of.JSONString: true, } - args := c.Args() json := c.String("format") == of.JSONString if !formats[c.String("format")] { return errors.Errorf("%q is not a supported format", c.String("format")) } var lastError error - if len(args) > 0 { - for _, name := range args { + if len(mountContainers) > 0 { + for _, ctr := range mountContainers { if json { if lastError != nil { logrus.Error(lastError) @@ -101,14 +117,6 @@ func mountCmd(c *cli.Context) error { lastError = errors.Wrapf(err, "json option cannot be used with a container id") continue } - ctr, err := runtime.LookupContainer(name) - if err != nil { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", name) - continue - } mountPoint, err := ctr.Mount() if err != nil { if lastError != nil { diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index fcb2f3cb8..9da6abf4b 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -25,7 +25,7 @@ var ( ` pauseCommand = cli.Command{ Name: "pause", - Usage: "Pauses all the processes in one or more containers", + Usage: "Pause all the processes in one or more containers", Description: pauseDescription, Flags: pauseFlags, Action: pauseCmd, diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index 0ad3f4c73..1708c671c 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -606,19 +606,50 @@ func portsToString(ports []ocicni.PortMapping) string { } func printFormat(format string, containers []shared.PsContainerOutput) error { - out := template.New("output") - out, err := out.Parse(format + "\n") + // return immediately if no containers are present + if len(containers) == 0 { + return nil + } + + // Use a tabwriter to align column format + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + + // Make a map of the field names for the headers + headerNames := make(map[string]string) + v := reflect.ValueOf(containers[0]) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + headerNames[t.Field(i).Name] = t.Field(i).Name + } + + // Spit out the header if "table" is present in the format + if strings.HasPrefix(format, "table") { + hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1) + format = hformat + headerTmpl, err := template.New("header").Parse(hformat) + if err != nil { + return err + } + if err := headerTmpl.Execute(w, headerNames); err != nil { + return err + } + fmt.Fprintln(w, "") + } + // Spit out the data rows now + dataTmpl, err := template.New("data").Parse(format) if err != nil { return err } + for _, container := range containers { - if err := out.Execute(os.Stdout, container); err != nil { + if err := dataTmpl.Execute(w, container); err != nil { return err } - + fmt.Fprintln(w, "") } - return nil + // Flush the writer + return w.Flush() } func dumpJSON(containers []shared.PsContainerOutput) error { diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 47130805e..2a78d0c54 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -9,7 +9,7 @@ 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/libpodruntime" + "github.com/containers/libpod/libpod/adapter" image2 "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" @@ -64,7 +64,7 @@ specified, the image with the 'latest' tag (if it exists) is pulled // 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 := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -116,14 +116,14 @@ func pullCmd(c *cli.Context) error { if err != nil { return errors.Wrapf(err, "error parsing %q", image) } - newImage, err := runtime.ImageRuntime().LoadFromArchiveReference(getContext(), srcRef, c.String("signature-policy"), writer) + newImage, err := runtime.LoadFromArchiveReference(getContext(), srcRef, c.String("signature-policy"), 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.ImageRuntime().New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true) + newImage, err := runtime.New(getContext(), image, c.String("signature-policy"), authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, true) if err != nil { return errors.Wrapf(err, "error pulling image %q", image) } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index 5e8ac81a2..c904f2f92 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -4,8 +4,7 @@ import ( "fmt" "os" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/libpod/adapter" "github.com/containers/storage" "github.com/pkg/errors" "github.com/urfave/cli" @@ -25,7 +24,7 @@ var ( } rmiCommand = cli.Command{ Name: "rmi", - Usage: "Removes one or more images from local storage", + Usage: "Remove one or more images from local storage", Description: rmiDescription, Action: rmiCmd, ArgsUsage: "IMAGE-NAME-OR-ID [...]", @@ -58,7 +57,7 @@ func rmiCmd(c *cli.Context) error { return err } removeAll := c.Bool("all") - runtime, err := libpodruntime.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -74,7 +73,7 @@ func rmiCmd(c *cli.Context) error { images := args[:] - removeImage := func(img *image.Image) { + removeImage := func(img *adapter.ContainerImage) { deleted = true msg, deleteErr = runtime.RemoveImage(ctx, img, c.Bool("force")) if deleteErr != nil { @@ -91,8 +90,8 @@ func rmiCmd(c *cli.Context) error { } if removeAll { - var imagesToDelete []*image.Image - imagesToDelete, err = runtime.ImageRuntime().GetImages() + var imagesToDelete []*adapter.ContainerImage + imagesToDelete, err = runtime.GetImages() if err != nil { return errors.Wrapf(err, "unable to query local images") } @@ -112,7 +111,7 @@ func rmiCmd(c *cli.Context) error { removeImage(i) } lastNumberofImages = len(imagesToDelete) - imagesToDelete, err = runtime.ImageRuntime().GetImages() + imagesToDelete, err = runtime.GetImages() if err != nil { return err } @@ -130,7 +129,7 @@ func rmiCmd(c *cli.Context) error { // See https://github.com/containers/libpod/issues/930 as // an exemplary inconsistency issue. for _, i := range images { - newImage, err := runtime.ImageRuntime().NewFromLocal(i) + newImage, err := runtime.NewImageFromLocal(i) if err != nil { fmt.Fprintln(os.Stderr, err) continue diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index a904ef75a..9040c4a5c 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -2,7 +2,6 @@ package shared import ( "context" - "encoding/json" "fmt" "github.com/google/shlex" "io" @@ -446,8 +445,7 @@ func getStrFromSquareBrackets(cmd string) string { // GetCtrInspectInfo takes container inspect data and collects all its info into a ContainerData // structure for inspection related methods -func GetCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerInspectData) (*inspect.ContainerData, error) { - config := ctr.Config() +func GetCtrInspectInfo(config *libpod.ContainerConfig, ctrInspectData *inspect.ContainerInspectData, createArtifact *cc.CreateConfig) (*inspect.ContainerData, error) { spec := config.Spec cpus, mems, period, quota, realtimePeriod, realtimeRuntime, shares := getCPUInfo(spec) @@ -456,16 +454,6 @@ func GetCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerI pidsLimit := getPidsInfo(spec) cgroup := getCgroup(spec) - var createArtifact cc.CreateConfig - artifact, err := ctr.GetArtifact("create-config") - if err == nil { - if err := json.Unmarshal(artifact, &createArtifact); err != nil { - return nil, err - } - } else { - logrus.Errorf("couldn't get some inspect information, error getting artifact %q: %v", ctr.ID(), err) - } - data := &inspect.ContainerData{ ctrInspectData, &inspect.HostConfig{ @@ -493,7 +481,7 @@ func GetCtrInspectInfo(ctr *libpod.Container, ctrInspectData *inspect.ContainerI PidsLimit: pidsLimit, Privileged: config.Privileged, ReadonlyRootfs: spec.Root.Readonly, - Runtime: ctr.RuntimeName(), + Runtime: config.OCIRuntime, NetworkMode: string(createArtifact.NetMode), IpcMode: string(createArtifact.IpcMode), Cgroup: cgroup, diff --git a/cmd/podman/tag.go b/cmd/podman/tag.go index c99e5d173..d19cf69a2 100644 --- a/cmd/podman/tag.go +++ b/cmd/podman/tag.go @@ -23,13 +23,13 @@ func tagCmd(c *cli.Context) error { if len(args) < 2 { return errors.Errorf("image name and at least one new name must be specified") } - localRuntime, err := adapter.GetRuntime(c) + runtime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not create runtime") } - defer localRuntime.Runtime.Shutdown(false) + defer runtime.Shutdown(false) - newImage, err := localRuntime.NewImageFromLocal(args[0]) + newImage, err := runtime.NewImageFromLocal(args[0]) if err != nil { return err } diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go index 24f0f178b..42f169228 100644 --- a/cmd/podman/umount.go +++ b/cmd/podman/umount.go @@ -21,6 +21,7 @@ var ( Name: "force, f", Usage: "force the complete umount all of the currently mounted containers", }, + LatestFlag, } description = ` @@ -33,7 +34,7 @@ An unmount can be forced with the --force flag. umountCommand = cli.Command{ Name: "umount", Aliases: []string{"unmount"}, - Usage: "Unmounts working container's root filesystem", + Usage: "Unmount working container's root filesystem", Description: description, Flags: sortFlags(umountFlags), Action: umountCmd, @@ -51,59 +52,37 @@ func umountCmd(c *cli.Context) error { force := c.Bool("force") umountAll := c.Bool("all") - args := c.Args() - if len(args) == 0 && !umountAll { - return errors.Errorf("container ID must be specified") + if err := checkAllAndLatest(c); err != nil { + return err } - if len(args) > 0 && umountAll { - return errors.Errorf("when using the --all switch, you may not pass any container IDs") + + containers, err := getAllOrLatestContainers(c, runtime, -1, "all") + if err != nil { + if len(containers) == 0 { + return err + } + fmt.Println(err.Error()) } umountContainerErrStr := "error unmounting container" var lastError error - if len(args) > 0 { - for _, name := range args { - ctr, err := runtime.LookupContainer(name) - if err != nil { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, name) - continue - } - - if err = ctr.Unmount(force); err != nil { - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, name) - continue - } - fmt.Printf("%s\n", ctr.ID()) - } - } else { - containers, err := runtime.GetContainers() - if err != nil { - return errors.Wrapf(err, "error reading Containers") + for _, ctr := range containers { + ctrState, err := ctr.State() + if ctrState == libpod.ContainerStateRunning || err != nil { + continue } - for _, ctr := range containers { - ctrState, err := ctr.State() - if ctrState == libpod.ContainerStateRunning || err != nil { - continue - } - if err = ctr.Unmount(force); err != nil { - if umountAll && errors.Cause(err) == storage.ErrLayerNotMounted { - continue - } - if lastError != nil { - logrus.Error(lastError) - } - lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, ctr.ID()) + if err = ctr.Unmount(force); err != nil { + if umountAll && errors.Cause(err) == storage.ErrLayerNotMounted { continue } - fmt.Printf("%s\n", ctr.ID()) + if lastError != nil { + logrus.Error(lastError) + } + lastError = errors.Wrapf(err, "%s %s", umountContainerErrStr, ctr.ID()) + continue } + fmt.Printf("%s\n", ctr.ID()) } return lastError } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index a3e8c050e..8b02057a1 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -9,7 +9,8 @@ type Version ( go_version: string, git_commit: string, built: int, - os_arch: string + os_arch: string, + remote_api_version: int ) type NotImplemented ( @@ -61,7 +62,7 @@ type ImageSearch ( star_count: int ) -# ListContainer is the returned struct for an individual container +# ListContainerData is the returned struct for an individual container type ListContainerData ( id: string, image: string, @@ -986,17 +987,15 @@ method ContainerRunlabel(runlabel: Runlabel) -> () # of strings # #### Example # ~~~ -# $ varlink call -m unix:/run/podman/io.podman/io.podman.ListContainerMounts +# $ varlink call unix:/run/podman/io.podman/io.podman.ListContainerMounts # { -# "mounts": [ -# "/var/lib/containers/storage/overlay/b215fb622c65ba3b06c6d2341be80b76a9de7ae415ce419e65228873d4f0dcc8/merged", -# "/var/lib/containers/storage/overlay/5eaf806073f79c0ed9a695180ad598e34f963f7407da1d2ccf3560bdab49b26f/merged", -# "/var/lib/containers/storage/overlay/1ecb6b1dbb251737c7a24a31869096839c3719d8b250bf075f75172ddcc701e1/merged", -# "/var/lib/containers/storage/overlay/7137b28a3c422165fe920cba851f2f8da271c6b5908672c451ebda03ad3919e2/merged" -# ] +# "mounts": { +# "04e4c255269ed2545e7f8bd1395a75f7949c50c223415c00c1d54bfa20f3b3d9": "/var/lib/containers/storage/overlay/a078925828f57e20467ca31cfca8a849210d21ec7e5757332b72b6924f441c17/merged", +# "1d58c319f9e881a644a5122ff84419dccf6d138f744469281446ab243ef38924": "/var/lib/containers/storage/overlay/948fcf93f8cb932f0f03fd52e3180a58627d547192ffe3b88e0013b98ddcd0d2/merged" +# } # } # ~~~ -method ListContainerMounts() -> (mounts: []string) +method ListContainerMounts() -> (mounts: [string]string) # MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination # mount is returned as a string. @@ -1018,7 +1017,7 @@ method UnmountContainer(name: string, force: bool) -> () # ImagesPrune removes all unused images from the local store. Upon successful pruning, # the IDs of the removed images are returned. -method ImagesPrune() -> (pruned: []string) +method ImagesPrune(all: bool) -> (pruned: []string) # This function is not implemented yet. method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) @@ -1035,6 +1034,22 @@ method GenerateKubeService() -> (notimplemented: NotImplemented) # like that created by GenerateKube. See also [GenerateKube](GenerateKube). method ReplayKube() -> (notimplemented: NotImplemented) +# ContainerConfig returns a container's config in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerConfig(name: string) -> (config: string) + +# ContainerArtifacts returns a container's artifacts in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerArtifacts(name: string, artifactName: string) -> (config: string) + +# ContainerInspectData returns a container's inspect data in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerInspectData(name: string) -> (config: string) + +# ContainerStateData returns a container's state config in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerStateData(name: string) -> (config: string) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) diff --git a/cmd/podman/version.go b/cmd/podman/version.go index d81deb696..ce773ee2e 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "os" + "text/tabwriter" "time" "github.com/containers/libpod/cmd/podman/formats" @@ -26,20 +28,22 @@ func versionCmd(c *cli.Context) error { default: out = formats.StdoutTemplate{Output: output, Template: versionOutputFormat} } - formats.Writer(out).Out() - return nil + return formats.Writer(out).Out() } - fmt.Println("Version: ", output.Version) - fmt.Println("Go Version: ", output.GoVersion) + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer w.Flush() + fmt.Fprintf(w, "Version:\t%s\n", output.Version) + fmt.Fprintf(w, "RemoteAPI Version:\t%d\n", output.RemoteAPIVersion) + fmt.Fprintf(w, "Go Version:\t%s\n", output.GoVersion) if output.GitCommit != "" { - fmt.Println("Git Commit: ", output.GitCommit) + fmt.Fprintf(w, "Git Commit:\t%s\n", output.GitCommit) } // Prints out the build time in readable format if output.Built != 0 { - fmt.Println("Built: ", time.Unix(output.Built, 0).Format(time.ANSIC)) + fmt.Fprintf(w, "Built:\t%s\n", time.Unix(output.Built, 0).Format(time.ANSIC)) } - fmt.Println("OS/Arch: ", output.OsArch) + fmt.Fprintf(w, "OS/Arch:\t%s\n", output.OsArch) return nil } @@ -53,7 +57,7 @@ var ( } versionFlags = []cli.Flag{ cli.StringFlag{ - Name: "format", + Name: "format, f", Usage: "Change the output format to JSON or a Go template", }, } |