Shell Completion for podman build flags

The PR containers/podman#6442 enables a new way to create
shell completions scripts. The shell completion is handled
by cobra and since the build flags are defined here
the completion functions for this should be defined here
as well. For Reference see:
https://github.com/spf13/cobra/blob/master/shell_completions.md

I added a unit test to ensure that the flags have a
completion function set.

These completions could also be used by buildah if someone
wants to implements this cobra completion logic for buildah.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
Paul Holzinger 2020-09-24 13:56:59 +02:00
parent 6348eb3a0e
commit 71ba2e4ef0
3 changed files with 172 additions and 0 deletions

View File

@ -11,9 +11,11 @@ import (
"strings"
"github.com/containers/buildah"
"github.com/containers/buildah/pkg/completion"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/util"
"github.com/containers/common/pkg/auth"
commonComp "github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/config"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
@ -125,6 +127,17 @@ func GetUserNSFlags(flags *UserNSResults) pflag.FlagSet {
return usernsFlags
}
// GetUserNSFlagsCompletions returns the FlagCompletions for the userns flags
func GetUserNSFlagsCompletions() commonComp.FlagCompletions {
flagCompletion := commonComp.FlagCompletions{}
flagCompletion["userns"] = completion.AutocompleteNamespaceFlag
flagCompletion["userns-uid-map"] = commonComp.AutocompleteNone
flagCompletion["userns-gid-map"] = commonComp.AutocompleteNone
flagCompletion["userns-uid-map-user"] = commonComp.AutocompleteSubuidName
flagCompletion["userns-gid-map-group"] = commonComp.AutocompleteSubgidName
return flagCompletion
}
// GetNameSpaceFlags returns the common flags for a namespace menu
func GetNameSpaceFlags(flags *NameSpaceResults) pflag.FlagSet {
fs := pflag.FlagSet{}
@ -137,6 +150,18 @@ func GetNameSpaceFlags(flags *NameSpaceResults) pflag.FlagSet {
return fs
}
// GetNameSpaceFlagsCompletions returns the FlagCompletions for the namespace flags
func GetNameSpaceFlagsCompletions() commonComp.FlagCompletions {
flagCompletion := commonComp.FlagCompletions{}
flagCompletion[string(specs.IPCNamespace)] = completion.AutocompleteNamespaceFlag
flagCompletion[string(specs.NetworkNamespace)] = completion.AutocompleteNamespaceFlag
flagCompletion["cni-config-dir"] = commonComp.AutocompleteDefault
flagCompletion["cni-plugin-path"] = commonComp.AutocompleteDefault
flagCompletion[string(specs.PIDNamespace)] = completion.AutocompleteNamespaceFlag
flagCompletion[string(specs.UTSNamespace)] = completion.AutocompleteNamespaceFlag
return flagCompletion
}
// GetLayerFlags returns the common flags for layers
func GetLayerFlags(flags *LayerResults) pflag.FlagSet {
fs := pflag.FlagSet{}
@ -145,6 +170,8 @@ func GetLayerFlags(flags *LayerResults) pflag.FlagSet {
return fs
}
// Note: GetLayerFlagsCompletion is not needed since GetLayerFlags only contains bool flags
// GetBudFlags returns common bud flags
func GetBudFlags(flags *BudResults) pflag.FlagSet {
fs := pflag.FlagSet{}
@ -189,6 +216,35 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet {
return fs
}
// GetBudFlagsCompletions returns the FlagCompletions for the common bud flags
func GetBudFlagsCompletions() commonComp.FlagCompletions {
flagCompletion := commonComp.FlagCompletions{}
flagCompletion["arch"] = commonComp.AutocompleteNone
flagCompletion["annotation"] = commonComp.AutocompleteNone
flagCompletion["authfile"] = commonComp.AutocompleteDefault
flagCompletion["build-arg"] = commonComp.AutocompleteNone
flagCompletion["cache-from"] = commonComp.AutocompleteNone
flagCompletion["cert-dir"] = commonComp.AutocompleteDefault
flagCompletion["creds"] = commonComp.AutocompleteNone
flagCompletion["file"] = commonComp.AutocompleteDefault
flagCompletion["format"] = commonComp.AutocompleteNone
flagCompletion["iidfile"] = commonComp.AutocompleteDefault
flagCompletion["label"] = commonComp.AutocompleteNone
flagCompletion["logfile"] = commonComp.AutocompleteDefault
flagCompletion["loglevel"] = commonComp.AutocompleteDefault
flagCompletion["timestamp"] = commonComp.AutocompleteNone
flagCompletion["os"] = commonComp.AutocompleteNone
flagCompletion["platform"] = commonComp.AutocompleteNone
flagCompletion["runtime-flag"] = commonComp.AutocompleteNone
flagCompletion["sign-by"] = commonComp.AutocompleteNone
flagCompletion["signature-policy"] = commonComp.AutocompleteNone
flagCompletion["tag"] = commonComp.AutocompleteNone
flagCompletion["target"] = commonComp.AutocompleteNone
flagCompletion["jobs"] = commonComp.AutocompleteNone
return flagCompletion
}
// GetFromAndBudFlags returns from and bud flags
func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults, namespaceResults *NameSpaceResults) (pflag.FlagSet, error) {
fs := pflag.FlagSet{}
defaultContainerConfig, err := config.Default()
@ -239,6 +295,44 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults,
return fs, nil
}
// GetFromAndBudFlagsCompletions returns the FlagCompletions for the from and bud flags
func GetFromAndBudFlagsCompletions() commonComp.FlagCompletions {
flagCompletion := commonComp.FlagCompletions{}
flagCompletion["add-host"] = commonComp.AutocompleteNone
flagCompletion["blob-cache"] = commonComp.AutocompleteNone
flagCompletion["cap-add"] = commonComp.AutocompleteCapabilities
flagCompletion["cap-drop"] = commonComp.AutocompleteCapabilities
flagCompletion["cgroup-parent"] = commonComp.AutocompleteDefault // FIXME: This would be a path right?!
flagCompletion["cpu-period"] = commonComp.AutocompleteNone
flagCompletion["cpu-quota"] = commonComp.AutocompleteNone
flagCompletion["cpu-shares"] = commonComp.AutocompleteNone
flagCompletion["cpuset-cpus"] = commonComp.AutocompleteNone
flagCompletion["cpuset-mems"] = commonComp.AutocompleteNone
flagCompletion["device"] = commonComp.AutocompleteDefault
flagCompletion["dns-search"] = commonComp.AutocompleteNone
flagCompletion["dns"] = commonComp.AutocompleteNone
flagCompletion["dns-option"] = commonComp.AutocompleteNone
flagCompletion["isolation"] = commonComp.AutocompleteNone
flagCompletion["memory"] = commonComp.AutocompleteNone
flagCompletion["memory-swap"] = commonComp.AutocompleteNone
flagCompletion["security-opt"] = commonComp.AutocompleteNone
flagCompletion["shm-size"] = commonComp.AutocompleteNone
flagCompletion["ulimit"] = commonComp.AutocompleteNone
flagCompletion["volume"] = commonComp.AutocompleteDefault
// Add in the usernamespace and namespace flag completions
userNsComp := GetUserNSFlagsCompletions()
for name, comp := range userNsComp {
flagCompletion[name] = comp
}
namespaceComp := GetNameSpaceFlagsCompletions()
for name, comp := range namespaceComp {
flagCompletion[name] = comp
}
return flagCompletion
}
// UseLayers returns true if BUILDAH_LAYERS is set to "1" or "true"
// otherwise it returns false
func UseLayers() bool {

55
pkg/cli/common_test.go Normal file
View File

@ -0,0 +1,55 @@
package cli
import (
"testing"
"github.com/containers/common/pkg/completion"
"github.com/spf13/pflag"
)
func testFlagCompletion(t *testing.T, flags pflag.FlagSet, flagCompletions completion.FlagCompletions) {
// lookup if for each flag a flag completion function exists
flags.VisitAll(func(f *pflag.Flag) {
// skip hidden, deprecated and boolean flags
if f.Hidden || len(f.Deprecated) > 0 || f.Value.Type() == "bool" {
return
}
if _, ok := flagCompletions[f.Name]; !ok {
t.Errorf("Flag %q has no shell completion function set.", f.Name)
}
})
// make sure no unnecessary flag completion functions are defined
for name := range flagCompletions {
if flag := flags.Lookup(name); flag == nil {
t.Errorf("Flag %q does not exists but has a shell completion function set.", name)
}
}
}
func TestUserNsFlagsCompletion(t *testing.T) {
flags := GetUserNSFlags(&UserNSResults{})
flagCompletions := GetUserNSFlagsCompletions()
testFlagCompletion(t, flags, flagCompletions)
}
func TestNameSpaceFlagsCompletion(t *testing.T) {
flags := GetNameSpaceFlags(&NameSpaceResults{})
flagCompletions := GetNameSpaceFlagsCompletions()
testFlagCompletion(t, flags, flagCompletions)
}
func TestBudFlagsCompletion(t *testing.T) {
flags := GetBudFlags(&BudResults{})
flagCompletions := GetBudFlagsCompletions()
testFlagCompletion(t, flags, flagCompletions)
}
func TestFromAndBudFlagsCompletions(t *testing.T) {
flags, err := GetFromAndBudFlags(&FromAndBudResults{}, &UserNSResults{}, &NameSpaceResults{})
if err != nil {
t.Error("Could load the from and bud flags.")
}
flagCompletions := GetFromAndBudFlagsCompletions()
testFlagCompletion(t, flags, flagCompletions)
}

View File

@ -0,0 +1,23 @@
package completion
import (
"strings"
"github.com/spf13/cobra"
)
/* Autocomplete Functions for cobra ValidArgsFunction */
// AutocompleteNamespaceFlag - Autocomplete the userns flag.
// -> host, private, container, ns:[path], [path]
func AutocompleteNamespaceFlag(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
var completions []string
// If we don't filter on "toComplete", zsh and fish will not do file completion
// even if the prefix typed by the user does not match the returned completions
for _, comp := range []string{"host", "private", "container", "ns:"} {
if strings.HasPrefix(comp, toComplete) {
completions = append(completions, comp)
}
}
return completions, cobra.ShellCompDirectiveDefault
}