mirror of
https://github.com/containers/skopeo.git
synced 2026-07-14 13:48:32 +00:00
This patch will allow commands like buildah run $ctr ls -lZ / To work correctly. Need to update vendor of urfave cli. Also changed all commands to no longer accept global options after the COMMAND. Single boolean options can now be passed together. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com> Closes: #493 Approved by: rhatdan
319 lines
8.0 KiB
Go
319 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/containers/storage"
|
|
"github.com/pkg/errors"
|
|
"github.com/projectatomic/buildah"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
type jsonContainer struct {
|
|
ID string `json:"id"`
|
|
Builder bool `json:"builder"`
|
|
ImageID string `json:"imageid"`
|
|
ImageName string `json:"imagename"`
|
|
ContainerName string `json:"containername"`
|
|
}
|
|
|
|
type containerOutputParams struct {
|
|
ContainerID string
|
|
Builder string
|
|
ImageID string
|
|
ImageName string
|
|
ContainerName string
|
|
}
|
|
|
|
type containerOptions struct {
|
|
all bool
|
|
format string
|
|
json bool
|
|
noHeading bool
|
|
noTruncate bool
|
|
quiet bool
|
|
}
|
|
|
|
type containerFilterParams struct {
|
|
id string
|
|
name string
|
|
ancestor string
|
|
}
|
|
|
|
var (
|
|
containersFlags = []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "all, a",
|
|
Usage: "also list non-buildah containers",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "filter, f",
|
|
Usage: "filter output based on conditions provided",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "format",
|
|
Usage: "pretty-print containers using a Go template",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "json",
|
|
Usage: "output in JSON format",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "noheading, n",
|
|
Usage: "do not print column headings",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "notruncate",
|
|
Usage: "do not truncate output",
|
|
},
|
|
cli.BoolFlag{
|
|
Name: "quiet, q",
|
|
Usage: "display only container IDs",
|
|
},
|
|
}
|
|
containersDescription = "Lists containers which appear to be " + buildah.Package + " working containers, their\n names and IDs, and the names and IDs of the images from which they were\n initialized"
|
|
containersCommand = cli.Command{
|
|
Name: "containers",
|
|
Usage: "List working containers and their base images",
|
|
Description: containersDescription,
|
|
Flags: containersFlags,
|
|
Action: containersCmd,
|
|
ArgsUsage: " ",
|
|
SkipArgReorder: true,
|
|
}
|
|
)
|
|
|
|
func containersCmd(c *cli.Context) error {
|
|
if err := validateFlags(c, containersFlags); err != nil {
|
|
return err
|
|
}
|
|
store, err := getStore(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.IsSet("quiet") && c.IsSet("format") {
|
|
return errors.Errorf("quiet and format are mutually exclusive")
|
|
}
|
|
|
|
opts := containerOptions{
|
|
all: c.Bool("all"),
|
|
format: c.String("format"),
|
|
json: c.Bool("json"),
|
|
noHeading: c.Bool("noheading"),
|
|
noTruncate: c.Bool("notruncate"),
|
|
quiet: c.Bool("quiet"),
|
|
}
|
|
|
|
var params *containerFilterParams
|
|
if c.IsSet("filter") {
|
|
params, err = parseCtrFilter(c.String("filter"))
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing filter")
|
|
}
|
|
}
|
|
|
|
if !opts.noHeading && !opts.quiet && opts.format == "" && !opts.json {
|
|
containerOutputHeader(!opts.noTruncate)
|
|
}
|
|
|
|
return outputContainers(store, opts, params)
|
|
}
|
|
|
|
func outputContainers(store storage.Store, opts containerOptions, params *containerFilterParams) error {
|
|
seenImages := make(map[string]string)
|
|
imageNameForID := func(id string) string {
|
|
if id == "" {
|
|
return buildah.BaseImageFakeName
|
|
}
|
|
imageName, ok := seenImages[id]
|
|
if ok {
|
|
return imageName
|
|
}
|
|
img, err2 := store.Image(id)
|
|
if err2 == nil && len(img.Names) > 0 {
|
|
seenImages[id] = img.Names[0]
|
|
}
|
|
return seenImages[id]
|
|
}
|
|
|
|
builders, err := openBuilders(store)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error reading build containers")
|
|
}
|
|
var (
|
|
containerOutput []containerOutputParams
|
|
JSONContainers []jsonContainer
|
|
)
|
|
if !opts.all {
|
|
// only output containers created by buildah
|
|
for _, builder := range builders {
|
|
image := imageNameForID(builder.FromImageID)
|
|
if !matchesCtrFilter(builder.ContainerID, builder.Container, builder.FromImageID, image, params) {
|
|
continue
|
|
}
|
|
if opts.json {
|
|
JSONContainers = append(JSONContainers, jsonContainer{ID: builder.ContainerID,
|
|
Builder: true,
|
|
ImageID: builder.FromImageID,
|
|
ImageName: image,
|
|
ContainerName: builder.Container})
|
|
continue
|
|
}
|
|
output := containerOutputParams{
|
|
ContainerID: builder.ContainerID,
|
|
Builder: " *",
|
|
ImageID: builder.FromImageID,
|
|
ImageName: image,
|
|
ContainerName: builder.Container,
|
|
}
|
|
containerOutput = append(containerOutput, output)
|
|
}
|
|
} else {
|
|
// output all containers currently in storage
|
|
builderMap := make(map[string]struct{})
|
|
for _, builder := range builders {
|
|
builderMap[builder.ContainerID] = struct{}{}
|
|
}
|
|
containers, err2 := store.Containers()
|
|
if err2 != nil {
|
|
return errors.Wrapf(err2, "error reading list of all containers")
|
|
}
|
|
for _, container := range containers {
|
|
name := ""
|
|
if len(container.Names) > 0 {
|
|
name = container.Names[0]
|
|
}
|
|
_, ours := builderMap[container.ID]
|
|
builder := ""
|
|
if ours {
|
|
builder = " *"
|
|
}
|
|
if !matchesCtrFilter(container.ID, name, container.ImageID, imageNameForID(container.ImageID), params) {
|
|
continue
|
|
}
|
|
if opts.json {
|
|
JSONContainers = append(JSONContainers, jsonContainer{ID: container.ID,
|
|
Builder: ours,
|
|
ImageID: container.ImageID,
|
|
ImageName: imageNameForID(container.ImageID),
|
|
ContainerName: name})
|
|
}
|
|
output := containerOutputParams{
|
|
ContainerID: container.ID,
|
|
Builder: builder,
|
|
ImageID: container.ImageID,
|
|
ImageName: imageNameForID(container.ImageID),
|
|
ContainerName: name,
|
|
}
|
|
containerOutput = append(containerOutput, output)
|
|
}
|
|
}
|
|
if opts.json {
|
|
data, err := json.MarshalIndent(JSONContainers, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("%s\n", data)
|
|
return nil
|
|
}
|
|
|
|
for _, ctr := range containerOutput {
|
|
if opts.quiet {
|
|
fmt.Printf("%-64s\n", ctr.ContainerID)
|
|
continue
|
|
}
|
|
if opts.format != "" {
|
|
if err := containerOutputUsingTemplate(opts.format, ctr); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
containerOutputUsingFormatString(!opts.noTruncate, ctr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func containerOutputUsingTemplate(format string, params containerOutputParams) error {
|
|
tmpl, err := template.New("container").Parse(format)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "Template parsing error")
|
|
}
|
|
|
|
err = tmpl.Execute(os.Stdout, params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println()
|
|
return nil
|
|
}
|
|
|
|
func containerOutputUsingFormatString(truncate bool, params containerOutputParams) {
|
|
if truncate {
|
|
fmt.Printf("%-12.12s %-8s %-12.12s %-32s %s\n", params.ContainerID, params.Builder, params.ImageID, params.ImageName, params.ContainerName)
|
|
} else {
|
|
fmt.Printf("%-64s %-8s %-64s %-32s %s\n", params.ContainerID, params.Builder, params.ImageID, params.ImageName, params.ContainerName)
|
|
}
|
|
}
|
|
|
|
func containerOutputHeader(truncate bool) {
|
|
if truncate {
|
|
fmt.Printf("%-12s %-8s %-12s %-32s %s\n", "CONTAINER ID", "BUILDER", "IMAGE ID", "IMAGE NAME", "CONTAINER NAME")
|
|
} else {
|
|
fmt.Printf("%-64s %-8s %-64s %-32s %s\n", "CONTAINER ID", "BUILDER", "IMAGE ID", "IMAGE NAME", "CONTAINER NAME")
|
|
}
|
|
}
|
|
|
|
func parseCtrFilter(filter string) (*containerFilterParams, error) {
|
|
params := new(containerFilterParams)
|
|
filters := strings.Split(filter, ",")
|
|
for _, param := range filters {
|
|
pair := strings.SplitN(param, "=", 2)
|
|
if len(pair) != 2 {
|
|
return nil, errors.Errorf("incorrect filter value %q, should be of form filter=value", param)
|
|
}
|
|
switch strings.TrimSpace(pair[0]) {
|
|
case "id":
|
|
params.id = pair[1]
|
|
case "name":
|
|
params.name = pair[1]
|
|
case "ancestor":
|
|
params.ancestor = pair[1]
|
|
default:
|
|
return nil, errors.Errorf("invalid filter %q", pair[0])
|
|
}
|
|
}
|
|
return params, nil
|
|
}
|
|
|
|
func matchesCtrName(ctrName, argName string) bool {
|
|
return strings.Contains(ctrName, argName)
|
|
}
|
|
|
|
func matchesAncestor(imgName, imgID, argName string) bool {
|
|
if matchesID(imgID, argName) {
|
|
return true
|
|
}
|
|
return matchesReference(imgName, argName)
|
|
}
|
|
|
|
func matchesCtrFilter(ctrID, ctrName, imgID, imgName string, params *containerFilterParams) bool {
|
|
if params == nil {
|
|
return true
|
|
}
|
|
if params.id != "" && !matchesID(ctrID, params.id) {
|
|
return false
|
|
}
|
|
if params.name != "" && !matchesCtrName(ctrName, params.name) {
|
|
return false
|
|
}
|
|
if params.ancestor != "" && !matchesAncestor(imgName, imgID, params.ancestor) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|