diff options
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/podman/common/specgen.go | 3 | ||||
-rw-r--r-- | cmd/podman/common/volumes.go | 137 | ||||
-rw-r--r-- | cmd/podman/containers/ps.go | 138 | ||||
-rw-r--r-- | cmd/podman/containers/stats.go | 17 | ||||
-rw-r--r-- | cmd/podman/images/build.go | 14 | ||||
-rw-r--r-- | cmd/podman/images/history.go | 3 | ||||
-rw-r--r-- | cmd/podman/images/list.go | 13 | ||||
-rw-r--r-- | cmd/podman/images/search.go | 19 | ||||
-rw-r--r-- | cmd/podman/networks/list.go | 46 | ||||
-rw-r--r-- | cmd/podman/parse/template.go | 22 | ||||
-rw-r--r-- | cmd/podman/parse/template_test.go | 30 | ||||
-rw-r--r-- | cmd/podman/pods/ps.go | 9 | ||||
-rw-r--r-- | cmd/podman/pods/stats.go | 3 | ||||
-rw-r--r-- | cmd/podman/registry/registry.go | 7 | ||||
-rw-r--r-- | cmd/podman/system/df.go | 28 | ||||
-rw-r--r-- | cmd/podman/system/service.go | 17 | ||||
-rw-r--r-- | cmd/podman/volumes/list.go | 5 |
17 files changed, 346 insertions, 165 deletions
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index f427830c6..bd3e5fafd 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -533,13 +533,14 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // Only add read-only tmpfs mounts in case that we are read-only and the // read-only tmpfs flag has been set. - mounts, volumes, overlayVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly) + mounts, volumes, overlayVolumes, imageVolumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly) if err != nil { return err } s.Mounts = mounts s.Volumes = volumes s.OverlayVolumes = overlayVolumes + s.ImageVolumes = imageVolumes for _, dev := range c.Devices { s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev}) diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go index 71f897264..8ab20ccae 100644 --- a/cmd/podman/common/volumes.go +++ b/cmd/podman/common/volumes.go @@ -37,43 +37,43 @@ var ( // Does not handle image volumes, init, and --volumes-from flags. // Can also add tmpfs mounts from read-only tmpfs. // TODO: handle options parsing/processing via containers/storage/pkg/mount -func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { +func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) { // Get mounts from the --mounts flag. - unifiedMounts, unifiedVolumes, err := getMounts(mountFlag) + unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := getMounts(mountFlag) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // Next --volumes flag. volumeMounts, volumeVolumes, overlayVolumes, err := getVolumeMounts(volumeFlag) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // Next --tmpfs flag. tmpfsMounts, err := getTmpfsMounts(tmpfsFlag) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, nil, err } // Unify mounts from --mount, --volume, --tmpfs. // Start with --volume. for dest, mount := range volumeMounts { if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) + return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) } unifiedMounts[dest] = mount } for dest, volume := range volumeVolumes { if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) + return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) } unifiedVolumes[dest] = volume } // Now --tmpfs for dest, tmpfs := range tmpfsMounts { if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) + return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) } unifiedMounts[dest] = tmpfs } @@ -98,29 +98,34 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo } } - // Check for conflicts between named volumes, overlay volumes, and mounts - for dest := range unifiedMounts { - if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + // Check for conflicts between named volumes, overlay & image volumes, + // and mounts + allMounts := make(map[string]bool) + testAndSet := func(dest string) error { + if _, ok := allMounts[dest]; ok { + return errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) } - if _, ok := overlayVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + allMounts[dest] = true + return nil + } + for dest := range unifiedMounts { + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } } for dest := range unifiedVolumes { - if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - } - if _, ok := overlayVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } } for dest := range overlayVolumes { - if _, ok := unifiedMounts[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } - if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + } + for dest := range unifiedImageVolumes { + if err := testAndSet(dest); err != nil { + return nil, nil, nil, nil, err } } @@ -130,7 +135,7 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo if mount.Type == TypeBind { absSrc, err := filepath.Abs(mount.Source) if err != nil { - return nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) + return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) } mount.Source = absSrc } @@ -144,8 +149,12 @@ func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bo for _, volume := range overlayVolumes { finalOverlayVolume = append(finalOverlayVolume, volume) } + finalImageVolumes := make([]*specgen.ImageVolume, 0, len(unifiedImageVolumes)) + for _, volume := range unifiedImageVolumes { + finalImageVolumes = append(finalImageVolumes, volume) + } - return finalMounts, finalVolumes, finalOverlayVolume, nil + return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil } // findMountType parses the input and extracts the type of the mount type and @@ -174,59 +183,69 @@ func findMountType(input string) (mountType string, tokens []string, err error) // podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... // podman run --mount type=tmpfs,target=/dev/shm ... // podman run --mount type=volume,source=test-volume, ... -func getMounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { +func getMounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) { finalMounts := make(map[string]spec.Mount) finalNamedVolumes := make(map[string]*specgen.NamedVolume) + finalImageVolumes := make(map[string]*specgen.ImageVolume) for _, mount := range mountFlag { // TODO: Docker defaults to "volume" if no mount type is specified. mountType, tokens, err := findMountType(mount) if err != nil { - return nil, nil, err + return nil, nil, nil, err } switch mountType { case TypeBind: mount, err := getBindMount(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) } finalMounts[mount.Destination] = mount case TypeTmpfs: mount, err := getTmpfsMount(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) } finalMounts[mount.Destination] = mount case TypeDevpts: mount, err := getDevptsMount(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) } finalMounts[mount.Destination] = mount + case "image": + volume, err := getImageVolume(tokens) + if err != nil { + return nil, nil, nil, err + } + if _, ok := finalImageVolumes[volume.Destination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Destination) + } + finalImageVolumes[volume.Destination] = volume case "volume": volume, err := getNamedVolume(tokens) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if _, ok := finalNamedVolumes[volume.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) + return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) } finalNamedVolumes[volume.Dest] = volume default: - return nil, nil, errors.Errorf("invalid filesystem type %q", mountType) + return nil, nil, nil, errors.Errorf("invalid filesystem type %q", mountType) } } - return finalMounts, finalNamedVolumes, nil + return finalMounts, finalNamedVolumes, finalImageVolumes, nil } // Parse a single bind mount entry from the --mount flag. @@ -531,6 +550,50 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) { return newVolume, nil } +// Parse the arguments into an image volume. An image volume is a volume based +// on a container image. The container image is first mounted on the host and +// is then bind-mounted into the container. An ImageVolume is always mounted +// read only. +func getImageVolume(args []string) (*specgen.ImageVolume, error) { + newVolume := new(specgen.ImageVolume) + + for _, val := range args { + kv := strings.SplitN(val, "=", 2) + switch kv[0] { + case "src", "source": + if len(kv) == 1 { + return nil, errors.Wrapf(optionArgError, kv[0]) + } + newVolume.Source = kv[1] + case "target", "dst", "destination": + if len(kv) == 1 { + return nil, errors.Wrapf(optionArgError, kv[0]) + } + if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + return nil, err + } + newVolume.Destination = filepath.Clean(kv[1]) + case "rw", "readwrite": + switch kv[1] { + case "true": + newVolume.ReadWrite = true + case "false": + // Nothing to do. RO is default. + default: + return nil, errors.Wrapf(util.ErrBadMntOption, "invalid rw value %q", kv[1]) + } + default: + return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) + } + } + + if len(newVolume.Source)*len(newVolume.Destination) == 0 { + return nil, errors.Errorf("must set source and destination for image volume") + } + + return newVolume, nil +} + func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.OverlayVolume, error) { mounts := make(map[string]spec.Mount) volumes := make(map[string]*specgen.NamedVolume) diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 90f4db19c..7da430bc6 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -12,6 +12,7 @@ import ( tm "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -40,9 +41,8 @@ var ( listOpts = entities.ContainerListOptions{ Filters: make(map[string][]string), } - filters []string - noTrunc bool - defaultHeaders = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" + filters []string + noTrunc bool ) func init() { @@ -91,10 +91,6 @@ func checkFlags(c *cobra.Command) error { if listOpts.Size || listOpts.Namespace { return errors.Errorf("quiet conflicts with size and namespace") } - if c.Flag("format").Changed && !report.IsJSON(listOpts.Format) { - // Quiet is overridden by Go template output. - listOpts.Quiet = false - } } // Size and namespace conflict with each other if listOpts.Size && listOpts.Namespace { @@ -155,7 +151,7 @@ func getResponses() ([]entities.ListContainer, error) { return responses, nil } -func ps(cmd *cobra.Command, args []string) error { +func ps(cmd *cobra.Command, _ []string) error { if err := checkFlags(cmd); err != nil { return err } @@ -180,24 +176,22 @@ func ps(cmd *cobra.Command, args []string) error { switch { case report.IsJSON(listOpts.Format): return jsonOut(listContainers) - case listOpts.Quiet: + case listOpts.Quiet && !cmd.Flags().Changed("format"): return quietOut(listContainers) } - // Output table Watch > 0 will refresh screen responses := make([]psReporter, 0, len(listContainers)) for _, r := range listContainers { responses = append(responses, psReporter{r}) } - var headers, format string + hdrs, format := createPsOut() if cmd.Flags().Changed("format") { - headers = "" format = report.NormalizeFormat(listOpts.Format) - } else { - headers, format = createPsOut() + format = parse.EnforceRange(format) } - format = headers + "{{range . }}" + format + "{{end}}" + ns := strings.NewReplacer(".Namespaces.", ".") + format = ns.Replace(format) tmpl, err := template.New("listContainers").Parse(format) if err != nil { @@ -206,13 +200,19 @@ func ps(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if listOpts.Watch > 0 { - for { - var responses []psReporter - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() + headers := func() error { return nil } + if !(listOpts.Quiet || cmd.Flags().Changed("format")) { + headers = func() error { + return tmpl.Execute(w, hdrs) + } + } + switch { + // Output table Watch > 0 will refresh screen + case listOpts.Watch > 0: + // responses will grow to the largest number of processes reported on, but will not thrash the gc + var responses []psReporter + for ; ; responses = responses[:0] { if ctnrs, err := getResponses(); err != nil { return err } else { @@ -221,18 +221,27 @@ func ps(cmd *cobra.Command, args []string) error { } } + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + + if err := headers(); err != nil { + return err + } if err := tmpl.Execute(w, responses); err != nil { return err } if err := w.Flush(); err != nil { + // we usually do not care about Flush() failures but here do not loop if Flush() has failed return err } + time.Sleep(time.Duration(listOpts.Watch) * time.Second) - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() } - } else if listOpts.Watch < 1 { + default: + if err := headers(); err != nil { + return err + } if err := tmpl.Execute(w, responses); err != nil { return err } @@ -241,30 +250,36 @@ func ps(cmd *cobra.Command, args []string) error { } // cannot use report.Headers() as it doesn't support structures as fields -func createPsOut() (string, string) { +func createPsOut() ([]map[string]string, string) { + hdrs := report.Headers(psReporter{}, map[string]string{ + "Cgroup": "cgroupns", + "CreatedHuman": "created", + "ID": "container id", + "IPC": "ipc", + "MNT": "mnt", + "NET": "net", + "PIDNS": "pidns", + "Pod": "pod id", + "PodName": "podname", // undo camelcase space break + "UTS": "uts", + "User": "userns", + }) + var row string if listOpts.Namespace { - headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDNS\tUSERNS\tUTS\n" - row := "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" - return headers, row - } - headers := defaultHeaders - row += "{{.ID}}" - row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" + row = "{{.ID}}\t{{.Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}" + } else { + row = "{{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" - if listOpts.Pod { - headers += "\tPOD ID\tPODNAME" - row += "\t{{.Pod}}\t{{.PodName}}" - } + if listOpts.Pod { + row += "\t{{.Pod}}\t{{.PodName}}" + } - if listOpts.Size { - headers += "\tSIZE" - row += "\t{{.Size}}" + if listOpts.Size { + row += "\t{{.Size}}" + } } - - headers = report.NormalizeFormat(headers) - row = report.NormalizeFormat(row) - return headers, row + return hdrs, "{{range .}}" + row + "\n{{end}}" } type psReporter struct { @@ -367,6 +382,41 @@ func (l psReporter) CreatedHuman() string { return units.HumanDuration(time.Since(time.Unix(l.Created, 0))) + " ago" } +// Cgroup exposes .Namespaces.Cgroup +func (l psReporter) Cgroup() string { + return l.Namespaces.Cgroup +} + +// IPC exposes .Namespaces.IPC +func (l psReporter) IPC() string { + return l.Namespaces.IPC +} + +// MNT exposes .Namespaces.MNT +func (l psReporter) MNT() string { + return l.Namespaces.MNT +} + +// NET exposes .Namespaces.NET +func (l psReporter) NET() string { + return l.Namespaces.NET +} + +// PIDNS exposes .Namespaces.PIDNS +func (l psReporter) PIDNS() string { + return l.Namespaces.PIDNS +} + +// User exposes .Namespaces.User +func (l psReporter) User() string { + return l.Namespaces.User +} + +// UTS exposes .Namespaces.UTS +func (l psReporter) UTS() string { + return l.Namespaces.UTS +} + // portsToString converts the ports used to a string of the from "port1, port2" // and also groups a continuous list of ports into a readable format. func portsToString(ports []ocicni.PortMapping) string { diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 85e7a1e82..bfab469ca 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -8,6 +8,7 @@ import ( tm "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/define" @@ -58,8 +59,7 @@ type statsOptionsCLI struct { } var ( - statsOptions statsOptionsCLI - defaultStatsRow = "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" + statsOptions statsOptionsCLI ) func statFlags(flags *pflag.FlagSet) { @@ -159,19 +159,19 @@ func outputStats(reports []define.ContainerStats) error { if report.IsJSON(statsOptions.Format) { return outputJSON(stats) } - format := defaultStatsRow - + format := "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" if len(statsOptions.Format) > 0 { format = report.NormalizeFormat(statsOptions.Format) - } else if len(statsOptions.Format) < 1 { - format = defaultStatsRow } - format = "{{range . }}" + format + "{{end}}" + format = parse.EnforceRange(format) + tmpl, err := template.New("stats").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + defer w.Flush() + if len(statsOptions.Format) < 1 { if err := tmpl.Execute(w, headers); err != nil { return err @@ -180,9 +180,6 @@ func outputStats(reports []define.ContainerStats) error { if err := tmpl.Execute(w, stats); err != nil { return err } - if err := w.Flush(); err != nil { - return err - } return nil } diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 18c31313b..0ff482cd9 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -240,14 +240,22 @@ func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buil } } - pullPolicy := imagebuildah.PullNever - if flags.Pull { - pullPolicy = imagebuildah.PullIfMissing + pullPolicy := imagebuildah.PullIfNewer + if c.Flags().Changed("pull") { + if flags.Pull { + pullPolicy = imagebuildah.PullAlways + } else { + pullPolicy = imagebuildah.PullNever + } } if flags.PullAlways { pullPolicy = imagebuildah.PullAlways } + if flags.PullNever { + pullPolicy = imagebuildah.PullNever + } + args := make(map[string]string) if c.Flag("build-arg").Changed { for _, arg := range flags.BuildArg { diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index 3075218d1..e9751b365 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -11,6 +11,7 @@ import ( "unicode" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" @@ -119,7 +120,7 @@ func history(cmd *cobra.Command, args []string) error { case opts.quiet: row = "{{.ID}}\n" } - format := "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) tmpl, err := template.New("report").Parse(format) if err != nil { diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 489b15086..e24631b24 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -12,6 +12,7 @@ import ( "github.com/containers/common/pkg/report" "github.com/containers/image/v5/docker/reference" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/docker/go-units" @@ -105,10 +106,10 @@ func images(cmd *cobra.Command, args []string) error { return err } switch { - case listFlag.quiet: - return writeID(imgs) case report.IsJSON(listFlag.format): return writeJSON(imgs) + case listFlag.quiet: + return writeID(imgs) default: if cmd.Flag("format").Changed { listFlag.noHeading = true // V1 compatibility @@ -171,9 +172,13 @@ func writeTemplate(imgs []imageReporter) error { } else { row = report.NormalizeFormat(listFlag.format) } + format := parse.EnforceRange(row) + + tmpl, err := template.New("list").Parse(format) + if err != nil { + return err + } - format := "{{range . }}" + row + "{{end}}" - tmpl := template.Must(template.New("list").Parse(format)) w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index b1a1442a6..66256aaa0 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -8,6 +8,7 @@ import ( "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/report" "github.com/containers/image/v5/types" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -130,26 +131,30 @@ func imageSearch(cmd *cobra.Command, args []string) error { } hdrs := report.Headers(entities.ImageSearchReport{}, nil) - row := "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" - if searchOptions.ListTags { + renderHeaders := true + var row string + switch { + case searchOptions.ListTags: if len(searchOptions.Filters) != 0 { return errors.Errorf("filters are not applicable to list tags result") } row = "{{.Name}}\t{{.Tag}}\n" - } - if cmd.Flags().Changed("format") { + case cmd.Flags().Changed("format"): + renderHeaders = parse.HasTable(searchOptions.Format) row = report.NormalizeFormat(searchOptions.Format) + default: + row = "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" } - row = "{{range .}}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("search").Parse(row) + tmpl, err := template.New("search").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if !cmd.Flags().Changed("format") { + if renderHeaders { if err := tmpl.Execute(w, hdrs); err != nil { return errors.Wrapf(err, "failed to write search column headers") } diff --git a/cmd/podman/networks/list.go b/cmd/podman/networks/list.go index 532af631e..f68e4ed75 100644 --- a/cmd/podman/networks/list.go +++ b/cmd/podman/networks/list.go @@ -8,6 +8,8 @@ import ( "text/tabwriter" "text/template" + "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/libpod/network" @@ -30,8 +32,6 @@ var ( var ( networkListOptions entities.NetworkListOptions - headers = "NAME\tVERSION\tPLUGINS\n" - defaultListRow = "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" ) func networkListFlags(flags *pflag.FlagSet) { @@ -66,13 +66,12 @@ func networkList(cmd *cobra.Command, args []string) error { return err } - // quiet means we only print the network names - if networkListOptions.Quiet { - return quietOut(responses) - } - - if strings.ToLower(networkListOptions.Format) == "json" { + switch { + case report.IsJSON(networkListOptions.Format): return jsonOut(responses) + case networkListOptions.Quiet: + // quiet means we only print the network names + return quietOut(responses) } nlprs := make([]ListPrintReports, 0, len(responses)) @@ -80,27 +79,32 @@ func networkList(cmd *cobra.Command, args []string) error { nlprs = append(nlprs, ListPrintReports{r}) } - row := networkListOptions.Format - if len(row) < 1 { - row = defaultListRow - } - if !strings.HasSuffix(row, "\n") { - row += "\n" + headers := report.Headers(ListPrintReports{}, map[string]string{ + "CNIVersion": "version", + "Plugins": "plugins", + }) + renderHeaders := true + row := "{{.Name}}\t{{.Version}}\t{{.Plugins}}\n" + if cmd.Flags().Changed("format") { + renderHeaders = parse.HasTable(networkListOptions.Format) + row = report.NormalizeFormat(networkListOptions.Format) } + format := parse.EnforceRange(row) - format := "{{range . }}" + row + "{{end}}" - if !cmd.Flag("format").Changed { - format = headers + format - } tmpl, err := template.New("listNetworks").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - if err := tmpl.Execute(w, nlprs); err != nil { - return err + defer w.Flush() + + if renderHeaders { + if err := tmpl.Execute(w, headers); err != nil { + return err + } + } - return w.Flush() + return tmpl.Execute(w, nlprs) } func quietOut(responses []*entities.NetworkListReport) error { diff --git a/cmd/podman/parse/template.go b/cmd/podman/parse/template.go new file mode 100644 index 000000000..0b80f1b3a --- /dev/null +++ b/cmd/podman/parse/template.go @@ -0,0 +1,22 @@ +package parse + +import ( + "regexp" + "strings" +) + +var rangeRegex = regexp.MustCompile(`{{\s*range\s*\.\s*}}.*{{\s*end\s*}}`) + +// TODO move to github.com/containers/common/pkg/report +// EnforceRange ensures that the format string contains a range +func EnforceRange(format string) string { + if !rangeRegex.MatchString(format) { + return "{{range .}}" + format + "{{end}}" + } + return format +} + +// EnforceRange ensures that the format string contains a range +func HasTable(format string) bool { + return strings.HasPrefix(format, "table ") +} diff --git a/cmd/podman/parse/template_test.go b/cmd/podman/parse/template_test.go new file mode 100644 index 000000000..7880d9bec --- /dev/null +++ b/cmd/podman/parse/template_test.go @@ -0,0 +1,30 @@ +package parse + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEnforceRange(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"{{range .}}{{.ID}}{{end}}", "{{range .}}{{.ID}}{{end}}"}, + {"{{.ID}}", "{{range .}}{{.ID}}{{end}}"}, + {"{{ range . }}{{ .ID }}{{ end }}", "{{ range . }}{{ .ID }}{{ end }}"}, + // EnforceRange does not verify syntax or semantics, that will happen later + {"{{range .}}{{.ID}}", "{{range .}}{{range .}}{{.ID}}{{end}}"}, + {".ID", "{{range .}}.ID{{end}}"}, + } + + for _, tc := range tests { + tc := tc + label := "TestEnforceRange_" + tc.input + t.Run(label, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.expected, EnforceRange(tc.input)) + }) + } +} diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index 688108c1a..40fc71780 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -11,6 +11,7 @@ import ( "time" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -113,20 +114,22 @@ func pods(cmd *cobra.Command, _ []string) error { "Created": "CREATED", "InfraID": "INFRA ID", }) + renderHeaders := true row := podPsFormat() if cmd.Flags().Changed("format") { + renderHeaders = parse.HasTable(psInput.Format) row = report.NormalizeFormat(psInput.Format) } - row = "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("listPods").Parse(row) + tmpl, err := template.New("listPods").Parse(format) if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) defer w.Flush() - if !psInput.Quiet && !cmd.Flag("format").Changed { + if renderHeaders { if err := tmpl.Execute(w, headers); err != nil { return err } diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 338f13d3e..c5d1e7f07 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -10,6 +10,7 @@ import ( "github.com/buger/goterm" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -135,7 +136,7 @@ func printFormattedPodStatsLines(headerNames []map[string]string, row string, st return nil } - row = "{{range .}}" + row + "{{end}}" + row = parse.EnforceRange(row) tmpl, err := template.New("pod stats").Parse(row) if err != nil { diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go index 1e439613c..9c0b290e7 100644 --- a/cmd/podman/registry/registry.go +++ b/cmd/podman/registry/registry.go @@ -12,8 +12,11 @@ import ( "github.com/spf13/cobra" ) -// DefaultRootAPIAddress is the default address of the REST socket -const DefaultRootAPIAddress = "unix:/run/podman/podman.sock" +// DefaultRootAPIPath is the default path of the REST socket +const DefaultRootAPIPath = "/run/podman/podman.sock" + +// DefaultRootAPIAddress is the default address of the REST socket with unix: prefix +const DefaultRootAPIAddress = "unix:" + DefaultRootAPIPath // DefaultVarlinkAddress is the default address of the varlink socket const DefaultVarlinkAddress = "unix:/run/podman/io.podman" diff --git a/cmd/podman/system/df.go b/cmd/podman/system/df.go index b11167938..fbdf274fb 100644 --- a/cmd/podman/system/df.go +++ b/cmd/podman/system/df.go @@ -9,6 +9,7 @@ import ( "time" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -55,7 +56,7 @@ func df(cmd *cobra.Command, args []string) error { w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) if dfOptions.Verbose { - return printVerbose(cmd, w, reports) + return printVerbose(w, cmd, reports) } return printSummary(w, cmd, reports) } @@ -131,20 +132,16 @@ func printSummary(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.Sys "Size": "SIZE", "Reclaimable": "RECLAIMABLE", }) - row := "{{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}\n" if cmd.Flags().Changed("format") { row = report.NormalizeFormat(dfOptions.Format) } - row = "{{range . }}" + row + "{{end}}" - - return writeTemplate(cmd, w, hdrs, row, dfSummaries) + return writeTemplate(w, cmd, hdrs, row, dfSummaries) } -func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.SystemDfReport) error { +func printVerbose(w *tabwriter.Writer, cmd *cobra.Command, reports *entities.SystemDfReport) error { defer w.Flush() - // Images fmt.Fprint(w, "Images space usage:\n\n") // convert to dfImage for output dfImages := make([]*dfImage, 0, len(reports.Images)) @@ -157,14 +154,11 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "UniqueSize": "UNIQUE SIZE", }) imageRow := "{{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}\n" - format := "{{range . }}" + imageRow + "{{end}}" - if err := writeTemplate(cmd, w, hdrs, format, dfImages); err != nil { + if err := writeTemplate(w, cmd, hdrs, imageRow, dfImages); err != nil { return nil } - // Containers fmt.Fprint(w, "\nContainers space usage:\n\n") - // convert to dfContainers for output dfContainers := make([]*dfContainer, 0, len(reports.Containers)) for _, d := range reports.Containers { @@ -176,14 +170,11 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "RWSize": "SIZE", }) containerRow := "{{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.RWSize}}\t{{.Created}}\t{{.Status}}\t{{.Names}}\n" - format = "{{range . }}" + containerRow + "{{end}}" - if err := writeTemplate(cmd, w, hdrs, format, dfContainers); err != nil { + if err := writeTemplate(w, cmd, hdrs, containerRow, dfContainers); err != nil { return nil } - // Volumes fmt.Fprint(w, "\nLocal Volumes space usage:\n\n") - dfVolumes := make([]*dfVolume, 0, len(reports.Volumes)) // convert to dfVolume for output for _, d := range reports.Volumes { @@ -193,14 +184,13 @@ func printVerbose(cmd *cobra.Command, w *tabwriter.Writer, reports *entities.Sys "VolumeName": "VOLUME NAME", }) volumeRow := "{{.VolumeName}}\t{{.Links}}\t{{.Size}}\n" - format = "{{range . }}" + volumeRow + "{{end}}" - return writeTemplate(cmd, w, hdrs, format, dfVolumes) + return writeTemplate(w, cmd, hdrs, volumeRow, dfVolumes) } -func writeTemplate(cmd *cobra.Command, w *tabwriter.Writer, hdrs []map[string]string, format string, - output interface{}) error { +func writeTemplate(w *tabwriter.Writer, cmd *cobra.Command, hdrs []map[string]string, format string, output interface{}) error { defer w.Flush() + format = parse.EnforceRange(format) tmpl, err := template.New("df").Parse(format) if err != nil { return err diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go index 2a2b1984f..0476c632d 100644 --- a/cmd/podman/system/service.go +++ b/cmd/podman/system/service.go @@ -131,20 +131,17 @@ func resolveAPIURI(_url []string) (string, error) { if srvArgs.Varlink { socketName = "io.podman" } - socketDir := filepath.Join(xdg, "podman", socketName) - if _, err := os.Stat(filepath.Dir(socketDir)); err != nil { - if os.IsNotExist(err) { - if err := os.Mkdir(filepath.Dir(socketDir), 0755); err != nil { - return "", err - } - } else { - return "", err - } + socketPath := filepath.Join(xdg, "podman", socketName) + if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil { + return "", err } - return "unix:" + socketDir, nil + return "unix:" + socketPath, nil case srvArgs.Varlink: return registry.DefaultVarlinkAddress, nil default: + if err := os.MkdirAll(filepath.Dir(registry.DefaultRootAPIPath), 0700); err != nil { + return "", err + } return registry.DefaultRootAPIAddress, nil } } diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go index b3b2b8ea1..ce0b7997d 100644 --- a/cmd/podman/volumes/list.go +++ b/cmd/podman/volumes/list.go @@ -9,6 +9,7 @@ import ( "text/template" "github.com/containers/common/pkg/report" + "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/validate" "github.com/containers/podman/v2/pkg/domain/entities" @@ -91,9 +92,9 @@ func outputTemplate(cmd *cobra.Command, responses []*entities.VolumeListReport) if cliOpts.Quiet { row = "{{.Name}}\n" } - row = "{{range . }}" + row + "{{end}}" + format := parse.EnforceRange(row) - tmpl, err := template.New("list volume").Parse(row) + tmpl, err := template.New("list volume").Parse(format) if err != nil { return err } |