mirror of https://github.com/containers/podman.git
fix podman pod inspect to support multiple pods
Just like the other inspect commands `podman pod inspect p1 p2` should return the json for both. To correctly implement this we follow the container inspect logic, this allows use to reuse the global inspect command. Note: To not break the existing single pod output format for podman pod inspect I added a pod-legacy inspect type. This is only used to make sure we will print the pod as single json and not an array like for the other commands. We cannot use the pod type since podman inspect --type pod did return an array and we should not break that as well. Fixes #15674 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
parent
e46bcd72f8
commit
d10e77e1bc
|
@ -11,6 +11,10 @@ const (
|
|||
NetworkType = "network"
|
||||
// PodType is the pod type.
|
||||
PodType = "pod"
|
||||
// PodLegacyType is the pod type for backwards compatibility with the old pod inspect code.
|
||||
// This allows us to use the shared inspect code but still provide the correct output format
|
||||
// when podman pod inspect was called.
|
||||
PodLegacyType = "pod-legacy"
|
||||
// VolumeType is the volume type
|
||||
VolumeType = "volume"
|
||||
)
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"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/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -55,18 +54,10 @@ 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 common.ImageType, common.ContainerType, common.AllType, common.PodType, common.NetworkType, common.VolumeType:
|
||||
// Valid types.
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid type %q: must be %q, %q, %q, %q, %q, or %q", options.Type,
|
||||
common.ImageType, common.ContainerType, common.PodType, common.NetworkType, common.VolumeType, common.AllType)
|
||||
}
|
||||
if options.Type == common.ImageType {
|
||||
if options.Latest {
|
||||
return nil, fmt.Errorf("latest is not supported for type %q", common.ImageType)
|
||||
|
@ -78,15 +69,10 @@ func newInspector(options entities.InspectOptions) (*inspector, error) {
|
|||
if options.Type == common.PodType && options.Size {
|
||||
return nil, fmt.Errorf("size is not supported for type %q", common.PodType)
|
||||
}
|
||||
podOpts := entities.PodInspectOptions{
|
||||
Latest: options.Latest,
|
||||
Format: options.Format,
|
||||
}
|
||||
return &inspector{
|
||||
containerEngine: registry.ContainerEngine(),
|
||||
imageEngine: registry.ImageEngine(),
|
||||
options: options,
|
||||
podOptions: podOpts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -140,34 +126,16 @@ func (i *inspector) inspect(namesOrIDs []string) error {
|
|||
for i := range ctrData {
|
||||
data = append(data, ctrData[i])
|
||||
}
|
||||
case common.PodType:
|
||||
for _, pod := range namesOrIDs {
|
||||
i.podOptions.NameOrID = pod
|
||||
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) {
|
||||
errs = []error{err}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
errs = nil
|
||||
data = append(data, podData)
|
||||
}
|
||||
case common.PodType, common.PodLegacyType:
|
||||
podData, allErrs, err := i.containerEngine.PodInspect(ctx, namesOrIDs, i.options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 {
|
||||
if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) {
|
||||
errs = []error{err}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
errs = nil
|
||||
data = append(data, podData)
|
||||
}
|
||||
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 {
|
||||
|
@ -198,7 +166,14 @@ func (i *inspector) inspect(namesOrIDs []string) error {
|
|||
var err error
|
||||
switch {
|
||||
case report.IsJSON(i.options.Format) || i.options.Format == "":
|
||||
err = printJSON(data)
|
||||
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
|
||||
row := inspectNormalize(i.options.Format, tmpType)
|
||||
|
@ -221,7 +196,7 @@ func (i *inspector) inspect(namesOrIDs []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func printJSON(data []interface{}) error {
|
||||
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 <,>, &&.
|
||||
|
@ -282,14 +257,13 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]inte
|
|||
data = append(data, networkData[0])
|
||||
continue
|
||||
}
|
||||
i.podOptions.NameOrID = name
|
||||
podData, err := i.containerEngine.PodInspect(ctx, i.podOptions)
|
||||
|
||||
podData, errs, err := i.containerEngine.PodInspect(ctx, []string{name}, i.options)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), define.ErrNoSuchPod.Error()) {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else {
|
||||
data = append(data, podData)
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
data = append(data, podData[0])
|
||||
continue
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
|
|
|
@ -1,23 +1,14 @@
|
|||
package pods
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/containers/common/pkg/report"
|
||||
"github.com/containers/podman/v4/cmd/podman/common"
|
||||
"github.com/containers/podman/v4/cmd/podman/inspect"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
inspectOptions = entities.PodInspectOptions{}
|
||||
)
|
||||
|
||||
var (
|
||||
inspectDescription = `Display the configuration for a pod by name or id
|
||||
|
||||
|
@ -27,10 +18,12 @@ var (
|
|||
Use: "inspect [options] POD [POD...]",
|
||||
Short: "Displays a pod configuration",
|
||||
Long: inspectDescription,
|
||||
RunE: inspect,
|
||||
RunE: inspectExec,
|
||||
ValidArgsFunction: common.AutocompletePods,
|
||||
Example: `podman pod inspect podID`,
|
||||
}
|
||||
|
||||
inspectOpts = &entities.InspectOptions{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -41,40 +34,15 @@ func init() {
|
|||
flags := inspectCmd.Flags()
|
||||
|
||||
formatFlagName := "format"
|
||||
flags.StringVarP(&inspectOptions.Format, formatFlagName, "f", "json", "Format the output to a Go template or json")
|
||||
flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "json", "Format the output to a Go template or json")
|
||||
_ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.PodInspectReport{}))
|
||||
|
||||
validate.AddLatestFlag(inspectCmd, &inspectOptions.Latest)
|
||||
validate.AddLatestFlag(inspectCmd, &inspectOpts.Latest)
|
||||
}
|
||||
|
||||
func inspect(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 && !inspectOptions.Latest {
|
||||
return errors.New("you must provide the name or id of a running pod")
|
||||
}
|
||||
if len(args) > 0 && inspectOptions.Latest {
|
||||
return errors.New("--latest and containers cannot be used together")
|
||||
}
|
||||
|
||||
if !inspectOptions.Latest {
|
||||
inspectOptions.NameOrID = args[0]
|
||||
}
|
||||
responses, err := registry.ContainerEngine().PodInspect(context.Background(), inspectOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if report.IsJSON(inspectOptions.Format) {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(responses)
|
||||
}
|
||||
|
||||
// Cannot use report.New() as it enforces {{range .}} for OriginUser templates
|
||||
tmpl := template.New(cmd.Name()).Funcs(template.FuncMap(report.DefaultFuncs))
|
||||
format := report.NormalizeFormat(inspectOptions.Format)
|
||||
tmpl, err = tmpl.Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpl.Execute(os.Stdout, *responses)
|
||||
func inspectExec(cmd *cobra.Command, args []string) error {
|
||||
// We need backwards compat with the old podman pod inspect behavior.
|
||||
// https://github.com/containers/podman/pull/15675
|
||||
inspectOpts.Type = common.PodLegacyType
|
||||
return inspect.Inspect(args, *inspectOpts)
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ type ContainerEngine interface {
|
|||
PodCreate(ctx context.Context, specg PodSpec) (*PodCreateReport, error)
|
||||
PodClone(ctx context.Context, podClone PodCloneOptions) (*PodCloneReport, error)
|
||||
PodExists(ctx context.Context, nameOrID string) (*BoolReport, error)
|
||||
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
|
||||
PodInspect(ctx context.Context, namesOrID []string, options InspectOptions) ([]*PodInspectReport, []error, error)
|
||||
PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error)
|
||||
PodLogs(ctx context.Context, pod string, options PodLogsOptions) error
|
||||
PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error)
|
||||
|
|
|
@ -438,15 +438,6 @@ type PodPSOptions struct {
|
|||
Sort string
|
||||
}
|
||||
|
||||
type PodInspectOptions struct {
|
||||
Latest bool
|
||||
|
||||
// Options for the API.
|
||||
NameOrID string
|
||||
|
||||
Format string
|
||||
}
|
||||
|
||||
type PodInspectReport struct {
|
||||
*define.InspectPodData
|
||||
}
|
||||
|
|
|
@ -505,23 +505,49 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti
|
|||
return reports, nil
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodInspectOptions) (*entities.PodInspectReport, error) {
|
||||
var (
|
||||
pod *libpod.Pod
|
||||
err error
|
||||
)
|
||||
// Look up the pod.
|
||||
func (ic *ContainerEngine) PodInspect(ctx context.Context, nameOrIDs []string, options entities.InspectOptions) ([]*entities.PodInspectReport, []error, error) {
|
||||
if options.Latest {
|
||||
pod, err = ic.Libpod.GetLatestPod()
|
||||
} else {
|
||||
pod, err = ic.Libpod.LookupPod(options.NameOrID)
|
||||
pod, err := ic.Libpod.GetLatestPod()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
inspect, err := pod.Inspect()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return []*entities.PodInspectReport{
|
||||
{
|
||||
InspectPodData: inspect,
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to look up requested container: %w", err)
|
||||
|
||||
var errs []error
|
||||
podReport := make([]*entities.PodInspectReport, 0, len(nameOrIDs))
|
||||
for _, name := range nameOrIDs {
|
||||
pod, err := ic.Libpod.LookupPod(name)
|
||||
if err != nil {
|
||||
// ErrNoSuchPod is non-fatal, other errors will be
|
||||
// treated as fatal.
|
||||
if errors.Is(err, define.ErrNoSuchPod) {
|
||||
errs = append(errs, fmt.Errorf("no such pod %s", name))
|
||||
continue
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
inspect, err := pod.Inspect()
|
||||
if err != nil {
|
||||
// ErrNoSuchPod is non-fatal, other errors will be
|
||||
// treated as fatal.
|
||||
if errors.Is(err, define.ErrNoSuchPod) {
|
||||
errs = append(errs, fmt.Errorf("no such pod %s", name))
|
||||
continue
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
podReport = append(podReport, &entities.PodInspectReport{InspectPodData: inspect})
|
||||
}
|
||||
inspect, err := pod.Inspect()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &entities.PodInspectReport{InspectPodData: inspect}, nil
|
||||
return podReport, errs, nil
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ package tunnel
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/podman/v4/libpod/define"
|
||||
"github.com/containers/podman/v4/pkg/bindings/pods"
|
||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||
"github.com/containers/podman/v4/pkg/errorhandling"
|
||||
"github.com/containers/podman/v4/pkg/util"
|
||||
)
|
||||
|
||||
|
@ -223,14 +225,25 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, opts entities.PodPSOptions
|
|||
return pods.List(ic.ClientCtx, options)
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodInspectOptions) (*entities.PodInspectReport, error) {
|
||||
switch {
|
||||
case options.Latest:
|
||||
return nil, errors.New("latest is not supported")
|
||||
case options.NameOrID == "":
|
||||
return nil, errors.New("NameOrID must be specified")
|
||||
func (ic *ContainerEngine) PodInspect(ctx context.Context, namesOrIDs []string, options entities.InspectOptions) ([]*entities.PodInspectReport, []error, error) {
|
||||
var errs []error
|
||||
podReport := make([]*entities.PodInspectReport, 0, len(namesOrIDs))
|
||||
for _, name := range namesOrIDs {
|
||||
inspect, err := pods.Inspect(ic.ClientCtx, name, nil)
|
||||
if err != nil {
|
||||
errModel, ok := err.(*errorhandling.ErrorModel)
|
||||
if !ok {
|
||||
return nil, nil, err
|
||||
}
|
||||
if errModel.ResponseCode == 404 {
|
||||
errs = append(errs, fmt.Errorf("no such pod %q", name))
|
||||
continue
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
podReport = append(podReport, inspect)
|
||||
}
|
||||
return pods.Inspect(ic.ClientCtx, options.NameOrID, nil)
|
||||
return podReport, errs, nil
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) PodStats(ctx context.Context, namesOrIds []string, opts entities.PodStatsOptions) ([]*entities.PodStatsReport, error) {
|
||||
|
|
|
@ -571,7 +571,7 @@ func (s *PodmanSessionIntegration) InspectContainerToJSON() []define.InspectCont
|
|||
func (s *PodmanSessionIntegration) InspectPodToJSON() define.InspectPodData {
|
||||
var i define.InspectPodData
|
||||
err := jsoniter.Unmarshal(s.Out.Contents(), &i)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
return i
|
||||
}
|
||||
|
||||
|
|
|
@ -118,4 +118,21 @@ var _ = Describe("Podman pod inspect", func() {
|
|||
|
||||
Expect(inspectOut.OutputToString()).To(ContainSubstring(macAddr))
|
||||
})
|
||||
|
||||
It("podman inspect two pods", func() {
|
||||
_, ec, podid1 := podmanTest.CreatePod(nil)
|
||||
Expect(ec).To(Equal(0))
|
||||
|
||||
_, ec, podid2 := podmanTest.CreatePod(nil)
|
||||
Expect(ec).To(Equal(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"pod", "inspect", podid1, podid2})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect).Should(Exit(0))
|
||||
Expect(inspect.OutputToString()).To(BeValidJSON())
|
||||
podData := inspect.InspectPodArrToJSON()
|
||||
Expect(podData).To(HaveLen(2))
|
||||
Expect(podData[0]).To(HaveField("ID", podid1))
|
||||
Expect(podData[1]).To(HaveField("ID", podid2))
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue