terraform: take into account dependency variables in diffs

This commit is contained in:
Mitchell Hashimoto 2014-06-05 07:27:01 -07:00
parent 8358e7dc1f
commit d77a72ba84
5 changed files with 67 additions and 15 deletions

View File

@ -24,7 +24,7 @@ type ResourceProvider interface {
// Diff diffs a resource versus a desired state and returns // Diff diffs a resource versus a desired state and returns
// a diff. // a diff.
Diff( Diff(
ResourceState, *ResourceState,
map[string]interface{}) (ResourceDiff, error) map[string]interface{}) (ResourceDiff, error)
} }

View File

@ -11,9 +11,9 @@ type MockResourceProvider struct {
ConfigureReturnWarnings []string ConfigureReturnWarnings []string
ConfigureReturnError error ConfigureReturnError error
DiffCalled bool DiffCalled bool
DiffState ResourceState DiffState *ResourceState
DiffDesired map[string]interface{} DiffDesired map[string]interface{}
DiffFn func(ResourceState, map[string]interface{}) (ResourceDiff, error) DiffFn func(*ResourceState, map[string]interface{}) (ResourceDiff, error)
DiffReturn ResourceDiff DiffReturn ResourceDiff
DiffReturnError error DiffReturnError error
ResourcesCalled bool ResourcesCalled bool
@ -27,7 +27,7 @@ func (p *MockResourceProvider) Configure(c map[string]interface{}) ([]string, er
} }
func (p *MockResourceProvider) Diff( func (p *MockResourceProvider) Diff(
state ResourceState, state *ResourceState,
desired map[string]interface{}) (ResourceDiff, error) { desired map[string]interface{}) (ResourceDiff, error) {
p.DiffCalled = true p.DiffCalled = true
p.DiffState = state p.DiffState = state

View File

@ -1,10 +1,21 @@
package terraform package terraform
import (
"sync"
)
// State keeps track of a snapshot state-of-the-world that Terraform // State keeps track of a snapshot state-of-the-world that Terraform
// can use to keep track of what real world resources it is actually // can use to keep track of what real world resources it is actually
// managing. // managing.
type State struct { type State struct {
resources map[string]ResourceState resources map[string]*ResourceState
once sync.Once
}
func (s *State) init() {
s.once.Do(func() {
s.resources = make(map[string]*ResourceState)
})
} }
// ResourceState holds the state of a resource that is used so that // ResourceState holds the state of a resource that is used so that
@ -33,7 +44,7 @@ type ResourceState struct {
// computeID. // computeID.
func (s *ResourceState) MergeDiff( func (s *ResourceState) MergeDiff(
d map[string]*ResourceAttrDiff, d map[string]*ResourceAttrDiff,
computedID string) ResourceState { computedID string) *ResourceState {
var result ResourceState var result ResourceState
if s != nil { if s != nil {
result = *s result = *s
@ -54,5 +65,5 @@ func (s *ResourceState) MergeDiff(
result.Attributes[k] = diff.New result.Attributes[k] = diff.New
} }
return result return &result
} }

View File

@ -141,7 +141,19 @@ func (t *Terraform) Refresh(*State) (*State, error) {
func (t *Terraform) diffWalkFn( func (t *Terraform) diffWalkFn(
state *State, result *Diff) depgraph.WalkFunc { state *State, result *Diff) depgraph.WalkFunc {
var resultLock sync.Mutex var l sync.RWMutex
// Initialize the state since we always read it
if state == nil {
state = new(State)
state.init()
}
// Initialize the result diff so we can write to it
result.init()
// This is the value that will be used for computed properties
computedId := "computed"
return func(n *depgraph.Noun) error { return func(n *depgraph.Noun) error {
// If it is the root node, ignore // If it is the root node, ignore
@ -155,7 +167,14 @@ func (t *Terraform) diffWalkFn(
panic(fmt.Sprintf("No provider for resource: %s", r.Id())) panic(fmt.Sprintf("No provider for resource: %s", r.Id()))
} }
var rs ResourceState l.RLock()
rs := state.resources[r.Id()]
vs := t.replaceVariables(r, state)
if len(vs) > 0 {
r = r.ReplaceVariables(vs)
}
l.RUnlock()
diff, err := p.Diff(rs, r.Config) diff, err := p.Diff(rs, r.Config)
if err != nil { if err != nil {
return err return err
@ -166,16 +185,38 @@ func (t *Terraform) diffWalkFn(
return nil return nil
} }
// Acquire a lock and modify the resulting diff // Acquire a lock since this function is called in parallel
resultLock.Lock() l.Lock()
defer resultLock.Unlock() defer l.Unlock()
result.init()
// Update the resulting diff
result.Resources[r.Id()] = diff.Attributes result.Resources[r.Id()] = diff.Attributes
// Update the state for child dependencies
state.resources[r.Id()] = rs.MergeDiff(diff.Attributes, computedId)
return nil return nil
} }
} }
// replaceVariables will return the mapping of variable replacements to
// apply for a given resource with a given state.
func (t *Terraform) replaceVariables(
r *config.Resource,
s *State) map[string]string {
result := make(map[string]string)
for k, v := range t.variables {
result[k] = v
}
for n, rs := range s.resources {
for attrK, attrV := range rs.Attributes {
result[fmt.Sprintf("%s.%s", n, attrK)] = attrV
}
}
return result
}
// matchingPrefixes takes a resource type and a set of resource // matchingPrefixes takes a resource type and a set of resource
// providers we know about by prefix and returns a list of prefixes // providers we know about by prefix and returns a list of prefixes
// that might be valid for that resource. // that might be valid for that resource.

View File

@ -141,7 +141,7 @@ func testProviderFunc(n string, rs []string) ResourceProviderFactory {
return func() (ResourceProvider, error) { return func() (ResourceProvider, error) {
diffFn := func( diffFn := func(
_ ResourceState, _ *ResourceState,
c map[string]interface{}) (ResourceDiff, error) { c map[string]interface{}) (ResourceDiff, error) {
var diff ResourceDiff var diff ResourceDiff
diff.Attributes = make(map[string]*ResourceAttrDiff) diff.Attributes = make(map[string]*ResourceAttrDiff)
@ -205,7 +205,7 @@ func testTerraform(t *testing.T, name string) *Terraform {
const testTerraformDiffStr = ` const testTerraformDiffStr = `
aws_instance.bar aws_instance.bar
foo: "" => "${aws_instance.foo.num}" foo: "" => "2"
aws_instance.foo aws_instance.foo
num: "" => "2" num: "" => "2"
` `