mirror of https://github.com/docker/cli.git
Merge pull request #893 from adshmh/add-support-for-mandatory-variables-syntax-to-stack-deploy
add support for mandatory variables to stack deploy
This commit is contained in:
commit
750b038707
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var delimiter = "\\$"
|
var delimiter = "\\$"
|
||||||
var substitution = "[_a-z][_a-z0-9]*(?::?-[^}]+)?"
|
var substitution = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?"
|
||||||
|
|
||||||
var patternString = fmt.Sprintf(
|
var patternString = fmt.Sprintf(
|
||||||
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
|
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
|
||||||
|
@ -37,57 +37,78 @@ func Substitute(template string, mapping Mapping) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
||||||
matches := pattern.FindStringSubmatch(substring)
|
matches := pattern.FindStringSubmatch(substring)
|
||||||
groups := make(map[string]string)
|
groups := matchGroups(matches)
|
||||||
for i, name := range pattern.SubexpNames() {
|
if escaped := groups["escaped"]; escaped != "" {
|
||||||
if i != 0 {
|
return escaped
|
||||||
groups[name] = matches[i]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
substitution := groups["named"]
|
substitution := groups["named"]
|
||||||
if substitution == "" {
|
if substitution == "" {
|
||||||
substitution = groups["braced"]
|
substitution = groups["braced"]
|
||||||
}
|
}
|
||||||
if substitution != "" {
|
|
||||||
// Soft default (fall back if unset or empty)
|
|
||||||
if strings.Contains(substitution, ":-") {
|
|
||||||
name, defaultValue := partition(substitution, ":-")
|
|
||||||
value, ok := mapping(name)
|
|
||||||
if !ok || value == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hard default (fall back if-and-only-if empty)
|
switch {
|
||||||
if strings.Contains(substitution, "-") {
|
|
||||||
name, defaultValue := partition(substitution, "-")
|
|
||||||
value, ok := mapping(name)
|
|
||||||
if !ok {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// No default (fall back to empty string)
|
case substitution == "":
|
||||||
value, ok := mapping(substitution)
|
err = &InvalidTemplateError{Template: template}
|
||||||
|
return ""
|
||||||
|
|
||||||
|
// Soft default (fall back if unset or empty)
|
||||||
|
case strings.Contains(substitution, ":-"):
|
||||||
|
name, defaultValue := partition(substitution, ":-")
|
||||||
|
value, ok := mapping(name)
|
||||||
|
if !ok || value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
|
||||||
|
// Hard default (fall back if-and-only-if empty)
|
||||||
|
case strings.Contains(substitution, "-"):
|
||||||
|
name, defaultValue := partition(substitution, "-")
|
||||||
|
value, ok := mapping(name)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
|
||||||
|
case strings.Contains(substitution, ":?"):
|
||||||
|
name, errorMessage := partition(substitution, ":?")
|
||||||
|
value, ok := mapping(name)
|
||||||
|
if !ok || value == "" {
|
||||||
|
err = &InvalidTemplateError{
|
||||||
|
Template: fmt.Sprintf("required variable %s is missing a value: %s", name, errorMessage),
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
|
||||||
|
case strings.Contains(substitution, "?"):
|
||||||
|
name, errorMessage := partition(substitution, "?")
|
||||||
|
value, ok := mapping(name)
|
||||||
|
if !ok {
|
||||||
|
err = &InvalidTemplateError{
|
||||||
|
Template: fmt.Sprintf("required variable %s is missing a value: %s", name, errorMessage),
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
if escaped := groups["escaped"]; escaped != "" {
|
value, _ := mapping(substitution)
|
||||||
return escaped
|
return value
|
||||||
}
|
|
||||||
|
|
||||||
err = &InvalidTemplateError{Template: template}
|
|
||||||
return ""
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchGroups(matches []string) map[string]string {
|
||||||
|
groups := make(map[string]string)
|
||||||
|
for i, name := range pattern.SubexpNames()[1:] {
|
||||||
|
groups[name] = matches[i+1]
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
// Split the string at the first occurrence of sep, and return the part before the separator,
|
// Split the string at the first occurrence of sep, and return the part before the separator,
|
||||||
// and the part after the separator.
|
// and the part after the separator.
|
||||||
//
|
//
|
||||||
|
|
|
@ -3,7 +3,9 @@ package template
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/internal/test/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaults = map[string]string{
|
var defaults = map[string]string{
|
||||||
|
@ -22,6 +24,12 @@ func TestEscaped(t *testing.T) {
|
||||||
assert.Equal(t, "${foo}", result)
|
assert.Equal(t, "${foo}", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubstituteNoMatch(t *testing.T) {
|
||||||
|
result, err := Substitute("foo", defaultMapping)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "foo", result)
|
||||||
|
}
|
||||||
|
|
||||||
func TestInvalid(t *testing.T) {
|
func TestInvalid(t *testing.T) {
|
||||||
invalidTemplates := []string{
|
invalidTemplates := []string{
|
||||||
"${",
|
"${",
|
||||||
|
@ -81,3 +89,64 @@ func TestNonAlphanumericDefault(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "ok /non:-alphanumeric", result)
|
assert.Equal(t, "ok /non:-alphanumeric", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMandatoryVariableErrors(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
template string
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
template: "not ok ${UNSET_VAR:?Mandatory Variable Unset}",
|
||||||
|
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
template: "not ok ${BAR:?Mandatory Variable Empty}",
|
||||||
|
expectedError: "required variable BAR is missing a value: Mandatory Variable Empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
template: "not ok ${UNSET_VAR:?}",
|
||||||
|
expectedError: "required variable UNSET_VAR is missing a value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
template: "not ok ${UNSET_VAR?Mandatory Variable Unset}",
|
||||||
|
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable Unset",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
template: "not ok ${UNSET_VAR?}",
|
||||||
|
expectedError: "required variable UNSET_VAR is missing a value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
_, err := Substitute(tc.template, defaultMapping)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, &InvalidTemplateError{}, err)
|
||||||
|
testutil.ErrorContains(t, err, tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultsForMandatoryVariables(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
template string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
template: "ok ${FOO:?err}",
|
||||||
|
expected: "ok first",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
template: "ok ${FOO?err}",
|
||||||
|
expected: "ok first",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
template: "ok ${BAR?err}",
|
||||||
|
expected: "ok ",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
result, err := Substitute(tc.template, defaultMapping)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue