spiegel_podman/cmd/podman/inspect/inspect.go
Paul Holzinger 43f7bdf822
podman inspect return exit code > 0 on print error
Unlikely to happen but when there is an error printing the data to
stdout (either as json or go template) we should not just log it and
exit with 0. Instead return a proper error and exit with 125.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2022-09-13 10:33:14 +02:00

287 lines
8 KiB
Go

package inspect
import (
"context"
"encoding/json" // due to a bug in json-iterator it cannot be used here
"errors"
"fmt"
"os"
"regexp"
"strings"
"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/pkg/domain/entities"
"github.com/spf13/cobra"
)
// 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", common.AllType, "Specify inspect-object type")
_ = cmd.RegisterFlagCompletionFunc(typeFlagName, common.AutocompleteInspectType)
validate.AddLatestFlag(cmd, &opts.Latest)
return &opts
}
// Inspect inspects the specified container/image/pod/volume 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
}
// newInspector creates a new inspector based on the specified options.
func newInspector(options entities.InspectOptions) (*inspector, error) {
if options.Type == common.ImageType {
if options.Latest {
return nil, fmt.Errorf("latest is not supported for type %q", common.ImageType)
}
if options.Size {
return nil, fmt.Errorf("size is not supported for type %q", common.ImageType)
}
}
if options.Type == common.PodType && options.Size {
return nil, fmt.Errorf("size is not supported for type %q", common.PodType)
}
return &inspector{
containerEngine: registry.ContainerEngine(),
imageEngine: registry.ImageEngine(),
options: options,
}, 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{}
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 == common.AllType {
tmpType = common.ContainerType // -l works with --type=all, defaults to containertype
}
}
// Inspect - note that AllType requires us to expensively query one-by-one.
switch tmpType {
case common.AllType:
allData, allErrs, err := i.inspectAll(ctx, namesOrIDs)
if err != nil {
return err
}
data = allData
errs = allErrs
case common.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 common.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 common.PodType, common.PodLegacyType:
podData, allErrs, err := i.containerEngine.PodInspect(ctx, namesOrIDs, i.options)
if err != nil {
return err
}
errs = allErrs
for i := range podData {
data = append(data, podData[i])
}
case common.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 common.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 fmt.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", i.options.Type,
common.ImageType, common.ContainerType, common.PodType, common.NetworkType, common.VolumeType, common.AllType)
}
// Always print an empty array
if data == nil {
data = []interface{}{}
}
var err error
switch {
case report.IsJSON(i.options.Format) || i.options.Format == "":
if i.options.Type == common.PodLegacyType && len(data) == 1 {
// We need backwards compat with the old podman pod inspect behavior.
// https://github.com/containers/podman/pull/15675
// TODO (5.0): consider removing this to better match other commands.
err = printJSON(data[0])
} else {
err = printJSON(data)
}
default:
// Landing here implies user has given a custom --format
var rpt *report.Formatter
format := inspectNormalize(i.options.Format, i.options.Type)
rpt, err = report.New(os.Stdout, "inspect").Parse(report.OriginUser, format)
if err != nil {
return err
}
defer rpt.Flush()
err = rpt.Execute(data)
}
if err != nil {
errs = append(errs, fmt.Errorf("printing inspect output: %w", 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 fmt.Errorf("inspecting object: %w", 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 (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, []error, error) {
var data []interface{}
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
}
podData, errs, err := i.containerEngine.PodInspect(ctx, []string{name}, i.options)
if err != nil {
return nil, nil, err
}
if len(errs) == 0 {
data = append(data, podData[0])
continue
}
if len(errs) > 0 {
allErrs = append(allErrs, fmt.Errorf("no such object: %q", name))
continue
}
}
return data, allErrs, nil
}
func inspectNormalize(row string, inspectType string) string {
m := regexp.MustCompile(`{{\s*\.Id\s*}}`)
row = m.ReplaceAllString(row, "{{.ID}}")
r := strings.NewReplacer(
".Src", ".Source",
".Dst", ".Destination",
".ImageID", ".Image",
)
// If inspect type is `image` we need to replace
// certain additional fields like `.Config.HealthCheck`
// but don't want to replace them for other inspect types.
if inspectType == common.ImageType {
r = strings.NewReplacer(
".Src", ".Source",
".Dst", ".Destination",
".ImageID", ".Image",
".Config.Healthcheck", ".HealthCheck",
)
}
return r.Replace(row)
}