Skip to content

Commit

Permalink
helper/schema: implement ComputedWhen
Browse files Browse the repository at this point in the history
ComputedWhen is a long `TODO`d schema feature that allows any field to
be marked as computed when some terget field changes.

Right now Terraform has no way of invalidating a field until it sees
_that_ field changes, but there are plenty of examples of scenarios
where one field changing implies that another field is going to be
assigned a new value.
  • Loading branch information
phinze committed Feb 25, 2016
1 parent 4121f79 commit 2761e3c
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 19 deletions.
5 changes: 3 additions & 2 deletions builtin/providers/atlas/resource_artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ func resourceArtifact() *schema.Resource {
},

"metadata_full": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Type: schema.TypeMap,
Computed: true,
ComputedWhen: []string{"name", "build", "version"},
},

"slug": &schema.Schema{
Expand Down
5 changes: 3 additions & 2 deletions builtin/providers/consul/resource_consul_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ func resourceConsulKeys() *schema.Resource {
},

"var": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Type: schema.TypeMap,
Computed: true,
ComputedWhen: []string{"key"},
},
},
}
Expand Down
80 changes: 78 additions & 2 deletions helper/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,18 @@ func (s *Schema) DefaultValue() (interface{}, error) {
return nil, nil
}

// MatchesKey returns whether or not the state key
// matches the schema key this schema type
func (s *Schema) KeysMatch(schemaKey, stateKey string) bool {
if s == nil {
return false
}
if s.Type.IsPrimitive() {
return schemaKey == stateKey
}
return strings.HasPrefix(stateKey, schemaKey)
}

// Returns a zero value for the schema.
func (s *Schema) ZeroValue() interface{} {
// If it's a set then we'll do a bit of extra work to provide the
Expand Down Expand Up @@ -366,9 +378,62 @@ func (m schemaMap) Diff(
}
}

// Go through and detect all of the ComputedWhens now that we've
// Go through and detect/handle all of the ComputedWhens now that we've
// finished the diff.
// TODO
for k, schema := range m {
for _, targetKey := range schema.ComputedWhen {
targetSchema := m[targetKey]
targetChanged := false
for diffKey := range result.Attributes {
if targetSchema.KeysMatch(targetKey, diffKey) {
targetChanged = true
break
}
}

if targetChanged {
// Then the ComputedWhen field should be computed
if schema.Type.IsPrimitive() {
// For primitives, we just tick on the NewComputed bit
d := &terraform.ResourceAttrDiff{}
if v, ok := result.Attributes[k]; ok {
d = v
}
d.NewComputed = true
result.Attributes[k] = d
} else {
// For list-like types, we mark the whole list as computed,
countAttrDiff := &terraform.ResourceAttrDiff{}
if v, ok := result.Attributes[k+".#"]; ok {
countAttrDiff = v
}
countAttrDiff.NewComputed = true

// then clear out any existing field-level diffs,
for diffKey := range result.Attributes {
if strings.HasPrefix(diffKey, k) {
delete(result.Attributes, diffKey)
}
}

result.Attributes[k+".#"] = countAttrDiff

// add back in current -> <computed> diffs for any of this
// attribute's fields currently in the state
if s != nil {
for stateKey, stateVal := range s.Attributes {
if strings.HasPrefix(stateKey, k) {
result.Attributes[stateKey] = &terraform.ResourceAttrDiff{
Old: stateVal,
NewComputed: true,
}
}
}
}
}
}
}
}

if result.Empty() {
// If we don't have any diff elements, just return nil
Expand Down Expand Up @@ -1293,3 +1358,14 @@ func (t ValueType) Zero() interface{} {
panic(fmt.Sprintf("unknown type %s", t))
}
}

// IsPrimitive returns true if the ValueType can be considered "primitive".
// i.e. It does not require dot-notation or multiple fields to store it.
func (t ValueType) IsPrimitive() bool {
switch t {
case TypeBool, TypeInt, TypeFloat, TypeString:
return true
default:
return false
}
}
141 changes: 128 additions & 13 deletions helper/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

