mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 03:58:01 +00:00
List Handling in API and CLI (#2584)
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/helper/jsonutil"
|
"github.com/hashicorp/vault/helper/jsonutil"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Builder is a struct to build a key/value mapping based on a list
|
// Builder is a struct to build a key/value mapping based on a list
|
||||||
@@ -107,6 +108,17 @@ func (b *Builder) add(raw string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repeated keys will be converted into a slice
|
||||||
|
if existingValue, ok := b.result[key]; ok {
|
||||||
|
var sliceValue []interface{}
|
||||||
|
if err := mapstructure.WeakDecode(existingValue, &sliceValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sliceValue = append(sliceValue, value)
|
||||||
|
b.result[key] = sliceValue
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
b.result[key] = value
|
b.result[key] = value
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,3 +85,36 @@ func TestBuilder_stdinTwice(t *testing.T) {
|
|||||||
t.Fatal("should error")
|
t.Fatal("should error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuilder_sameKeyTwice(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
err := b.Add("foo=bar", "foo=baz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"foo": []interface{}{"bar", "baz"},
|
||||||
|
}
|
||||||
|
actual := b.Map()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuilder_sameKeyMultipleTimes(t *testing.T) {
|
||||||
|
var b Builder
|
||||||
|
err := b.Add("foo=bar", "foo=baz", "foo=bay", "foo=bax", "bar=baz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"foo": []interface{}{"bar", "baz", "bay", "bax"},
|
||||||
|
"bar": "baz",
|
||||||
|
}
|
||||||
|
actual := b.Map()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -174,6 +174,16 @@ func ParseArbitraryStringSlice(input string, sep string) []string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrimStrings takes a slice of strings and returns a slice of strings
|
||||||
|
// with trimmed spaces
|
||||||
|
func TrimStrings(items []string) []string {
|
||||||
|
ret := make([]string, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
ret[i] = strings.TrimSpace(item)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// Removes duplicate and empty elements from a slice of strings. This also may
|
// Removes duplicate and empty elements from a slice of strings. This also may
|
||||||
// convert the items in the slice to lower case and returns a sorted slice.
|
// convert the items in the slice to lower case and returns a sorted slice.
|
||||||
func RemoveDuplicates(items []string, lowercase bool) []string {
|
func RemoveDuplicates(items []string, lowercase bool) []string {
|
||||||
|
|||||||
@@ -315,3 +315,12 @@ func TestGlobbedStringsMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTrimStrings(t *testing.T) {
|
||||||
|
input := []string{"abc", "123", "abcd ", "123 "}
|
||||||
|
expected := []string{"abc", "123", "abcd", "123"}
|
||||||
|
actual := TrimStrings(input)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Fatalf("Bad TrimStrings: expected:%#v, got:%#v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
log "github.com/mgutz/logxi/v1"
|
log "github.com/mgutz/logxi/v1"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/vault/helper/parseutil"
|
|
||||||
"github.com/hashicorp/vault/helper/errutil"
|
"github.com/hashicorp/vault/helper/errutil"
|
||||||
"github.com/hashicorp/vault/helper/logformat"
|
"github.com/hashicorp/vault/helper/logformat"
|
||||||
|
"github.com/hashicorp/vault/helper/parseutil"
|
||||||
"github.com/hashicorp/vault/logical"
|
"github.com/hashicorp/vault/logical"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -587,6 +587,10 @@ func (t FieldType) Zero() interface{} {
|
|||||||
return map[string]interface{}{}
|
return map[string]interface{}{}
|
||||||
case TypeDurationSecond:
|
case TypeDurationSecond:
|
||||||
return 0
|
return 0
|
||||||
|
case TypeSlice:
|
||||||
|
return []interface{}{}
|
||||||
|
case TypeStringSlice, TypeCommaStringSlice:
|
||||||
|
return []string{}
|
||||||
default:
|
default:
|
||||||
panic("unknown type: " + t.String())
|
panic("unknown type: " + t.String())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/helper/parseutil"
|
"github.com/hashicorp/vault/helper/parseutil"
|
||||||
|
"github.com/hashicorp/vault/helper/strutil"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,7 +31,8 @@ func (d *FieldData) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch schema.Type {
|
switch schema.Type {
|
||||||
case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString:
|
case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString, TypeSlice,
|
||||||
|
TypeStringSlice, TypeCommaStringSlice:
|
||||||
_, _, err := d.getPrimitive(field, schema)
|
_, _, err := d.getPrimitive(field, schema)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error converting input %v for field %s: %s", value, field, err)
|
return fmt.Errorf("Error converting input %v for field %s: %s", value, field, err)
|
||||||
@@ -105,7 +107,8 @@ func (d *FieldData) GetOkErr(k string) (interface{}, bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch schema.Type {
|
switch schema.Type {
|
||||||
case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString:
|
case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString,
|
||||||
|
TypeSlice, TypeStringSlice, TypeCommaStringSlice:
|
||||||
return d.getPrimitive(k, schema)
|
return d.getPrimitive(k, schema)
|
||||||
default:
|
default:
|
||||||
return nil, false,
|
return nil, false,
|
||||||
@@ -177,6 +180,36 @@ func (d *FieldData) getPrimitive(
|
|||||||
}
|
}
|
||||||
return result, true, nil
|
return result, true, nil
|
||||||
|
|
||||||
|
case TypeSlice:
|
||||||
|
var result []interface{}
|
||||||
|
if err := mapstructure.WeakDecode(raw, &result); err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
return result, true, nil
|
||||||
|
|
||||||
|
case TypeStringSlice:
|
||||||
|
var result []string
|
||||||
|
if err := mapstructure.WeakDecode(raw, &result); err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
return strutil.TrimStrings(result), true, nil
|
||||||
|
|
||||||
|
case TypeCommaStringSlice:
|
||||||
|
var result []string
|
||||||
|
config := &mapstructure.DecoderConfig{
|
||||||
|
Result: &result,
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
DecodeHook: mapstructure.StringToSliceHookFunc(","),
|
||||||
|
}
|
||||||
|
decoder, err := mapstructure.NewDecoder(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(raw); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return strutil.TrimStrings(result), true, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("Unknown type: %s", schema.Type))
|
panic(fmt.Sprintf("Unknown type: %s", schema.Type))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,105 @@ func TestFieldDataGet(t *testing.T) {
|
|||||||
"foo",
|
"foo",
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"slice type, empty slice": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{},
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]interface{}{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"slice type, filled, mixed slice": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{123, "abc"},
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]interface{}{123, "abc"},
|
||||||
|
},
|
||||||
|
|
||||||
|
"string slice type, filled slice": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeStringSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{123, "abc"},
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]string{"123", "abc"},
|
||||||
|
},
|
||||||
|
|
||||||
|
"comma string slice type, comma string with one value": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeCommaStringSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "value1",
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]string{"value1"},
|
||||||
|
},
|
||||||
|
|
||||||
|
"comma string slice type, comma string with multi value": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeCommaStringSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "value1,value2,value3",
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]string{"value1", "value2", "value3"},
|
||||||
|
},
|
||||||
|
|
||||||
|
"comma string slice type, nil string slice value": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeCommaStringSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": "",
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
|
|
||||||
|
"commma string slice type, string slice with one value": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeCommaStringSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{"value1"},
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]string{"value1"},
|
||||||
|
},
|
||||||
|
|
||||||
|
"comma string slice type, string slice with multi value": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeCommaStringSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{"value1", "value2", "value3"},
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]string{"value1", "value2", "value3"},
|
||||||
|
},
|
||||||
|
|
||||||
|
"comma string slice type, empty string slice value": {
|
||||||
|
map[string]*FieldSchema{
|
||||||
|
"foo": &FieldSchema{Type: TypeCommaStringSlice},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{},
|
||||||
|
},
|
||||||
|
"foo",
|
||||||
|
[]string{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range cases {
|
for name, tc := range cases {
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ const (
|
|||||||
// TypeDurationSecond represent as seconds, this can be either an
|
// TypeDurationSecond represent as seconds, this can be either an
|
||||||
// integer or go duration format string (e.g. 24h)
|
// integer or go duration format string (e.g. 24h)
|
||||||
TypeDurationSecond
|
TypeDurationSecond
|
||||||
|
|
||||||
|
// TypeSlice represents a slice of any type
|
||||||
|
TypeSlice
|
||||||
|
// TypeStringSlice is a helper for TypeSlice that returns a sanitized
|
||||||
|
// slice of strings
|
||||||
|
TypeStringSlice
|
||||||
|
// TypeCommaStringSlice is a helper for TypeSlice that returns a sanitized
|
||||||
|
// slice of strings and also supports parsing a comma-separated list in
|
||||||
|
// a string field
|
||||||
|
TypeCommaStringSlice
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t FieldType) String() string {
|
func (t FieldType) String() string {
|
||||||
@@ -27,6 +37,8 @@ func (t FieldType) String() string {
|
|||||||
return "map"
|
return "map"
|
||||||
case TypeDurationSecond:
|
case TypeDurationSecond:
|
||||||
return "duration (sec)"
|
return "duration (sec)"
|
||||||
|
case TypeSlice, TypeStringSlice, TypeCommaStringSlice:
|
||||||
|
return "slice"
|
||||||
default:
|
default:
|
||||||
return "unknown type"
|
return "unknown type"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user