helper/schema: PromoteSingle for legacy support of "maybe list" types
This commit is contained in:
parent
f29845e54e
commit
487a37b0dd
|
@ -23,6 +23,7 @@ func Provisioner() terraform.ResourceProvisioner {
|
|||
"inline": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{Type: schema.TypeString},
|
||||
PromoteSingle: true,
|
||||
Optional: true,
|
||||
ConflictsWith: []string{"script", "scripts"},
|
||||
},
|
||||
|
|
|
@ -79,10 +79,35 @@ func (r *ConfigFieldReader) readField(
|
|||
|
||||
k := strings.Join(address, ".")
|
||||
schema := schemaList[len(schemaList)-1]
|
||||
|
||||
// If we're getting the single element of a promoted list, then
|
||||
// check to see if we have a single element we need to promote.
|
||||
if address[len(address)-1] == "0" && len(schemaList) > 1 {
|
||||
lastSchema := schemaList[len(schemaList)-2]
|
||||
if lastSchema.Type == TypeList && lastSchema.PromoteSingle {
|
||||
k := strings.Join(address[:len(address)-1], ".")
|
||||
result, err := r.readPrimitive(k, schema)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch schema.Type {
|
||||
case TypeBool, TypeFloat, TypeInt, TypeString:
|
||||
return r.readPrimitive(k, schema)
|
||||
case TypeList:
|
||||
// If we support promotion then we first check if we have a lone
|
||||
// value that we must promote.
|
||||
// a value that is alone.
|
||||
if schema.PromoteSingle {
|
||||
result, err := r.readPrimitive(k, schema.Elem.(*Schema))
|
||||
if err == nil && result.Exists {
|
||||
result.Value = []interface{}{result.Value}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
return readListField(&nestedConfigFieldReader{r}, address, schema)
|
||||
case TypeMap:
|
||||
return r.readMap(k, schema)
|
||||
|
|
|
@ -118,9 +118,16 @@ type Schema struct {
|
|||
// TypeSet or TypeList. Specific use cases would be if a TypeSet is being
|
||||
// used to wrap a complex structure, however less than one instance would
|
||||
// cause instability.
|
||||
Elem interface{}
|
||||
MaxItems int
|
||||
MinItems int
|
||||
//
|
||||
// PromoteSingle, if true, will allow single elements to be standalone
|
||||
// and promote them to a list. For example "foo" would be promoted to
|
||||
// ["foo"] automatically. This is primarily for legacy reasons and the
|
||||
// ambiguity is not recommended for new usage. Promotion is only allowed
|
||||
// for primitive element types.
|
||||
Elem interface{}
|
||||
MaxItems int
|
||||
MinItems int
|
||||
PromoteSingle bool
|
||||
|
||||
// The following fields are only valid for a TypeSet type.
|
||||
//
|
||||
|
@ -1140,6 +1147,14 @@ func (m schemaMap) validateList(
|
|||
// We use reflection to verify the slice because you can't
|
||||
// case to []interface{} unless the slice is exactly that type.
|
||||
rawV := reflect.ValueOf(raw)
|
||||
|
||||
// If we support promotion and the raw value isn't a slice, wrap
|
||||
// it in []interface{} and check again.
|
||||
if schema.PromoteSingle && rawV.Kind() != reflect.Slice {
|
||||
raw = []interface{}{raw}
|
||||
rawV = reflect.ValueOf(raw)
|
||||
}
|
||||
|
||||
if rawV.Kind() != reflect.Slice {
|
||||
return nil, []error{fmt.Errorf(
|
||||
"%s: should be a list", k)}
|
||||
|
|
|
@ -582,6 +582,72 @@ func TestSchemaMap_Diff(t *testing.T) {
|
|||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "List decode with promotion",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
PromoteSingle: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ports": "5",
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ports.#": &terraform.ResourceAttrDiff{
|
||||
Old: "0",
|
||||
New: "1",
|
||||
},
|
||||
"ports.0": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "5",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "List decode with promotion with list",
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
Type: TypeList,
|
||||
Required: true,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
PromoteSingle: true,
|
||||
},
|
||||
},
|
||||
|
||||
State: nil,
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ports": []interface{}{"5"},
|
||||
},
|
||||
|
||||
Diff: &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"ports.#": &terraform.ResourceAttrDiff{
|
||||
Old: "0",
|
||||
New: "1",
|
||||
},
|
||||
"ports.0": &terraform.ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "5",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
Schema: map[string]*Schema{
|
||||
"ports": &Schema{
|
||||
|
@ -3585,6 +3651,40 @@ func TestSchemaMap_Validate(t *testing.T) {
|
|||
Err: true,
|
||||
},
|
||||
|
||||
"List with promotion": {
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeList,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
PromoteSingle: true,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ingress": "5",
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
"List with promotion set as list": {
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
Type: TypeList,
|
||||
Elem: &Schema{Type: TypeInt},
|
||||
PromoteSingle: true,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
|
||||
Config: map[string]interface{}{
|
||||
"ingress": []interface{}{"5"},
|
||||
},
|
||||
|
||||
Err: false,
|
||||
},
|
||||
|
||||
"Optional sub-resource": {
|
||||
Schema: map[string]*Schema{
|
||||
"ingress": &Schema{
|
||||
|
|
Loading…
Reference in New Issue