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 <tibor@docker.com>
This commit is contained in:
Tibor Vass 2019-04-17 22:09:29 +00:00
parent 2432af701a
commit 1ed02c40fe
6 changed files with 111 additions and 1 deletions

View File

@ -164,6 +164,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
return nil, err return nil, err
} }
if plugin.Err != nil { if plugin.Err != nil {
// TODO: why are we not returning plugin.Err?
return nil, errPluginNotFound(name) return nil, errPluginNotFound(name)
} }
cmd := exec.Command(plugin.Path, args...) cmd := exec.Command(plugin.Path, args...)

View File

@ -5,6 +5,7 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image"
) )
// NewBuilderCommand returns a cobra command for `builder` subcommands // NewBuilderCommand returns a cobra command for `builder` subcommands
@ -18,6 +19,7 @@ func NewBuilderCommand(dockerCli command.Cli) *cobra.Command {
} }
cmd.AddCommand( cmd.AddCommand(
NewPruneCommand(dockerCli), NewPruneCommand(dockerCli),
image.NewBuildCommand(dockerCli),
) )
return cmd return cmd
} }

View File

@ -161,3 +161,34 @@ func ValidateOutputPathFileMode(fileMode os.FileMode) error {
} }
return nil 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
}

33
cli/command/utils_test.go Normal file
View File

@ -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)
}

View File

@ -50,6 +50,7 @@ type ConfigFile struct {
CurrentContext string `json:"currentContext,omitempty"` CurrentContext string `json:"currentContext,omitempty"`
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"` CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
Plugins map[string]map[string]string `json:"plugins,omitempty"` Plugins map[string]map[string]string `json:"plugins,omitempty"`
Aliases map[string]string `json:"aliases,omitempty"`
} }
// ProxyConfig contains proxy configuration settings // ProxyConfig contains proxy configuration settings
@ -72,6 +73,7 @@ func New(fn string) *ConfigFile {
HTTPHeaders: make(map[string]string), HTTPHeaders: make(map[string]string),
Filename: fn, Filename: fn,
Plugins: make(map[string]map[string]string), Plugins: make(map[string]map[string]string),
Aliases: make(map[string]string),
} }
} }

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
@ -16,11 +15,16 @@ import (
"github.com/docker/cli/cli/version" "github.com/docker/cli/cli/version"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
var allowedAliases = map[string]struct{}{
"builder": {},
}
func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand { func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand {
var ( var (
opts *cliflags.ClientOptions opts *cliflags.ClientOptions
@ -204,6 +208,38 @@ func tryPluginRun(dockerCli command.Cli, cmd *cobra.Command, subcommand string)
return nil 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 { func runDocker(dockerCli *command.DockerCli) error {
tcmd := newDockerCommand(dockerCli) tcmd := newDockerCommand(dockerCli)
@ -216,6 +252,11 @@ func runDocker(dockerCli *command.DockerCli) error {
return err return err
} }
args, os.Args, err = processAliases(dockerCli, cmd, args, os.Args)
if err != nil {
return err
}
if len(args) > 0 { if len(args) > 0 {
if _, _, err := cmd.Find(args); err != nil { if _, _, err := cmd.Find(args); err != nil {
err := tryPluginRun(dockerCli, cmd, args[0]) err := tryPluginRun(dockerCli, cmd, args[0])