podman/cmd/podman/inspect/inspect.go

330 lines
9.1 KiB
Go

package inspect
import (
"context"
"encoding/json" // due to a bug in json-iterator it cannot be used here
"fmt"
"os"
"regexp"
"strings"
"text/template"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/cmd/podman/validate"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
const (
// AllType can be of type ImageType or ContainerType.
AllType = "all"
// ContainerType is the container type.
ContainerType = "container"
// ImageType is the image type.
ImageType = "image"
// NetworkType is the network type
NetworkType = "network"
// PodType is the pod type.
PodType = "pod"
// VolumeType is the volume type
VolumeType = "volume"
)
// AddInspectFlagSet takes a command and adds the inspect flags and returns an
// InspectOptions object.
func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions {
opts := entities.InspectOptions{}
flags := cmd.Flags()
flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size")
formatFlagName := "format"
flags.StringVarP(&opts.Format, formatFlagName, "f", "json", "Format the output to a Go template or json")
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, completion.AutocompleteNone)
typeFlagName := "type"
flags.StringVarP(&opts.Type, typeFlagName, "t", AllType, fmt.Sprintf("Specify inspect-object type (%q, %q or %q)", ImageType, ContainerType, AllType))
_ = cmd.RegisterFlagCompletionFunc(typeFlagName, common.AutocompleteInspectType)
validate.AddLatestFlag(cmd, &opts.Latest)
return &opts
}
// Inspect inspects the specified container/image names or IDs.
func Inspect(namesOrIDs []string, options entities.InspectOptions) error {
inspector, err := newInspector(options)
if err != nil {
return err
}
return inspector.inspect(namesOrIDs)
}
// inspector allows for inspecting images and containers.
type inspector struct {
containerEngine entities.ContainerEngine
imageEngine entities.ImageEngine
options entities.InspectOptions
podOptions entities.PodInspectOptions
}
// newInspector creates a new inspector based on the specified options.
func newInspector(options entities.InspectOptions) (*inspector, error) {
switch options.Type {
case ImageType, ContainerType, AllType, PodType, NetworkType, VolumeType:
// Valid types.
default:
return nil, errors.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", options.Type, ImageType, ContainerType, PodType, NetworkType, VolumeType, AllType)
}
if options.Type == ImageType {
if options.Latest {
return nil, errors.Errorf("latest is not supported for type %q", ImageType)
}
if options.Size {
return nil, errors.Errorf("size is not supported for type %q", ImageType)
}
}
if options.Type == PodType && options.Size {
return nil, errors.Errorf("size is not supported for type %q", PodType)
}
podOpts := entities.PodInspectOptions{
Latest: options.Latest,
Format: options.Format,
}
return &inspector{
containerEngine: registry.ContainerEngine(),
imageEngine: registry.ImageEngine(),
options: options,
podOptions: podOpts,
}, nil
}
// inspect inspects the specified container/image names or IDs.
func (i *inspector) inspect(namesOrIDs []string) error {
// data - dumping place for inspection results.
var data []interface{} // nolint
var errs []error
ctx := context.Background()
if len(namesOrIDs) == 0 {
if !i.options.Latest && !i.options.All {
return errors.New("no names or ids specified")
}
}
tmpType := i.options.Type
if i.options.Latest {
if len(namesOrIDs) > 0 {
return errors.New("--latest and arguments cannot be used together")
}
if i.options.Type == AllType {
tmpType = ContainerType // -l works with --type=all, defaults to containertype
}
}
// Inspect - note that AllType requires us to expensively query one-by-one.
switch tmpType {
case AllType:
allData, allErrs, err := i.inspectAll(ctx, namesOrIDs)
if err != nil {
return err
}
data = allData
errs = allErrs
case ImageType:
imgData, allErrs, err := i.imageEngine.Inspect(ctx, namesOrIDs, i.options)
if err != nil {
return err
}
errs = allErrs
for i := range imgData {
data = append(data, imgData[i])
}
case ContainerType:
ctrData, allErrs, err := i.containerEngine.ContainerInspect(ctx, namesOrIDs, i.options)
if err != nil {
return err
}
errs = allErrs
for i := range ctrData {
data = append(data, ctrData[i])
}
case PodType:
for _, pod := range namesOrIDs {
i.podOptions.NameOrID = pod
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
if err != nil {
cause := errors.Cause(err)
if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) {
errs = []error{err}
} else {
return err
}
} else {
errs = nil
data = append(data, podData)
}
}
if i.podOptions.Latest { // latest means there are no names in the namesOrID array
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
if err != nil {
cause := errors.Cause(err)
if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) {
errs = []error{err}
} else {
return err
}
} else {
errs = nil
data = append(data, podData)
}
}
case NetworkType:
networkData, allErrs, err := registry.ContainerEngine().NetworkInspect(ctx, namesOrIDs, i.options)
if err != nil {
return err
}
errs = allErrs
for i := range networkData {
data = append(data, networkData[i])
}
case VolumeType:
volumeData, allErrs, err := i.containerEngine.VolumeInspect(ctx, namesOrIDs, i.options)
if err != nil {
return err
}
errs = allErrs
for i := range volumeData {
data = append(data, volumeData[i])
}
default:
return errors.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", i.options.Type, ImageType, ContainerType, PodType, NetworkType, VolumeType, AllType)
}
// Always print an empty array
if data == nil {
data = []interface{}{}
}
var err error
switch {
case report.IsJSON(i.options.Format) || i.options.Format == "":
err = printJSON(data)
default:
// Landing here implies user has given a custom --format
row := inspectNormalize(i.options.Format)
row = report.NormalizeFormat(row)
row = report.EnforceRange(row)
err = printTmpl(tmpType, row, data)
}
if err != nil {
logrus.Errorf("Printing inspect output: %v", err)
}
if len(errs) > 0 {
if len(errs) > 1 {
for _, err := range errs[1:] {
fmt.Fprintf(os.Stderr, "error inspecting object: %v\n", err)
}
}
return errors.Errorf("error inspecting object: %v", errs[0])
}
return nil
}
func printJSON(data []interface{}) error {
enc := json.NewEncoder(os.Stdout)
// by default, json marshallers will force utf=8 from
// a string. this breaks healthchecks that use <,>, &&.
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
return enc.Encode(data)
}
func printTmpl(typ, row string, data []interface{}) error {
// We cannot use c/common/reports here, too many levels of interface{}
t, err := template.New(typ + " inspect").Funcs(template.FuncMap(report.DefaultFuncs)).Parse(row)
if err != nil {
return err
}
w, err := report.NewWriterDefault(os.Stdout)
if err != nil {
return err
}
err = t.Execute(w, data)
w.Flush()
return err
}
func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, []error, error) {
var data []interface{} // nolint
allErrs := []error{}
for _, name := range namesOrIDs {
ctrData, errs, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options)
if err != nil {
return nil, nil, err
}
if len(errs) == 0 {
data = append(data, ctrData[0])
continue
}
imgData, errs, err := i.imageEngine.Inspect(ctx, []string{name}, i.options)
if err != nil {
return nil, nil, err
}
if len(errs) == 0 {
data = append(data, imgData[0])
continue
}
volumeData, errs, err := i.containerEngine.VolumeInspect(ctx, []string{name}, i.options)
if err != nil {
return nil, nil, err
}
if len(errs) == 0 {
data = append(data, volumeData[0])
continue
}
networkData, errs, err := registry.ContainerEngine().NetworkInspect(ctx, namesOrIDs, i.options)
if err != nil {
return nil, nil, err
}
if len(errs) == 0 {
data = append(data, networkData[0])
continue
}
i.podOptions.NameOrID = name
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
if err != nil {
cause := errors.Cause(err)
if !strings.Contains(cause.Error(), define.ErrNoSuchPod.Error()) {
return nil, nil, err
}
} else {
data = append(data, podData)
continue
}
if len(errs) > 0 {
allErrs = append(allErrs, errors.Errorf("no such object: %q", name))
continue
}
}
return data, allErrs, nil
}
func inspectNormalize(row string) string {
m := regexp.MustCompile(`{{\s*\.Id\s*}}`)
row = m.ReplaceAllString(row, "{{.ID}}")
r := strings.NewReplacer(
".Src", ".Source",
".Dst", ".Destination",
".ImageID", ".Image",
)
return r.Replace(row)
}