From fe52ce1115d13c840c105002bff63ddc55d331f8 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Tue, 7 Nov 2017 11:11:49 -0500 Subject: [PATCH] Add TypeKVPairs field type (#3535) --- logical/framework/backend.go | 2 + logical/framework/field_data.go | 30 ++++++++++++- logical/framework/field_data_test.go | 65 ++++++++++++++++++++++++++++ logical/framework/field_type.go | 6 +++ 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/logical/framework/backend.go b/logical/framework/backend.go index 4b9b8b949..5fad6c645 100644 --- a/logical/framework/backend.go +++ b/logical/framework/backend.go @@ -614,6 +614,8 @@ func (t FieldType) Zero() interface{} { return false case TypeMap: return map[string]interface{}{} + case TypeKVPairs: + return map[string]string{} case TypeDurationSecond: return 0 case TypeSlice: diff --git a/logical/framework/field_data.go b/logical/framework/field_data.go index 7fac97652..1d03c335e 100644 --- a/logical/framework/field_data.go +++ b/logical/framework/field_data.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "regexp" + "strings" "github.com/hashicorp/vault/helper/parseutil" "github.com/hashicorp/vault/helper/strutil" @@ -34,7 +35,8 @@ func (d *FieldData) Validate() error { switch schema.Type { case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString, - TypeNameString, TypeSlice, TypeStringSlice, TypeCommaStringSlice: + TypeNameString, TypeSlice, TypeStringSlice, TypeCommaStringSlice, + TypeKVPairs: _, _, err := d.getPrimitive(field, schema) if err != nil { return fmt.Errorf("Error converting input %v for field %s: %s", value, field, err) @@ -110,7 +112,8 @@ func (d *FieldData) GetOkErr(k string) (interface{}, bool, error) { switch schema.Type { case TypeBool, TypeInt, TypeMap, TypeDurationSecond, TypeString, - TypeNameString, TypeSlice, TypeStringSlice, TypeCommaStringSlice: + TypeNameString, TypeSlice, TypeStringSlice, TypeCommaStringSlice, + TypeKVPairs: return d.getPrimitive(k, schema) default: return nil, false, @@ -236,6 +239,29 @@ func (d *FieldData) getPrimitive( } return strutil.TrimStrings(result), true, nil + case TypeKVPairs: + // First try to parse this as a map + var mapResult map[string]string + if err := mapstructure.WeakDecode(raw, &mapResult); err == nil { + return mapResult, true, nil + } + + // If map parse fails, parse as a string list of = delimited pairs + var listResult []string + if err := mapstructure.WeakDecode(raw, &listResult); err != nil { + return nil, true, err + } + + result := make(map[string]string, len(listResult)) + for _, keyPair := range listResult { + keyPairSlice := strings.SplitN(keyPair, "=", 2) + if len(keyPairSlice) != 2 || keyPairSlice[0] == "" { + return nil, false, fmt.Errorf("invalid key pair %q", keyPair) + } + result[keyPairSlice[0]] = keyPairSlice[1] + } + return result, true, nil + default: panic(fmt.Sprintf("Unknown type: %s", schema.Type)) } diff --git a/logical/framework/field_data_test.go b/logical/framework/field_data_test.go index 90c1660c0..c9edd82e2 100644 --- a/logical/framework/field_data_test.go +++ b/logical/framework/field_data_test.go @@ -279,6 +279,53 @@ func TestFieldDataGet(t *testing.T) { "bar.baz-bay123", }, + "keypair type, valid value map type": { + map[string]*FieldSchema{ + "foo": &FieldSchema{Type: TypeKVPairs}, + }, + map[string]interface{}{ + "foo": map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": 1, + }, + }, + "foo", + map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "1", + }, + }, + + "keypair type, list of equal sign delim key pairs type": { + map[string]*FieldSchema{ + "foo": &FieldSchema{Type: TypeKVPairs}, + }, + map[string]interface{}{ + "foo": []interface{}{"key1=value1", "key2=value2", "key3=1"}, + }, + "foo", + map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "1", + }, + }, + + "keypair type, single equal sign delim value": { + map[string]*FieldSchema{ + "foo": &FieldSchema{Type: TypeKVPairs}, + }, + map[string]interface{}{ + "foo": "key1=value1", + }, + "foo", + map[string]string{ + "key1": "value1", + }, + }, + "name string type, not supplied": { map[string]*FieldSchema{ "foo": {Type: TypeNameString}, @@ -359,6 +406,15 @@ func TestFieldDataGet(t *testing.T) { "foo", []string{}, }, + + "type kv pair, not supplied": { + map[string]*FieldSchema{ + "foo": {Type: TypeKVPairs}, + }, + map[string]interface{}{}, + "foo", + map[string]string{}, + }, } for name, tc := range cases { @@ -422,6 +478,15 @@ func TestFieldDataGet_Error(t *testing.T) { }, "foo", }, + "keypair type, csv version empty key name": { + map[string]*FieldSchema{ + "foo": &FieldSchema{Type: TypeKVPairs}, + }, + map[string]interface{}{ + "foo": []interface{}{"=value1", "key2=value2", "key3=1"}, + }, + "foo", + }, } for _, tc := range cases { diff --git a/logical/framework/field_type.go b/logical/framework/field_type.go index 548655cb3..f18e442ba 100644 --- a/logical/framework/field_type.go +++ b/logical/framework/field_type.go @@ -30,6 +30,10 @@ const ( // rules. These rules include start and end with an alphanumeric // character and characters in the middle can be alphanumeric or . or -. TypeNameString + + // TypeKVPairs allows you to represent the data as a map or a list of + // equal sign delimited key pairs + TypeKVPairs ) func (t FieldType) String() string { @@ -44,6 +48,8 @@ func (t FieldType) String() string { return "bool" case TypeMap: return "map" + case TypeKVPairs: + return "keypair" case TypeDurationSecond: return "duration (sec)" case TypeSlice, TypeStringSlice, TypeCommaStringSlice: