Merge pull request #5043 from hashicorp/b-taskenv-conflicts

taskenv: have maps take precedence over primitives
This commit is contained in:
Michael Schurter 2019-01-07 12:34:48 -08:00 committed by GitHub
commit d686ad51fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 60 deletions

View file

@ -14,28 +14,11 @@ var (
ErrInvalidObjectPath = errors.New("invalid object path")
)
type ErrKeyExists struct {
msg string
}
func NewErrKeyExists(newKey, oldKey string) *ErrKeyExists {
return &ErrKeyExists{
msg: fmt.Sprintf(
"cannot add key %q because %q already exists with a different type",
newKey, oldKey,
),
}
}
func (e *ErrKeyExists) Error() string {
return e.msg
}
// addNestedKey expands keys into their nested form:
//
// k="foo.bar", v="quux" -> {"foo": {"bar": "quux"}}
//
// Existing keys are overwritten.
// Existing keys are overwritten. Map values take precedence over primitives.
//
// If the key has dots but cannot be converted to a valid nested data structure
// (eg "foo...bar", "foo.", or non-object value exists for key), an error is
@ -64,13 +47,17 @@ func addNestedKey(dst map[string]interface{}, k, v string) error {
var target map[string]interface{}
if existingI, ok := dst[newKey]; ok {
existing, ok := existingI.(map[string]interface{})
if !ok {
// Existing value is not a map, unable to support this key
cleanup()
return NewErrKeyExists(k, newKey)
}
if existing, ok := existingI.(map[string]interface{}); ok {
// Target already exists
target = existing
} else {
// Existing value is not a map. Maps should
// take precedence over primitive values (eg
// overwrite attr.driver.qemu = "1" with
// attr.driver.qemu.version = "...")
target = make(map[string]interface{})
dst[newKey] = target
}
} else {
// Does not exist, create
target = make(map[string]interface{})
@ -96,6 +83,13 @@ func addNestedKey(dst map[string]interface{}, k, v string) error {
return ErrInvalidObjectPath
}
if existingI, ok := dst[newKey]; ok {
if _, ok := existingI.(map[string]interface{}); ok {
// Existing value is a map which takes precedence over
// a primitive value. Drop primitive.
return nil
}
}
dst[newKey] = v
return nil
}

View file

@ -53,7 +53,7 @@ func TestAddNestedKey_Ok(t *testing.T) {
},
},
{
// Nested object b should get overwritten with "x"
// Nested object b should take precedence over values
M: map[string]interface{}{
"a": map[string]interface{}{
"b": map[string]interface{}{
@ -64,7 +64,9 @@ func TestAddNestedKey_Ok(t *testing.T) {
K: "a.b",
Result: map[string]interface{}{
"a": map[string]interface{}{
"b": "x",
"b": map[string]interface{}{
"c": "c",
},
},
},
},
@ -123,6 +125,81 @@ func TestAddNestedKey_Ok(t *testing.T) {
},
},
},
// Regardless of whether attr.driver.qemu = "1" is added first
// or second, attr.driver.qemu.version = "..." should take
// precedence (nested maps take precedence over values)
{
M: map[string]interface{}{
"attr": map[string]interface{}{
"driver": map[string]interface{}{
"qemu": "1",
},
},
},
K: "attr.driver.qemu.version",
Result: map[string]interface{}{
"attr": map[string]interface{}{
"driver": map[string]interface{}{
"qemu": map[string]interface{}{
"version": "x",
},
},
},
},
},
{
M: map[string]interface{}{
"attr": map[string]interface{}{
"driver": map[string]interface{}{
"qemu": map[string]interface{}{
"version": "1.2.3",
},
},
},
},
K: "attr.driver.qemu",
Result: map[string]interface{}{
"attr": map[string]interface{}{
"driver": map[string]interface{}{
"qemu": map[string]interface{}{
"version": "1.2.3",
},
},
},
},
},
{
M: map[string]interface{}{
"a": "a",
},
K: "a.b",
Result: map[string]interface{}{
"a": map[string]interface{}{
"b": "x",
},
},
},
{
M: map[string]interface{}{
"a": "a",
"foo": map[string]interface{}{
"b": "b",
"bar": "quux",
},
"c": map[string]interface{}{},
},
K: "foo.bar.quux",
Result: map[string]interface{}{
"a": "a",
"foo": map[string]interface{}{
"b": "b",
"bar": map[string]interface{}{
"quux": "x",
},
},
"c": map[string]interface{}{},
},
},
}
for i := range cases {
@ -234,40 +311,6 @@ func TestAddNestedKey_Bad(t *testing.T) {
K: "foo.bar..quux",
Result: ErrInvalidObjectPath,
},
{
M: func() map[string]interface{} {
return map[string]interface{}{
"a": "a",
}
},
K: "a.b",
Result: NewErrKeyExists("a.b", "a"),
},
{
M: func() map[string]interface{} {
return map[string]interface{}{
"a": "a",
"foo": map[string]interface{}{
"b": "b",
"bar": "quux",
},
"c": map[string]interface{}{},
}
},
K: "foo.bar.quux",
Result: NewErrKeyExists("foo.bar.quux", "bar"),
},
{
M: func() map[string]interface{} {
return map[string]interface{}{
"a": map[string]interface{}{
"b": "b",
},
}
},
K: "a.b.c.",
Result: NewErrKeyExists("a.b.c.", "b"),
},
}
for i := range cases {