"#30 ComputedWhen": {
"#30 ComputedWhen does nothing when there is no diff on target property": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Type: TypeString,
Expand Down Expand Up @@ -1194,7 +1194,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

"#31": {
"#31 ComputedWhen doesn't interfere with normal Computed behavior": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Type: TypeString,
Expand Down Expand Up @@ -1229,8 +1229,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

/* TODO
{
"The ComputedWhen attr should be computed when the target attr changes": {
Schema: map[string]*Schema{
"availability_zone": &Schema{
Type: TypeString,
Expand All @@ -1255,10 +1254,9 @@ func TestSchemaMap_Diff(t *testing.T) {
"port": 8080,
},

Diff: &terraform.ResourceDiff{
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"availability_zone": &terraform.ResourceAttrDiff{
Old: "foo",
NewComputed: true,
},
"port": &terraform.ResourceAttrDiff{
Expand All @@ -1270,9 +1268,126 @@ func TestSchemaMap_Diff(t *testing.T) {

Err: false,
},
*/

"#32 Maps": {
"ComputedWhen properly marks TypeMap as computed when the target attr changes": {
Schema: map[string]*Schema{
"mapping": &Schema{
Type: TypeMap,
Computed: true,
ComputedWhen: []string{"port"},
},

"port": &Schema{
Type: TypeInt,
Optional: true,
},
},

State: &terraform.InstanceState{
Attributes: map[string]string{
"mapping.#": "2",
"mapping.foo": "123",
"mapping.bar": "456",
"port": "80",
},
},

Config: map[string]interface{}{
"port": 8080,
},

Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"mapping.#": &terraform.ResourceAttrDiff{
Old: "2",
NewComputed: true,
},
"mapping.foo": &terraform.ResourceAttrDiff{
Old: "123",
NewComputed: true,
},
"mapping.bar": &terraform.ResourceAttrDiff{
Old: "456",
NewComputed: true,
},
"port": &terraform.ResourceAttrDiff{
Old: "80",
New: "8080",
},
},
},

Err: false,
},

"ComputedWhen works when target is a complex set": {
Schema: map[string]*Schema{
"mapping": &Schema{
Type: TypeMap,
Computed: true,
ComputedWhen: []string{"config"},
},

"config": &Schema{
Type: TypeSet,
Optional: true,
Elem: &Resource{
Schema: map[string]*Schema{
"name": &Schema{
Type: TypeString,
Required: true,
},
},
},
},
},

State: &terraform.InstanceState{
Attributes: map[string]string{
"mapping.#": "2",
"mapping.foo": "123",
"mapping.bar": "456",
"config.#": "1",
"config.965232600.name": "foo",
},
},

Config: map[string]interface{}{
"config": []map[string]interface{}{
map[string]interface{}{
"name": "bar",
},
},
},

Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"mapping.#": &terraform.ResourceAttrDiff{
Old: "2",
NewComputed: true,
},
"mapping.foo": &terraform.ResourceAttrDiff{
Old: "123",
NewComputed: true,
},
"mapping.bar": &terraform.ResourceAttrDiff{
Old: "456",
NewComputed: true,
},
"config.965232600.name": &terraform.ResourceAttrDiff{
Old: "foo",
NewRemoved: true,
},
"config.1125683609.name": &terraform.ResourceAttrDiff{
New: "bar",
},
},
},

Err: false,
},

"Map, config and no state": {
Schema: map[string]*Schema{
"config_vars": &Schema{
Type: TypeMap,
Expand Down Expand Up @@ -1306,7 +1421,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

"#33 Maps": {
"Map, changing keys": {
Schema: map[string]*Schema{
"config_vars": &Schema{
Type: TypeMap,
Expand Down Expand Up @@ -1343,7 +1458,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

"#34 Maps": {
"Computed Map, changing keys": {
Schema: map[string]*Schema{
"vars": &Schema{
Type: TypeMap,
Expand Down Expand Up @@ -1383,7 +1498,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

"#35 Maps": {
"Computed Map, no diff": {
Schema: map[string]*Schema{
"vars": &Schema{
Type: TypeMap,
Expand All @@ -1404,7 +1519,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

"#36 Maps": {
"List of Maps, changing keys": {
Schema: map[string]*Schema{
"config_vars": &Schema{
Type: TypeList,
Expand Down Expand Up @@ -1443,7 +1558,7 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},

"#37 Maps": {
"List of Maps, removed from config": {
Schema: map[string]*Schema{
"config_vars": &Schema{
Type: TypeList,
Expand Down

0 comments on commit 2761e3c

Please sign in to comment.