From 1fbf24b65b5bd8b28f66cf48c1cb759e6f06dd4b Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Tue, 21 Oct 2025 16:58:03 +0530 Subject: [PATCH 1/2] feat: add `--format` flag to artifact inspect Many commands support the `--format` flag which accept a go template to allow for formatting for certain values, but it is not yet implemented for artifact inspect command. Adding this feature will allow easy formatting in scripts as well as running it on a terminal. This feature is implemented for artifact inspect by taking reference from images and network commands implementation. Fixes: [#27112](https://github.com/containers/podman/issues/27112) Signed-off-by: Akash Yadav --- cmd/podman/artifact/inspect.go | 41 +++++++++++++--- cmd/podman/inspect/inspect.go | 19 ++++++- .../markdown/podman-artifact-inspect.1.md | 49 +++++++++++++++++-- test/e2e/artifact_test.go | 12 +++++ 4 files changed, 108 insertions(+), 13 deletions(-) diff --git a/cmd/podman/artifact/inspect.go b/cmd/podman/artifact/inspect.go index a252e71f65..bfd38c3b77 100644 --- a/cmd/podman/artifact/inspect.go +++ b/cmd/podman/artifact/inspect.go @@ -1,11 +1,15 @@ package artifact import ( + "os" + "github.com/containers/podman/v5/cmd/podman/common" + "github.com/containers/podman/v5/cmd/podman/inspect" "github.com/containers/podman/v5/cmd/podman/registry" "github.com/containers/podman/v5/cmd/podman/utils" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/spf13/cobra" + "go.podman.io/common/pkg/report" ) var ( @@ -13,11 +17,12 @@ var ( Use: "inspect [ARTIFACT...]", Short: "Inspect an OCI artifact", Long: "Provide details on an OCI artifact", - RunE: inspect, + RunE: artifactInspect, Args: cobra.MinimumNArgs(1), ValidArgsFunction: common.AutocompleteArtifacts, Example: `podman artifact inspect quay.io/myimage/myartifact:latest`, } + inspectOpts *entities.InspectOptions ) func init() { @@ -26,10 +31,11 @@ func init() { Parent: artifactCmd, }) - // TODO When things firm up on inspect looks, we can do a format implementation - // flags := inspectCmd.Flags() - // formatFlagName := "format" - // flags.StringVar(&inspectFlag.format, formatFlagName, "", "Format volume output using JSON or a Go template") + inspectOpts = new(entities.InspectOptions) + + flags := inspectCmd.Flags() + formatFlagName := "format" + flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "json", "Format volume output using JSON or a Go template") // This is something we wanted to do but did not seem important enough for initial PR // remoteFlagName := "remote" @@ -37,14 +43,33 @@ func init() { // TODO When the inspect structure has been defined, we need to uncomment and redirect this. Reminder, this // will also need to be reflected in the podman-artifact-inspect man page - // _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{})) + _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.ArtifactInspectReport{})) } -func inspect(_ *cobra.Command, args []string) error { +func artifactInspect(_ *cobra.Command, args []string) error { artifactOptions := entities.ArtifactInspectOptions{} inspectData, err := registry.ImageEngine().ArtifactInspect(registry.Context(), args[0], artifactOptions) if err != nil { return err } - return utils.PrintGenericJSON(inspectData) + + switch { + case report.IsJSON(inspectOpts.Format) || inspectOpts.Format == "": + return utils.PrintGenericJSON(inspectData) + default: + // Landing here implies user has given a custom --format + var rpt *report.Formatter + format := inspect.InspectNormalize(inspectOpts.Format, inspectOpts.Type) + rpt, err = report.New(os.Stdout, "inspect").Parse(report.OriginUser, format) + if err != nil { + return err + } + defer rpt.Flush() + + // Storing and passing inspectData in an array to [Execute] is workaround to avoid getting an error. + // Which seems to happen when type passed to [Execute] is not a slice. + // Error: template: inspect:1:8: executing "inspect" at <.>: range can't iterate over {0x6600020c444 sha256:4bafff5c1b2c950651101d22d3dbf76744446aeb5f79fc926674e0db1083qew456} + data := []any{inspectData} + return rpt.Execute(data) + } } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go index cbba736a61..e9f397e5ba 100644 --- a/cmd/podman/inspect/inspect.go +++ b/cmd/podman/inspect/inspect.go @@ -176,7 +176,7 @@ func (i *inspector) inspect(namesOrIDs []string) error { default: // Landing here implies user has given a custom --format var rpt *report.Formatter - format := inspectNormalize(i.options.Format, i.options.Type) + format := InspectNormalize(i.options.Format, i.options.Type) rpt, err = report.New(os.Stdout, "inspect").Parse(report.OriginUser, format) if err != nil { return err @@ -258,7 +258,22 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]any, return data, allErrs, nil } -func inspectNormalize(row string, inspectType string) string { +// InspectNormalize modifies a given row string based on the specified inspect type. +// It replaces specific field names within the row string for standardization. +// For the `image` inspect type, it includes additional field replacements like `.Config.Healthcheck`. +// +// Parameters: +// - row: The input string that represents a data row to be modified. +// - inspectType: The type of inspection (e.g., "image") to determine specific replacements. +// +// Returns: +// - A new string with the necessary replacements applied based on the inspect type. +// +// InspectNormalize does not need to be exported but to avoid de-duplication of code. We had to export it. +// It can be reverted back once `podman artifact inspect` can use [Inspect] to fetch artifact data instead of +// fetching it itself. +// The reason why we did it in this way can be further read [here](https://github.com/containers/podman/pull/27182#issuecomment-3402465389). +func InspectNormalize(row string, inspectType string) string { m := regexp.MustCompile(`{{\s*\.Id\s*}}`) row = m.ReplaceAllString(row, "{{.ID}}") diff --git a/docs/source/markdown/podman-artifact-inspect.1.md b/docs/source/markdown/podman-artifact-inspect.1.md index 5d4cf47f8d..3c318e01a7 100644 --- a/docs/source/markdown/podman-artifact-inspect.1.md +++ b/docs/source/markdown/podman-artifact-inspect.1.md @@ -20,15 +20,58 @@ annotation using RFC3339Nano format, showing when the artifact was initially cre ## OPTIONS -#### **--help** +#### **--format**, **-f**=*format* -Print usage statement. +Format the output using the given Go template. +The keys of the returned JSON can be used as the values for the --format flag (see examples below). + +Valid placeholders for the Go template are listed below: + +| **Placeholder** | **Description** | +| ------------------------ | -------------------------------------------------- | +| .Artifact ... | Artifact details (nested struct) | +| .Digest | Artifact digest (sha256:+64-char hash) | +| .Manifest ... | Artifact manifest details (struct) | +| .Name | Artifact name (string) | +| .TotalSizeBytes | Total Size of the artifact in bytes | ## EXAMPLES Inspect an OCI image in the local store. -``` + +```shell $ podman artifact inspect quay.io/myartifact/myml:latest +{ + "Manifest": { + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.empty.v1+json", + "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", + "size": 2, + "data": "e30=" + }, + "layers": [ + { + "mediaType": "text/plain; charset=utf-8", + "digest": "sha256:f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2", + "size": 5, + "annotations": { + "org.opencontainers.image.title": "foobar.txt" + } + } + ] + }, + "Name": "quay.io/myartifact/mytxt:latest", + "Digest": "sha256:6c28fa07a5b0a1cee29862c1f6ea38a66df982495b14da2c052427eb628ed8c6" +} +``` + +Inspect artifact digest for the specified artifact: + +```shell +$ podman artifact inspect quay.io/myartifact/mytxt:latest --format {{.Digest}} +sha256:6c28fa07a5b0a1cee29862c1f6ea38a66df982495b14da2c052427eb628ed8c6 ``` ## SEE ALSO diff --git a/test/e2e/artifact_test.go b/test/e2e/artifact_test.go index ce5e0f9fcb..9cd783d41c 100644 --- a/test/e2e/artifact_test.go +++ b/test/e2e/artifact_test.go @@ -636,6 +636,18 @@ var _ = Describe("Podman artifact", func() { // Verify we have 2 layers Expect(a.Manifest.Layers).To(HaveLen(2)) }) + + It("podman artifact inspect with --format", func() { + artifact1File, err := createArtifactFile(4192) + Expect(err).ToNot(HaveOccurred()) + artifact1Name := "localhost/test/artifact1" + addArtifact1 := podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File) + + artifactDigest := addArtifact1.OutputToString() + + podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest, "--format", "{{.Digest}}") + podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest[:12], "--format", "{{.Name}}") + }) }) func digestToFilename(digest string) string { From 339a432dd9a60a34b369cdfec86312e60c874e5e Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Wed, 22 Oct 2025 13:04:13 +0200 Subject: [PATCH 2/2] fix artifact inspect issues Fix remaining CI issues from PR #27182. Signed-off-by: Paul Holzinger --- cmd/podman/artifact/inspect.go | 4 ++-- docs/source/markdown/podman-artifact-inspect.1.md | 6 +++++- test/e2e/artifact_test.go | 6 ++++-- test/system/610-format.bats | 6 +++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/cmd/podman/artifact/inspect.go b/cmd/podman/artifact/inspect.go index bfd38c3b77..ca51612001 100644 --- a/cmd/podman/artifact/inspect.go +++ b/cmd/podman/artifact/inspect.go @@ -14,11 +14,11 @@ import ( var ( inspectCmd = &cobra.Command{ - Use: "inspect [ARTIFACT...]", + Use: "inspect [options] ARTIFACT", Short: "Inspect an OCI artifact", Long: "Provide details on an OCI artifact", RunE: artifactInspect, - Args: cobra.MinimumNArgs(1), + Args: cobra.ExactArgs(1), ValidArgsFunction: common.AutocompleteArtifacts, Example: `podman artifact inspect quay.io/myimage/myartifact:latest`, } diff --git a/docs/source/markdown/podman-artifact-inspect.1.md b/docs/source/markdown/podman-artifact-inspect.1.md index 3c318e01a7..54e765da01 100644 --- a/docs/source/markdown/podman-artifact-inspect.1.md +++ b/docs/source/markdown/podman-artifact-inspect.1.md @@ -4,7 +4,7 @@ podman\-artifact\-inspect - Inspect an OCI artifact ## SYNOPSIS -**podman artifact inspect** [*name*] ... +**podman artifact inspect** *name* ## DESCRIPTION @@ -35,6 +35,10 @@ Valid placeholders for the Go template are listed below: | .Name | Artifact name (string) | | .TotalSizeBytes | Total Size of the artifact in bytes | +#### **--help**, **-h** + +Print usage statement + ## EXAMPLES Inspect an OCI image in the local store. diff --git a/test/e2e/artifact_test.go b/test/e2e/artifact_test.go index 9cd783d41c..6bc22a79dd 100644 --- a/test/e2e/artifact_test.go +++ b/test/e2e/artifact_test.go @@ -645,8 +645,10 @@ var _ = Describe("Podman artifact", func() { artifactDigest := addArtifact1.OutputToString() - podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest, "--format", "{{.Digest}}") - podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest[:12], "--format", "{{.Name}}") + session := podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest, "--format", "{{.Digest}}") + Expect(session.OutputToString()).To(Equal("sha256:" + artifactDigest)) + session = podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest[:12], "-f", "{{.Name}}") + Expect(session.OutputToString()).To(Equal(artifact1Name)) }) }) diff --git a/test/system/610-format.bats b/test/system/610-format.bats index a0d7e119d1..35a5cf7d64 100644 --- a/test/system/610-format.bats +++ b/test/system/610-format.bats @@ -12,6 +12,7 @@ function teardown() { run_podman '?' secret rm "s-$(safename)" run_podman '?' pod rm -f "p-$(safename)" run_podman '?' rm -f -t0 "c-$(safename)" + run_podman '?' artifact rm "a-$(safename)" basic_teardown } @@ -125,10 +126,12 @@ function check_subcommand() { ctrname="c-$(safename)" podname="p-$(safename)" secretname="s-$(safename)" + artifactname="a-$(safename)" # Setup: some commands need a container, pod, secret, ... run_podman run -d --name $ctrname $IMAGE top run_podman pod create $podname run_podman secret create $secretname /etc/hosts + run_podman artifact add $artifactname /etc/hosts # For 'search' and 'image search': if local cache registry is available, # use it. This bypasses quay, and thus prevents flakes. @@ -147,7 +150,7 @@ image inspect | $IMAGE container inspect | $ctrname inspect | $ctrname - +artifact inspect | $artifactname volume inspect | -a secret inspect | $secretname network inspect | podman @@ -200,6 +203,7 @@ stats | --no-stream run_podman rm -f -t0 $ctrname run_podman secret rm $secretname run_podman '?' machine rm -f $machinename + run_podman artifact rm $artifactname # Make sure there are no leftover commands in our table - this would # indicate a typo in the table, or a flaw in our logic such that