Skip to content

Commit 5b328ca

Browse files
mheonflouthoc
andcommitted
Initial implementation of podman quadlet commands
This adds `podman quadlet list`, `podman quadlet install`, `podman quadlet rm` and `podman quadlet print`. Signed-off-by: Matt Heon <[email protected]> Co-authored-by: flouthoc <[email protected]> Signed-off-by: flouthoc <[email protected]>
1 parent f235d47 commit 5b328ca

28 files changed

+2223
-456
lines changed

cmd/podman/common/completion.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,28 @@ func getPods(cmd *cobra.Command, toComplete string, cType completeType, statuses
173173
return suggestions, cobra.ShellCompDirectiveNoFileComp
174174
}
175175

176+
func getQuadlets(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) {
177+
suggestions := []string{}
178+
lsOpts := entities.QuadletListOptions{}
179+
engine, err := setupContainerEngine(cmd)
180+
if err != nil {
181+
cobra.CompErrorln(err.Error())
182+
return nil, cobra.ShellCompDirectiveNoFileComp
183+
}
184+
quadlets, err := engine.QuadletList(registry.Context(), lsOpts)
185+
if err != nil {
186+
cobra.CompErrorln(err.Error())
187+
return nil, cobra.ShellCompDirectiveNoFileComp
188+
}
189+
190+
for _, q := range quadlets {
191+
if strings.HasPrefix(q.Name, toComplete) {
192+
suggestions = append(suggestions, q.Name)
193+
}
194+
}
195+
return suggestions, cobra.ShellCompDirectiveNoFileComp
196+
}
197+
176198
func getVolumes(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) {
177199
suggestions := []string{}
178200
lsOpts := entities.VolumeListOptions{}
@@ -730,6 +752,14 @@ func AutocompleteImages(cmd *cobra.Command, args []string, toComplete string) ([
730752
return getImages(cmd, toComplete)
731753
}
732754

755+
// AutocompleteQuadlets - Autocomplete quadlets.
756+
func AutocompleteQuadlets(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
757+
if !validCurrentCmdLine(cmd, args, toComplete) {
758+
return nil, cobra.ShellCompDirectiveNoFileComp
759+
}
760+
return getQuadlets(cmd, toComplete)
761+
}
762+
733763
// AutocompleteManifestListAndMember - Autocomplete names of manifest lists and digests of items in them.
734764
func AutocompleteManifestListAndMember(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
735765
if !validCurrentCmdLine(cmd, args, toComplete) {
@@ -827,6 +857,11 @@ func AutocompleteDefaultOneArg(cmd *cobra.Command, args []string, toComplete str
827857
return nil, cobra.ShellCompDirectiveNoFileComp
828858
}
829859

860+
// AutocompleteDefaultManyArg - Autocomplete for many args.
861+
func AutocompleteDefaultManyArg(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
862+
return nil, cobra.ShellCompDirectiveDefault
863+
}
864+
830865
// AutocompleteCommitCommand - Autocomplete podman commit command args.
831866
func AutocompleteCommitCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
832867
if !validCurrentCmdLine(cmd, args, toComplete) {
@@ -1775,6 +1810,14 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string)
17751810
return completeKeyValues(toComplete, kv)
17761811
}
17771812

1813+
// AutocompleteQuadletFilters - Autocomplete quadlet filter options.
1814+
func AutocompleteQuadletFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
1815+
kv := keyValueCompletion{
1816+
"name=": func(s string) ([]string, cobra.ShellCompDirective) { return getQuadlets(cmd, s) },
1817+
}
1818+
return completeKeyValues(toComplete, kv)
1819+
}
1820+
17781821
// AutocompletePodPsFilters - Autocomplete pod ps filter options.
17791822
func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
17801823
kv := keyValueCompletion{

cmd/podman/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ import (
1919
_ "github.com/containers/podman/v5/cmd/podman/manifest"
2020
_ "github.com/containers/podman/v5/cmd/podman/networks"
2121
_ "github.com/containers/podman/v5/cmd/podman/pods"
22+
_ "github.com/containers/podman/v5/cmd/podman/quadlet"
2223
"github.com/containers/podman/v5/cmd/podman/registry"
2324
_ "github.com/containers/podman/v5/cmd/podman/secrets"
2425
_ "github.com/containers/podman/v5/cmd/podman/system"
2526
_ "github.com/containers/podman/v5/cmd/podman/system/connection"
2627
"github.com/containers/podman/v5/cmd/podman/validate"
2728
_ "github.com/containers/podman/v5/cmd/podman/volumes"
2829
"github.com/containers/podman/v5/pkg/domain/entities"
30+
"github.com/containers/podman/v5/pkg/logiface"
2931
"github.com/containers/podman/v5/pkg/rootless"
3032
"github.com/containers/podman/v5/pkg/terminal"
3133
"github.com/containers/storage/pkg/reexec"
@@ -34,12 +36,22 @@ import (
3436
"golang.org/x/term"
3537
)
3638

39+
type logrusLogger struct{}
40+
41+
func (l logrusLogger) Errorf(format string, args ...interface{}) {
42+
logrus.Errorf(format, args...)
43+
}
44+
func (l logrusLogger) Debugf(format string, args ...interface{}) {
45+
logrus.Debugf(format, args...)
46+
}
47+
3748
func main() {
3849
if reexec.Init() {
3950
// We were invoked with a different argv[0] indicating that we
4051
// had a specific job to do as a subprocess, and it's done.
4152
return
4253
}
54+
logiface.SetLogger(logrusLogger{})
4355

4456
if filepath.Base(os.Args[0]) == registry.PodmanSh ||
4557
(len(os.Args[0]) > 0 && filepath.Base(os.Args[0][1:]) == registry.PodmanSh) {

cmd/podman/quadlet/install.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package quadlet
2+
3+
import (
4+
"errors"
5+
"fmt"
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/cmd/podman/utils"
10+
"github.com/containers/podman/v5/pkg/domain/entities"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
quadletInstallDescription = `Install a quadlet file or quadlet application. Quadlets may be specified as local files, Web URLs, and OCI artifacts.`
16+
17+
quadletInstallCmd = &cobra.Command{
18+
Use: "install [options] QUADLET-PATH-OR-URL [FILES-PATH-OR-URL...]",
19+
Short: "Install a quadlet file or quadlet application",
20+
Long: quadletInstallDescription,
21+
RunE: install,
22+
Args: func(_ *cobra.Command, args []string) error {
23+
if len(args) == 0 {
24+
return fmt.Errorf("must provide at least one argument")
25+
}
26+
return nil
27+
},
28+
ValidArgsFunction: common.AutocompleteDefaultManyArg,
29+
Example: `podman quadlet install /path/to/myquadlet.container
30+
podman quadlet install https://github.com/containers/podman/blob/main/test/e2e/quadlet/basic.container
31+
podman quadlet install oci-artifact://my-artifact:latest`,
32+
}
33+
34+
installOptions entities.QuadletInstallOptions
35+
)
36+
37+
func installFlags(cmd *cobra.Command) {
38+
flags := cmd.Flags()
39+
flags.BoolVar(&installOptions.ReloadSystemd, "reload-systemd", true, "Reload systemd after installing Quadlets")
40+
}
41+
42+
func init() {
43+
registry.Commands = append(registry.Commands, registry.CliCommand{
44+
Command: quadletInstallCmd,
45+
Parent: quadletCmd,
46+
})
47+
installFlags(quadletInstallCmd)
48+
}
49+
50+
func install(cmd *cobra.Command, args []string) error {
51+
var errs utils.OutputErrors
52+
installReport, err := registry.ContainerEngine().QuadletInstall(registry.Context(), args, installOptions)
53+
if err != nil {
54+
return err
55+
}
56+
for pathOrURL, err := range installReport.QuadletErrors {
57+
errs = append(errs, fmt.Errorf("quadlet %q failed to install: %v", pathOrURL, err))
58+
}
59+
for _, s := range installReport.InstalledQuadlets {
60+
fmt.Println(s)
61+
}
62+
63+
if len(installReport.QuadletErrors) > 0 {
64+
errs = append(errs, errors.New("errors occurred installing some Quadlets"))
65+
}
66+
67+
return errs.PrintErrors()
68+
}

cmd/podman/quadlet/list.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package quadlet
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/podman/v5/cmd/podman/common"
10+
"github.com/containers/podman/v5/cmd/podman/registry"
11+
"github.com/containers/podman/v5/cmd/podman/validate"
12+
"github.com/containers/podman/v5/pkg/domain/entities"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
var (
17+
quadletListDescription = `List all Quadlets configured for the current user.`
18+
19+
quadletListCmd = &cobra.Command{
20+
Use: "list [options]",
21+
Short: "List Quadlets",
22+
Long: quadletListDescription,
23+
RunE: list,
24+
Args: validate.NoArgs,
25+
ValidArgsFunction: completion.AutocompleteNone,
26+
Example: `podman quadlet list
27+
podman quadlet list --format '{{ .Unit }}'
28+
podman quadlet list --filter 'name=test*'`,
29+
}
30+
31+
listOptions entities.QuadletListOptions
32+
format string
33+
)
34+
35+
func listFlags(cmd *cobra.Command) {
36+
formatFlagName := "format"
37+
filterFlagName := "filter"
38+
flags := cmd.Flags()
39+
40+
flags.StringArrayVarP(&listOptions.Filters, filterFlagName, "f", []string{}, "Filter output based on conditions given")
41+
flags.StringVar(&format, formatFlagName, "{{range .}}{{.Name}}\t{{.UnitName}}\t{{.Path}}\t{{.Status}}\t{{.App}}\n{{end -}}", "Pretty-print output to JSON or using a Go template")
42+
_ = quadletListCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.ListQuadlet{}))
43+
_ = quadletListCmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteQuadletFilters)
44+
}
45+
46+
func init() {
47+
registry.Commands = append(registry.Commands, registry.CliCommand{
48+
Command: quadletListCmd,
49+
Parent: quadletCmd,
50+
})
51+
listFlags(quadletListCmd)
52+
}
53+
54+
func list(cmd *cobra.Command, args []string) error {
55+
quadlets, err := registry.ContainerEngine().QuadletList(registry.Context(), listOptions)
56+
if err != nil {
57+
return err
58+
}
59+
60+
if report.IsJSON(format) {
61+
return outputJSON(quadlets)
62+
}
63+
return outputTemplate(cmd, quadlets)
64+
}
65+
66+
func outputTemplate(cmd *cobra.Command, responses []*entities.ListQuadlet) error {
67+
headers := report.Headers(entities.ListQuadlet{}, map[string]string{
68+
"Name": "NAME",
69+
"UnitName": "UNIT NAME",
70+
"Path": "PATH ON DISK",
71+
"Status": "STATUS",
72+
"App": "APPLICATION",
73+
})
74+
75+
rpt := report.New(os.Stdout, cmd.Name())
76+
defer rpt.Flush()
77+
78+
var err error
79+
origin := report.OriginPodman
80+
if cmd.Flag("format").Changed {
81+
origin = report.OriginUser
82+
}
83+
rpt, err = rpt.Parse(origin, format)
84+
if err != nil {
85+
return err
86+
}
87+
88+
if err := rpt.Execute(headers); err != nil {
89+
return fmt.Errorf("writing column headers: %w", err)
90+
}
91+
92+
return rpt.Execute(responses)
93+
}
94+
95+
func outputJSON(vols []*entities.ListQuadlet) error {
96+
b, err := json.MarshalIndent(vols, "", " ")
97+
if err != nil {
98+
return err
99+
}
100+
fmt.Println(string(b))
101+
return nil
102+
}

cmd/podman/quadlet/print.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package quadlet
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/spf13/cobra"
9+
)
10+
11+
var (
12+
quadletPrintDescription = `Print the contents of a Quadlet, displaying the file including all comments`
13+
14+
quadletPrintCmd = &cobra.Command{
15+
Use: "print QUADLET",
16+
Short: "Display the contents of a quadlet",
17+
Long: quadletPrintDescription,
18+
RunE: print,
19+
ValidArgsFunction: common.AutocompleteQuadlets,
20+
Args: cobra.ExactArgs(1),
21+
Example: `podman quadlet print myquadlet.container
22+
podman quadlet print mypod.pod
23+
podman quadlet print myimage.build`,
24+
}
25+
)
26+
27+
func init() {
28+
registry.Commands = append(registry.Commands, registry.CliCommand{
29+
Command: quadletPrintCmd,
30+
Parent: quadletCmd,
31+
})
32+
}
33+
34+
func print(cmd *cobra.Command, args []string) error {
35+
quadletContents, err := registry.ContainerEngine().QuadletPrint(registry.Context(), args[0])
36+
if err != nil {
37+
return err
38+
}
39+
40+
fmt.Print(quadletContents)
41+
42+
return nil
43+
}

cmd/podman/quadlet/quadlet.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package quadlet
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+
// Pull in configured json library
11+
json = registry.JSONLibrary()
12+
13+
// Command: podman _quadlet_
14+
quadletCmd = &cobra.Command{
15+
Use: "quadlet",
16+
Short: "Allows users to manage Quadlets",
17+
Long: "Allows users to manage Quadlets",
18+
RunE: validate.SubCommandExists,
19+
}
20+
)
21+
22+
func init() {
23+
registry.Commands = append(registry.Commands, registry.CliCommand{
24+
Command: quadletCmd,
25+
})
26+
}

0 commit comments

Comments
 (0)