Add indexed key support to --state-values-set (#1042)
Adds support for indexed key arguments and escaped dot arguments to `--state-values-set`. Things like `--state-values-set config\.yml.something=abc` or `--state-values-set config.something[0]=abc` can be passed in the command line. Resolves #899
This commit is contained in:
parent
03898b7a98
commit
4b2d6946be
7
main.go
7
main.go
|
|
@ -2,10 +2,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/roboll/helmfile/pkg/app/version"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/roboll/helmfile/pkg/app/version"
|
||||||
|
|
||||||
"github.com/roboll/helmfile/pkg/app"
|
"github.com/roboll/helmfile/pkg/app"
|
||||||
"github.com/roboll/helmfile/pkg/helmexec"
|
"github.com/roboll/helmfile/pkg/helmexec"
|
||||||
"github.com/roboll/helmfile/pkg/maputil"
|
"github.com/roboll/helmfile/pkg/maputil"
|
||||||
|
|
@ -483,10 +484,10 @@ func NewUrfaveCliConfigImpl(c *cli.Context) (configImpl, error) {
|
||||||
ops := strings.Split(optsSet[i], ",")
|
ops := strings.Split(optsSet[i], ",")
|
||||||
for j := range ops {
|
for j := range ops {
|
||||||
op := strings.SplitN(ops[j], "=", 2)
|
op := strings.SplitN(ops[j], "=", 2)
|
||||||
k := strings.Split(op[0], ".")
|
k := maputil.ParseKey(op[0])
|
||||||
v := op[1]
|
v := op[1]
|
||||||
|
|
||||||
set = maputil.Set(set, k, v)
|
maputil.Set(set, k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conf.set = set
|
conf.set = set
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package maputil
|
package maputil
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
func CastKeysToStrings(s interface{}) (map[string]interface{}, error) {
|
func CastKeysToStrings(s interface{}) (map[string]interface{}, error) {
|
||||||
new := map[string]interface{}{}
|
new := map[string]interface{}{}
|
||||||
|
|
@ -60,32 +63,129 @@ func recursivelyStringifyMapKey(v interface{}) (interface{}, error) {
|
||||||
return casted_v, nil
|
return casted_v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Set(m map[string]interface{}, key []string, value string) map[string]interface{} {
|
type arg interface {
|
||||||
|
getMap(map[string]interface{}) map[string]interface{}
|
||||||
|
set(map[string]interface{}, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type keyArg struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a keyArg) getMap(m map[string]interface{}) map[string]interface{} {
|
||||||
|
_, ok := m[a.key]
|
||||||
|
if !ok {
|
||||||
|
m[a.key] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
switch t := m[a.key].(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
return t
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unexpected type: %v(%T)", t, t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a keyArg) set(m map[string]interface{}, value string) {
|
||||||
|
m[a.key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexedKeyArg struct {
|
||||||
|
key string
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a indexedKeyArg) getArray(m map[string]interface{}) []interface{} {
|
||||||
|
_, ok := m[a.key]
|
||||||
|
if !ok {
|
||||||
|
m[a.key] = make([]interface{}, a.index+1)
|
||||||
|
}
|
||||||
|
switch t := m[a.key].(type) {
|
||||||
|
case []interface{}:
|
||||||
|
if len(t) <= a.index {
|
||||||
|
t2 := make([]interface{}, a.index+1)
|
||||||
|
copy(t, t2)
|
||||||
|
t = t2
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unexpected type: %v(%T)", t, t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a indexedKeyArg) getMap(m map[string]interface{}) map[string]interface{} {
|
||||||
|
t := a.getArray(m)
|
||||||
|
if t[a.index] == nil {
|
||||||
|
t[a.index] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
switch t := t[a.index].(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
return t
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unexpected type: %v(%T)", t, t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a indexedKeyArg) set(m map[string]interface{}, value string) {
|
||||||
|
t := a.getArray(m)
|
||||||
|
t[a.index] = value
|
||||||
|
m[a.key] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCursor(key string) arg {
|
||||||
|
key = strings.ReplaceAll(key, "[", " ")
|
||||||
|
key = strings.ReplaceAll(key, "]", " ")
|
||||||
|
k := key
|
||||||
|
idx := 0
|
||||||
|
|
||||||
|
n, err := fmt.Sscanf(key, "%s %d", &k, &idx)
|
||||||
|
|
||||||
|
if n == 2 && err == nil {
|
||||||
|
return indexedKeyArg{
|
||||||
|
key: k,
|
||||||
|
index: idx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyArg{
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseKey(key string) []string {
|
||||||
|
r := []string{}
|
||||||
|
part := ""
|
||||||
|
escaped := false
|
||||||
|
for _, rune := range key {
|
||||||
|
split := false
|
||||||
|
switch {
|
||||||
|
case escaped == false && rune == '\\':
|
||||||
|
escaped = true
|
||||||
|
continue
|
||||||
|
case rune == '.':
|
||||||
|
split = escaped == false
|
||||||
|
}
|
||||||
|
escaped = false
|
||||||
|
if split {
|
||||||
|
r = append(r, part)
|
||||||
|
part = ""
|
||||||
|
} else {
|
||||||
|
part = part + string(rune)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(part) > 0 {
|
||||||
|
r = append(r, part)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set(m map[string]interface{}, key []string, value string) {
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
panic(fmt.Errorf("bug: unexpected length of key: %d", len(key)))
|
panic(fmt.Errorf("bug: unexpected length of key: %d", len(key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
k := key[0]
|
for len(key) > 1 {
|
||||||
|
m, key = getCursor(key[0]).getMap(m), key[1:]
|
||||||
if len(key) == 1 {
|
|
||||||
m[k] = value
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remain := key[1:]
|
getCursor(key[0]).set(m, value)
|
||||||
|
|
||||||
nested, ok := m[k]
|
|
||||||
if !ok {
|
|
||||||
nested = map[string]interface{}{}
|
|
||||||
}
|
|
||||||
switch t := nested.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
nested = Set(t, remain, value)
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unexpected type: %v(%T)", t, t))
|
|
||||||
}
|
|
||||||
|
|
||||||
m[k] = nested
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,72 @@ func TestMapUtil_IFKeys(t *testing.T) {
|
||||||
t.Errorf("unexpected c: expected=C, got=%s", c)
|
t.Errorf("unexpected c: expected=C, got=%s", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapUtil_KeyArg(t *testing.T) {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
|
||||||
|
key := []string{"a", "b", "c"}
|
||||||
|
|
||||||
|
Set(m, key, "C")
|
||||||
|
|
||||||
|
c := (((m["a"].(map[string]interface{}))["b"]).(map[string]interface{}))["c"]
|
||||||
|
|
||||||
|
if c != "C" {
|
||||||
|
t.Errorf("unexpected c: expected=C, got=%s", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapUtil_IndexedKeyArg(t *testing.T) {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
|
||||||
|
key := []string{"a", "b[0]", "c"}
|
||||||
|
|
||||||
|
Set(m, key, "C")
|
||||||
|
|
||||||
|
c := (((m["a"].(map[string]interface{}))["b"].([]interface{}))[0].(map[string]interface{}))["c"]
|
||||||
|
|
||||||
|
if c != "C" {
|
||||||
|
t.Errorf("unexpected c: expected=C, got=%s", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type parseKeyTc struct {
|
||||||
|
key string
|
||||||
|
result map[int]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapUtil_ParseKey(t *testing.T) {
|
||||||
|
tcs := []parseKeyTc{
|
||||||
|
{
|
||||||
|
key: `a.b.c`,
|
||||||
|
result: map[int]string{
|
||||||
|
0: "a",
|
||||||
|
1: "b",
|
||||||
|
2: "c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `a\.b.c`,
|
||||||
|
result: map[int]string{
|
||||||
|
0: "a.b",
|
||||||
|
1: "c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: `a\.b\.c`,
|
||||||
|
result: map[int]string{
|
||||||
|
0: "a.b.c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
parts := ParseKey(tc.key)
|
||||||
|
|
||||||
|
for index, value := range tc.result {
|
||||||
|
if parts[index] != value {
|
||||||
|
t.Errorf("unexpected key part[%d]: expected=%s, got=%s", index, value, parts[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue