mirror of
https://github.com/containers/podman.git
synced 2025-11-29 01:28:22 +08:00
Merge pull request #27182 from skyraider256526/main
feat: add `--format` flag to artifact inspect
This commit is contained in:
@@ -1,23 +1,28 @@
|
|||||||
package artifact
|
package artifact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/containers/podman/v5/cmd/podman/common"
|
"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/registry"
|
||||||
"github.com/containers/podman/v5/cmd/podman/utils"
|
"github.com/containers/podman/v5/cmd/podman/utils"
|
||||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"go.podman.io/common/pkg/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inspectCmd = &cobra.Command{
|
inspectCmd = &cobra.Command{
|
||||||
Use: "inspect [ARTIFACT...]",
|
Use: "inspect [options] ARTIFACT",
|
||||||
Short: "Inspect an OCI artifact",
|
Short: "Inspect an OCI artifact",
|
||||||
Long: "Provide details on an OCI artifact",
|
Long: "Provide details on an OCI artifact",
|
||||||
RunE: inspect,
|
RunE: artifactInspect,
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: common.AutocompleteArtifacts,
|
ValidArgsFunction: common.AutocompleteArtifacts,
|
||||||
Example: `podman artifact inspect quay.io/myimage/myartifact:latest`,
|
Example: `podman artifact inspect quay.io/myimage/myartifact:latest`,
|
||||||
}
|
}
|
||||||
|
inspectOpts *entities.InspectOptions
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -26,10 +31,11 @@ func init() {
|
|||||||
Parent: artifactCmd,
|
Parent: artifactCmd,
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO When things firm up on inspect looks, we can do a format implementation
|
inspectOpts = new(entities.InspectOptions)
|
||||||
// flags := inspectCmd.Flags()
|
|
||||||
// formatFlagName := "format"
|
flags := inspectCmd.Flags()
|
||||||
// flags.StringVar(&inspectFlag.format, formatFlagName, "", "Format volume output using JSON or a Go template")
|
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
|
// This is something we wanted to do but did not seem important enough for initial PR
|
||||||
// remoteFlagName := "remote"
|
// 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
|
// 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
|
// 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{}
|
artifactOptions := entities.ArtifactInspectOptions{}
|
||||||
inspectData, err := registry.ImageEngine().ArtifactInspect(registry.Context(), args[0], artifactOptions)
|
inspectData, err := registry.ImageEngine().ArtifactInspect(registry.Context(), args[0], artifactOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ func (i *inspector) inspect(namesOrIDs []string) error {
|
|||||||
default:
|
default:
|
||||||
// Landing here implies user has given a custom --format
|
// Landing here implies user has given a custom --format
|
||||||
var rpt *report.Formatter
|
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)
|
rpt, err = report.New(os.Stdout, "inspect").Parse(report.OriginUser, format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -258,7 +258,22 @@ func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]any,
|
|||||||
return data, allErrs, nil
|
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*}}`)
|
m := regexp.MustCompile(`{{\s*\.Id\s*}}`)
|
||||||
row = m.ReplaceAllString(row, "{{.ID}}")
|
row = m.ReplaceAllString(row, "{{.ID}}")
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
podman\-artifact\-inspect - Inspect an OCI artifact
|
podman\-artifact\-inspect - Inspect an OCI artifact
|
||||||
|
|
||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
**podman artifact inspect** [*name*] ...
|
**podman artifact inspect** *name*
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
|
|
||||||
@@ -20,15 +20,62 @@ annotation using RFC3339Nano format, showing when the artifact was initially cre
|
|||||||
|
|
||||||
## OPTIONS
|
## 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 |
|
||||||
|
|
||||||
|
#### **--help**, **-h**
|
||||||
|
|
||||||
|
Print usage statement
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
Inspect an OCI image in the local store.
|
Inspect an OCI image in the local store.
|
||||||
```
|
|
||||||
|
```shell
|
||||||
$ podman artifact inspect quay.io/myartifact/myml:latest
|
$ 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
|
## SEE ALSO
|
||||||
|
|||||||
@@ -668,6 +668,20 @@ var _ = Describe("Podman artifact", func() {
|
|||||||
// Verify we have 2 layers
|
// Verify we have 2 layers
|
||||||
Expect(a.Manifest.Layers).To(HaveLen(2))
|
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()
|
||||||
|
|
||||||
|
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))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
func digestToFilename(digest string) string {
|
func digestToFilename(digest string) string {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ function teardown() {
|
|||||||
run_podman '?' secret rm "s-$(safename)"
|
run_podman '?' secret rm "s-$(safename)"
|
||||||
run_podman '?' pod rm -f "p-$(safename)"
|
run_podman '?' pod rm -f "p-$(safename)"
|
||||||
run_podman '?' rm -f -t0 "c-$(safename)"
|
run_podman '?' rm -f -t0 "c-$(safename)"
|
||||||
|
run_podman '?' artifact rm "a-$(safename)"
|
||||||
|
|
||||||
basic_teardown
|
basic_teardown
|
||||||
}
|
}
|
||||||
@@ -125,10 +126,12 @@ function check_subcommand() {
|
|||||||
ctrname="c-$(safename)"
|
ctrname="c-$(safename)"
|
||||||
podname="p-$(safename)"
|
podname="p-$(safename)"
|
||||||
secretname="s-$(safename)"
|
secretname="s-$(safename)"
|
||||||
|
artifactname="a-$(safename)"
|
||||||
# Setup: some commands need a container, pod, secret, ...
|
# Setup: some commands need a container, pod, secret, ...
|
||||||
run_podman run -d --name $ctrname $IMAGE top
|
run_podman run -d --name $ctrname $IMAGE top
|
||||||
run_podman pod create $podname
|
run_podman pod create $podname
|
||||||
run_podman secret create $secretname /etc/hosts
|
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,
|
# For 'search' and 'image search': if local cache registry is available,
|
||||||
# use it. This bypasses quay, and thus prevents flakes.
|
# use it. This bypasses quay, and thus prevents flakes.
|
||||||
@@ -147,7 +150,7 @@ image inspect | $IMAGE
|
|||||||
container inspect | $ctrname
|
container inspect | $ctrname
|
||||||
inspect | $ctrname
|
inspect | $ctrname
|
||||||
|
|
||||||
|
artifact inspect | $artifactname
|
||||||
volume inspect | -a
|
volume inspect | -a
|
||||||
secret inspect | $secretname
|
secret inspect | $secretname
|
||||||
network inspect | podman
|
network inspect | podman
|
||||||
@@ -200,6 +203,7 @@ stats | --no-stream
|
|||||||
run_podman rm -f -t0 $ctrname
|
run_podman rm -f -t0 $ctrname
|
||||||
run_podman secret rm $secretname
|
run_podman secret rm $secretname
|
||||||
run_podman '?' machine rm -f $machinename
|
run_podman '?' machine rm -f $machinename
|
||||||
|
run_podman artifact rm $artifactname
|
||||||
|
|
||||||
# Make sure there are no leftover commands in our table - this would
|
# 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
|
# indicate a typo in the table, or a flaw in our logic such that
|
||||||
|
|||||||
Reference in New Issue
Block a user