Skip to content

Commit ef9b936

Browse files
committed
podman artifact
the podman artifact verb is used to manage OCI artifacts. the following verbs were added to `podman artifact`: * add * inspect * ls * pull * push * rm Notable items with this PR: * all artifact commands and their output are subject to change. i.e. consider all of this tech preview * there is no way to add a file to an artifact that already exists in the store. you would need to delete and recreate the artifact. * all references to artifacts names should be fully qualified names in the form of repo/name:tag (i.e. quay.io/artifact/foobar:latest) * i understand that we will likely want to be able to attribute things like arch, etc to artifact files. this function is not available yet. Many thanks to Paul Holzinger for autocompletion PRs and review PRs that fixed issues early on. Also fix up some Args function to specify the correct number of args. Signed-off-by: Paul Holzinger <[email protected]> Signed-off-by: Brent Baude <[email protected]>
1 parent 04e6488 commit ef9b936

29 files changed

+2344
-64
lines changed

cmd/podman/artifact/add.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package artifact
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/containers/podman/v5/cmd/podman/common"
7+
"github.com/containers/podman/v5/cmd/podman/registry"
8+
"github.com/containers/podman/v5/pkg/domain/entities"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var (
13+
addCmd = &cobra.Command{
14+
Use: "add ARTIFACT PATH [...PATH]",
15+
Short: "Add an OCI artifact to the local store",
16+
Long: "Add an OCI artifact to the local store from the local filesystem",
17+
RunE: add,
18+
Args: cobra.MinimumNArgs(2),
19+
ValidArgsFunction: common.AutocompleteArtifactAdd,
20+
Example: `podman artifact add quay.io/myimage/myartifact:latest /tmp/foobar.txt`,
21+
}
22+
)
23+
24+
func init() {
25+
registry.Commands = append(registry.Commands, registry.CliCommand{
26+
Command: addCmd,
27+
Parent: artifactCmd,
28+
})
29+
30+
// TODO When the inspect structure has been defined, we need to uncommand and redirect this. Reminder, this
31+
// will also need to be reflected in the podman-artifact-inspect man page
32+
// _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{}))
33+
}
34+
35+
func add(cmd *cobra.Command, args []string) error {
36+
report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], entities.ArtifactAddoptions{})
37+
if err != nil {
38+
return err
39+
}
40+
fmt.Println(report.ArtifactDigest.Encoded())
41+
return nil
42+
}

cmd/podman/artifact/artifact.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package artifact
2+
3+
import (
4+
"github.com/containers/podman/v5/cmd/podman/registry"
5+
"github.com/containers/podman/v5/cmd/podman/validate"
6+
"github.com/spf13/cobra"
7+
)
8+
9+
var (
10+
json = registry.JSONLibrary()
11+
// Command: podman _artifact_
12+
artifactCmd = &cobra.Command{
13+
Use: "artifact",
14+
Short: "Manage OCI artifacts",
15+
Long: "Manage OCI artifacts",
16+
//PersistentPreRunE: validate.NoOp,
17+
RunE: validate.SubCommandExists,
18+
}
19+
)
20+
21+
func init() {
22+
if !registry.IsRemote() {
23+
registry.Commands = append(registry.Commands, registry.CliCommand{
24+
Command: artifactCmd,
25+
})
26+
}
27+
}

cmd/podman/artifact/inspect.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package artifact
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/containers/podman/v5/cmd/podman/common"
8+
"github.com/containers/podman/v5/cmd/podman/registry"
9+
"github.com/containers/podman/v5/pkg/domain/entities"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var (
14+
inspectCmd = &cobra.Command{
15+
Use: "inspect [ARTIFACT...]",
16+
Short: "Inspect an OCI artifact",
17+
Long: "Provide details on an OCI artifact",
18+
RunE: inspect,
19+
Args: cobra.MinimumNArgs(1),
20+
ValidArgsFunction: common.AutocompleteArtifacts,
21+
Example: `podman artifact inspect quay.io/myimage/myartifact:latest`,
22+
}
23+
inspectFlag = inspectFlagType{}
24+
)
25+
26+
type inspectFlagType struct {
27+
format string
28+
remote bool
29+
}
30+
31+
func init() {
32+
registry.Commands = append(registry.Commands, registry.CliCommand{
33+
Command: inspectCmd,
34+
Parent: artifactCmd,
35+
})
36+
37+
// TODO When things firm up on inspect looks, we can do a format implementation
38+
// flags := inspectCmd.Flags()
39+
// formatFlagName := "format"
40+
// flags.StringVar(&inspectFlag.format, formatFlagName, "", "Format volume output using JSON or a Go template")
41+
42+
// This is something we wanted to do but did not seem important enough for initial PR
43+
// remoteFlagName := "remote"
44+
// flags.BoolVar(&inspectFlag.remote, remoteFlagName, false, "Inspect the image on a container image registry")
45+
46+
// TODO When the inspect structure has been defined, we need to uncomment and redirect this. Reminder, this
47+
// will also need to be reflected in the podman-artifact-inspect man page
48+
// _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{}))
49+
}
50+
51+
func inspect(cmd *cobra.Command, args []string) error {
52+
if inspectFlag.remote {
53+
return fmt.Errorf("not implemented")
54+
}
55+
56+
if inspectFlag.format != "" {
57+
return fmt.Errorf("not implemented")
58+
}
59+
60+
artifactOptions := entities.ArtifactInspectOptions{}
61+
inspectData, err := registry.ImageEngine().ArtifactInspect(registry.GetContext(), args[0], artifactOptions)
62+
if err != nil {
63+
return err
64+
}
65+
return printJSON(inspectData)
66+
}
67+
68+
func printJSON(data interface{}) error {
69+
enc := json.NewEncoder(os.Stdout)
70+
// by default, json marshallers will force utf=8 from
71+
// a string.
72+
enc.SetEscapeHTML(false)
73+
enc.SetIndent("", " ")
74+
return enc.Encode(data)
75+
}

