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])