Merge component 'cli' from git@github.com:docker/cli master
This commit is contained in:
@ -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]
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user