cmd/podman/artifact/list.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package artifact
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/containers/common/pkg/completion"
8+
"github.com/containers/common/pkg/report"
9+
"github.com/containers/image/v5/docker/reference"
10+
"github.com/containers/podman/v5/cmd/podman/common"
11+
"github.com/containers/podman/v5/cmd/podman/registry"
12+
"github.com/containers/podman/v5/cmd/podman/validate"
13+
"github.com/containers/podman/v5/pkg/domain/entities"
14+
"github.com/docker/go-units"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
var (
19+
ListCmd = &cobra.Command{
20+
Use: "ls [options]",
21+
Aliases: []string{"list"},
22+
Short: "List OCI artifacts",
23+
Long: "List OCI artifacts in local store",
24+
RunE: list,
25+
Args: validate.NoArgs,
26+
ValidArgsFunction: completion.AutocompleteNone,
27+
Example: `podman artifact ls`,
28+
}
29+
listFlag = listFlagType{}
30+
)
31+
32+
type listFlagType struct {
33+
format string
34+
}
35+
36+
type artifactListOutput struct {
37+
Digest string
38+
Repository string
39+
Size string
40+
Tag string
41+
}
42+
43+
var (
44+
defaultArtifactListOutputFormat = "{{range .}}{{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.Size}}\n{{end -}}"
45+
)
46+
47+
func init() {
48+
registry.Commands = append(registry.Commands, registry.CliCommand{
49+
Command: ListCmd,
50+
Parent: artifactCmd,
51+
})
52+
flags := ListCmd.Flags()
53+
formatFlagName := "format"
54+
flags.StringVar(&listFlag.format, formatFlagName, defaultArtifactListOutputFormat, "Format volume output using JSON or a Go template")
55+
_ = ListCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&artifactListOutput{}))
56+
// TODO When the inspect structure has been defined, we need to uncomment and redirect this. Reminder, this
57+
// will also need to be reflected in the podman-artifact-inspect man page
58+
// _ = inspectCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&machine.InspectInfo{}))
59+
}
60+
61+
func list(cmd *cobra.Command, _ []string) error {
62+
reports, err := registry.ImageEngine().ArtifactList(registry.GetContext(), entities.ArtifactListOptions{})
63+
if err != nil {
64+
return err
65+
}
66+
67+
return outputTemplate(cmd, reports)
68+
}
69+
70+
func outputTemplate(cmd *cobra.Command, lrs []*entities.ArtifactListReport) error {
71+
var err error
72+
artifacts := make([]artifactListOutput, 0)
73+
for _, lr := range lrs {
74+
var (
75+
tag string
76+
)
77+
artifactName, err := lr.Artifact.GetName()
78+
if err != nil {
79+
return err
80+
}
81+
repo, err := reference.Parse(artifactName)
82+
if err != nil {
83+
return err
84+
}
85+
named, ok := repo.(reference.Named)
86+
if !ok {
87+
return fmt.Errorf("%q is an invalid artifact name", artifactName)
88+
}
89+
if tagged, ok := named.(reference.Tagged); ok {
90+
tag = tagged.Tag()
91+
}
92+
93+
// Note: Right now we only support things that are single manifests
94+
// We should certainly expand this support for things like arch, etc
95+
// as we move on
96+
artifactDigest, err := lr.Artifact.GetDigest()
97+
if err != nil {
98+
return err
99+
}
100+
// TODO when we default to shorter ids, i would foresee a switch
101+
// like images that will show the full ids.
102+
artifacts = append(artifacts, artifactListOutput{
103+
Digest: artifactDigest.Encoded(),
104+
Repository: named.Name(),
105+
Size: units.HumanSize(float64(lr.Artifact.TotalSizeBytes())),
106+
Tag: tag,
107+
})
108+
}
109+
110+
headers := report.Headers(artifactListOutput{}, map[string]string{
111+
"REPOSITORY": "REPOSITORY",
112+
"Tag": "TAG",
113+
"Size": "SIZE",
114+
"Digest": "DIGEST",
115+
})
116+
117+
rpt := report.New(os.Stdout, cmd.Name())
118+
defer rpt.Flush()
119+
120+
switch {
121+
case cmd.Flag("format").Changed:
122+
rpt, err = rpt.Parse(report.OriginUser, listFlag.format)
123+
default:
124+
rpt, err = rpt.Parse(report.OriginPodman, listFlag.format)
125+
}
126+
if err != nil {
127+
return err
128+
}
129+
130+
if rpt.RenderHeaders {
131+
if err := rpt.Execute(headers); err != nil {
132+
return fmt.Errorf("failed to write report column headers: %w", err)
133+
}
134+
}
135+
return rpt.Execute(artifacts)
136+
}

0 commit comments

Comments
 (0)