Merge component 'cli' from git@github.com:docker/cli master

This commit is contained in:
GordonTheTurtle
2018-08-02 16:36:50 +00:00
3 changed files with 143 additions and 58 deletions

View File

@ -14,7 +14,7 @@ var patternString = fmt.Sprintf(
delimiter, delimiter, substitution, substitution,
)
var pattern = regexp.MustCompile(patternString)
var defaultPattern = regexp.MustCompile(patternString)
// DefaultSubstituteFuncs contains the default SubstitueFunc used by the docker cli
var DefaultSubstituteFuncs = []SubstituteFunc{
@ -51,7 +51,7 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
var err error
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
matches := pattern.FindStringSubmatch(substring)
groups := matchGroups(matches)
groups := matchGroups(matches, pattern)
if escaped := groups["escaped"]; escaped != "" {
return escaped
}
@ -90,26 +90,31 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
// Substitute variables in the string with their values
func Substitute(template string, mapping Mapping) (string, error) {
return SubstituteWith(template, mapping, pattern, DefaultSubstituteFuncs...)
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
}
// ExtractVariables returns a map of all the variables defined in the specified
// composefile (dict representation) and their default value if any.
func ExtractVariables(configDict map[string]interface{}) map[string]string {
return recurseExtract(configDict)
func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]string {
if pattern == nil {
pattern = defaultPattern
}
return recurseExtract(configDict, pattern)
}
func recurseExtract(value interface{}) map[string]string {
func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]string {
m := map[string]string{}
switch value := value.(type) {
case string:
if v, is := extractVariable(value); is {
m[v.name] = v.value
if values, is := extractVariable(value, pattern); is {
for _, v := range values {
m[v.name] = v.value
}
}
case map[string]interface{}:
for _, elem := range value {
submap := recurseExtract(elem)
submap := recurseExtract(elem, pattern)
for key, value := range submap {
m[key] = value
}
@ -117,8 +122,10 @@ func recurseExtract(value interface{}) map[string]string {
case []interface{}:
for _, elem := range value {
if v, is := extractVariable(elem); is {
m[v.name] = v.value
if values, is := extractVariable(elem, pattern); is {
for _, v := range values {
m[v.name] = v.value
}
}
}
}
@ -131,36 +138,40 @@ type extractedValue struct {
value string
}
func extractVariable(value interface{}) (extractedValue, bool) {
func extractVariable(value interface{}, pattern *regexp.Regexp) ([]extractedValue, bool) {
sValue, ok := value.(string)
if !ok {
return extractedValue{}, false
return []extractedValue{}, false
}
matches := pattern.FindStringSubmatch(sValue)
matches := pattern.FindAllStringSubmatch(sValue, -1)
if len(matches) == 0 {
return extractedValue{}, false
return []extractedValue{}, false
}
groups := matchGroups(matches)
if escaped := groups["escaped"]; escaped != "" {
return extractedValue{}, false
values := []extractedValue{}
for _, match := range matches {
groups := matchGroups(match, pattern)
if escaped := groups["escaped"]; escaped != "" {
continue
}
val := groups["named"]
if val == "" {
val = groups["braced"]
}
name := val
var defaultValue string
switch {
case strings.Contains(val, ":?"):
name, _ = partition(val, ":?")
case strings.Contains(val, "?"):
name, _ = partition(val, "?")
case strings.Contains(val, ":-"):
name, defaultValue = partition(val, ":-")
case strings.Contains(val, "-"):
name, defaultValue = partition(val, "-")
}
values = append(values, extractedValue{name: name, value: defaultValue})
}
val := groups["named"]
if val == "" {
val = groups["braced"]
}
name := val
var defaultValue string
switch {
case strings.Contains(val, ":?"):
name, _ = partition(val, ":?")
case strings.Contains(val, "?"):
name, _ = partition(val, "?")
case strings.Contains(val, ":-"):
name, defaultValue = partition(val, ":-")
case strings.Contains(val, "-"):
name, defaultValue = partition(val, "-")
}
return extractedValue{name: name, value: defaultValue}, true
return values, len(values) > 0
}
// Soft default (fall back if unset or empty)
@ -207,7 +218,7 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s
return value, true, nil
}
func matchGroups(matches []string) map[string]string {
func matchGroups(matches []string, pattern *regexp.Regexp) map[string]string {
groups := make(map[string]string)
for i, name := range pattern.SubexpNames()[1:] {
groups[name] = matches[i+1]

View File

@ -161,15 +161,15 @@ func TestSubstituteWithCustomFunc(t *testing.T) {
return value, true, nil
}
result, err := SubstituteWith("ok ${FOO}", defaultMapping, pattern, errIsMissing)
result, err := SubstituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok first", result))
result, err = SubstituteWith("ok ${BAR}", defaultMapping, pattern, errIsMissing)
result, err = SubstituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok ", result))
_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, pattern, errIsMissing)
_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
assert.Check(t, is.ErrorContains(err, "required variable"))
}
@ -245,18 +245,21 @@ func TestExtractVariables(t *testing.T) {
},
"baz": []interface{}{
"foo",
"$docker:${project:-cli}",
"$toto",
},
},
expected: map[string]string{
"bar": "foo",
"fruit": "banana",
"toto": "",
"bar": "foo",
"fruit": "banana",
"toto": "",
"docker": "",
"project": "cli",
},
},
}
for _, tc := range testCases {
actual := ExtractVariables(tc.dict)
actual := ExtractVariables(tc.dict, defaultPattern)
assert.Check(t, is.DeepEqual(actual, tc.expected))
}
}

View File

@ -584,6 +584,31 @@ __docker_daemon_os_is() {
[ "$actual_os" = "$expected_os" ]
}
# __docker_stack_orchestrator_is tests whether the client is configured to use
# the orchestrator that is passed in as the first argument.
__docker_stack_orchestrator_is() {
case "$1" in
kubernetes)
if [ -z "$stack_orchestrator_is_kubernetes" ] ; then
__docker_q stack ls --help | grep -qe --namespace
stack_orchestrator_is_kubernetes=$?
fi
return $stack_orchestrator_is_kubernetes
;;
swarm)
if [ -z "$stack_orchestrator_is_swarm" ] ; then
__docker_q stack deploy --help | grep -qe "with-registry-auth"
stack_orchestrator_is_swarm=$?
fi
return $stack_orchestrator_is_swarm
;;
*)
return 1
;;
esac
}
# __docker_pos_first_nonflag finds the position of the first word that is neither
# option nor an option's argument. If there are options that require arguments,
# you should pass a glob describing those options, e.g. "--option1|-o|--option2"
@ -1050,6 +1075,23 @@ __docker_complete_signals() {
COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo "$cur" | tr '[:lower:]' '[:upper:]')" ) )
}
__docker_complete_stack_orchestrator_options() {
case "$prev" in
--kubeconfig)
_filedir
return 0
;;
--namespace)
return 0
;;
--orchestrator)
COMPREPLY=( $( compgen -W "all kubernetes swarm" -- "$cur") )
return 0
;;
esac
return 1
}
__docker_complete_user_group() {
if [[ $cur == *:* ]] ; then
COMPREPLY=( $(compgen -g -- "${cur#*:}") )
@ -4378,11 +4420,15 @@ _docker_stack() {
remove
up
"
__docker_complete_stack_orchestrator_options && return
__docker_subcommands "$subcommands $aliases" && return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
local options="--help --orchestrator"
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;;
*)
COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
@ -4391,12 +4437,12 @@ _docker_stack() {
}
_docker_stack_deploy() {
__docker_complete_stack_orchestrator_options && return
case "$prev" in
--bundle-file)
if __docker_daemon_is_experimental ; then
_filedir dab
return
fi
_filedir dab
return
;;
--compose-file|-c)
_filedir yml
@ -4410,12 +4456,14 @@ _docker_stack_deploy() {
case "$cur" in
-*)
local options="--compose-file -c --help --prune --resolve-image --with-registry-auth"
__docker_daemon_is_experimental && options+=" --bundle-file"
local options="--compose-file -c --help --orchestrator"
__docker_daemon_is_experimental && __docker_stack_orchestrator_is swarm && options+=" --bundle-file"
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig --namespace"
__docker_stack_orchestrator_is swarm && options+=" --prune --resolve-image --with-registry-auth"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag '--bundle-file|--compose-file|-c|--resolve-image')
local counter=$(__docker_pos_first_nonflag '--bundle-file|--compose-file|-c|--kubeconfig|--namespace|--orchestrator|--resolve-image')
if [ "$cword" -eq "$counter" ]; then
__docker_complete_stacks
fi
@ -4432,6 +4480,8 @@ _docker_stack_list() {
}
_docker_stack_ls() {
__docker_complete_stack_orchestrator_options && return
case "$prev" in
--format)
return
@ -4440,7 +4490,9 @@ _docker_stack_ls() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--format --help" -- "$cur" ) )
local options="--format --help --orchestrator"
__docker_stack_orchestrator_is kubernetes && options+=" --all-namespaces --kubeconfig --namespace"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;;
esac
}
@ -4462,6 +4514,8 @@ _docker_stack_ps() {
;;
esac
__docker_complete_stack_orchestrator_options && return
case "$prev" in
--filter|-f)
COMPREPLY=( $( compgen -S = -W "id name desired-state" -- "$cur" ) )
@ -4475,10 +4529,12 @@ _docker_stack_ps() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--filter -f --format --help --no-resolve --no-trunc --quiet -q" -- "$cur" ) )
local options="--filter -f --format --help --no-resolve --no-trunc --orchestrator --quiet -q"
__docker_stack_orchestrator_is kubernetes && options+=" --all-namespaces --kubeconfig --namespace"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag '--filter|-f')
local counter=$(__docker_pos_first_nonflag '--all-namespaces|--filter|-f|--format|--kubeconfig|--namespace')
if [ "$cword" -eq "$counter" ]; then
__docker_complete_stacks
fi
@ -4491,9 +4547,13 @@ _docker_stack_remove() {
}
_docker_stack_rm() {
__docker_complete_stack_orchestrator_options && return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
local options="--help --orchestrator"
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig --namespace"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;;
*)
__docker_complete_stacks
@ -4517,6 +4577,8 @@ _docker_stack_services() {
;;
esac
__docker_complete_stack_orchestrator_options && return
case "$prev" in
--filter|-f)
COMPREPLY=( $( compgen -S = -W "id label name" -- "$cur" ) )
@ -4530,10 +4592,12 @@ _docker_stack_services() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--filter -f --format --help --quiet -q" -- "$cur" ) )
local options="--filter -f --format --help --orchestrator --quiet -q"
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig --namespace"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;;
*)
local counter=$(__docker_pos_first_nonflag '--filter|-f|--format')
local counter=$(__docker_pos_first_nonflag '--filter|-f|--format|--kubeconfig|--namespace|--orchestrator')
if [ "$cword" -eq "$counter" ]; then
__docker_complete_stacks
fi
@ -4800,6 +4864,8 @@ _docker_top() {
}
_docker_version() {
__docker_complete_stack_orchestrator_options && return
case "$prev" in
--format|-f)
return
@ -4808,7 +4874,9 @@ _docker_version() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--format -f --help" -- "$cur" ) )
local options="--format -f --help"
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;;
esac
}
@ -5038,6 +5106,9 @@ _docker() {
local host config daemon_os
# variables to cache client info, populated on demand for performance reasons
local stack_orchestrator_is_kubernetes stack_orchestrator_is_swarm
COMPREPLY=()
local cur prev words cword
_get_comp_words_by_ref -n : cur prev words cword