From 1ed02c40fe689096a68a0a5c03ad49bc3c998635 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Wed, 17 Apr 2019 22:09:29 +0000 Subject: [PATCH] cli-plugins: alias an existing allowed command (only builder for now) With this patch it is possible to alias an existing allowed command. At the moment only builder allows an alias. This also properly puts the build command under builder, instead of image where it was for historical reasons. Signed-off-by: Tibor Vass --- cli-plugins/manager/manager.go | 1 + cli/command/builder/cmd.go | 2 ++ cli/command/utils.go | 31 ++++++++++++++++++++++++ cli/command/utils_test.go | 33 ++++++++++++++++++++++++++ cli/config/configfile/file.go | 2 ++ cmd/docker/docker.go | 43 +++++++++++++++++++++++++++++++++- 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 cli/command/utils_test.go diff --git a/cli-plugins/manager/manager.go b/cli-plugins/manager/manager.go index 78ff64bb43..e7cd2de40a 100644 --- a/cli-plugins/manager/manager.go +++ b/cli-plugins/manager/manager.go @@ -164,6 +164,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command return nil, err } if plugin.Err != nil { + // TODO: why are we not returning plugin.Err? return nil, errPluginNotFound(name) } cmd := exec.Command(plugin.Path, args...) diff --git a/cli/command/builder/cmd.go b/cli/command/builder/cmd.go index b5f4d0d9eb..724f7ca9c9 100644 --- a/cli/command/builder/cmd.go +++ b/cli/command/builder/cmd.go @@ -5,6 +5,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/image" ) // NewBuilderCommand returns a cobra command for `builder` subcommands @@ -18,6 +19,7 @@ func NewBuilderCommand(dockerCli command.Cli) *cobra.Command { } cmd.AddCommand( NewPruneCommand(dockerCli), + image.NewBuildCommand(dockerCli), ) return cmd } diff --git a/cli/command/utils.go b/cli/command/utils.go index 0356fa4cc6..21e702eb3f 100644 --- a/cli/command/utils.go +++ b/cli/command/utils.go @@ -161,3 +161,34 @@ func ValidateOutputPathFileMode(fileMode os.FileMode) error { } return nil } + +func stringSliceIndex(s, subs []string) int { + j := 0 + if len(subs) > 0 { + for i, x := range s { + if j < len(subs) && subs[j] == x { + j++ + } else { + j = 0 + } + if len(subs) == j { + return i + 1 - j + } + } + } + return -1 +} + +// StringSliceReplaceAt replaces the sub-slice old, with the sub-slice new, in the string +// slice s, returning a new slice and a boolean indicating if the replacement happened. +// requireIdx is the index at which old needs to be found at (or -1 to disregard that). +func StringSliceReplaceAt(s, old, new []string, requireIndex int) ([]string, bool) { + idx := stringSliceIndex(s, old) + if (requireIndex != -1 && requireIndex != idx) || idx == -1 { + return s, false + } + out := append([]string{}, s[:idx]...) + out = append(out, new...) + out = append(out, s[idx+len(old):]...) + return out, true +} diff --git a/cli/command/utils_test.go b/cli/command/utils_test.go new file mode 100644 index 0000000000..0452a7c4cf --- /dev/null +++ b/cli/command/utils_test.go @@ -0,0 +1,33 @@ +package command + +import ( + "testing" + + "gotest.tools/assert" +) + +func TestStringSliceReplaceAt(t *testing.T) { + out, ok := StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, -1) + assert.Assert(t, ok) + assert.DeepEqual(t, []string{"abc", "baz", "bax"}, out) + + out, ok = StringSliceReplaceAt([]string{"foo"}, []string{"foo", "bar"}, []string{"baz"}, -1) + assert.Assert(t, !ok) + assert.DeepEqual(t, []string{"foo"}, out) + + out, ok = StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0) + assert.Assert(t, !ok) + assert.DeepEqual(t, []string{"abc", "foo", "bar", "bax"}, out) + + out, ok = StringSliceReplaceAt([]string{"foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0) + assert.Assert(t, ok) + assert.DeepEqual(t, []string{"baz", "bax"}, out) + + out, ok = StringSliceReplaceAt([]string{"abc", "foo", "bar", "baz"}, []string{"foo", "bar"}, nil, -1) + assert.Assert(t, ok) + assert.DeepEqual(t, []string{"abc", "baz"}, out) + + out, ok = StringSliceReplaceAt([]string{"foo"}, nil, []string{"baz"}, -1) + assert.Assert(t, !ok) + assert.DeepEqual(t, []string{"foo"}, out) +} diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index c8d601162f..67ae655b0b 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -50,6 +50,7 @@ type ConfigFile struct { CurrentContext string `json:"currentContext,omitempty"` CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"` Plugins map[string]map[string]string `json:"plugins,omitempty"` + Aliases map[string]string `json:"aliases,omitempty"` } // ProxyConfig contains proxy configuration settings @@ -72,6 +73,7 @@ func New(fn string) *ConfigFile { HTTPHeaders: make(map[string]string), Filename: fn, Plugins: make(map[string]map[string]string), + Aliases: make(map[string]string), } } diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 450d20e72e..22b978de9b 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "os" "os/exec" @@ -16,11 +15,16 @@ import ( "github.com/docker/cli/cli/version" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" ) +var allowedAliases = map[string]struct{}{ + "builder": {}, +} + func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand { var ( opts *cliflags.ClientOptions @@ -204,6 +208,38 @@ func tryPluginRun(dockerCli command.Cli, cmd *cobra.Command, subcommand string) return nil } +func processAliases(dockerCli command.Cli, cmd *cobra.Command, args, osArgs []string) ([]string, []string, error) { + aliasMap := dockerCli.ConfigFile().Aliases + aliases := make([][2][]string, 0, len(aliasMap)) + + for k, v := range aliasMap { + if _, ok := allowedAliases[k]; !ok { + return args, osArgs, errors.Errorf("Not allowed to alias %q. Allowed aliases: %#v", k, allowedAliases) + } + if _, _, err := cmd.Find(strings.Split(v, " ")); err == nil { + return args, osArgs, errors.Errorf("Not allowed to alias with builtin %q as target", v) + } + aliases = append(aliases, [2][]string{{k}, {v}}) + } + + if v, ok := aliasMap["builder"]; ok { + aliases = append(aliases, + [2][]string{{"build"}, {v, "build"}}, + [2][]string{{"image", "build"}, {v, "build"}}, + ) + } + for _, al := range aliases { + var didChange bool + args, didChange = command.StringSliceReplaceAt(args, al[0], al[1], 0) + if didChange { + osArgs, _ = command.StringSliceReplaceAt(osArgs, al[0], al[1], -1) + break + } + } + + return args, osArgs, nil +} + func runDocker(dockerCli *command.DockerCli) error { tcmd := newDockerCommand(dockerCli) @@ -216,6 +252,11 @@ func runDocker(dockerCli *command.DockerCli) error { return err } + args, os.Args, err = processAliases(dockerCli, cmd, args, os.Args) + if err != nil { + return err + } + if len(args) > 0 { if _, _, err := cmd.Find(args); err != nil { err := tryPluginRun(dockerCli, cmd, args[0])