diff --git a/terraform/resource.go b/terraform/resource.go new file mode 100644 index 000000000..3efa11c69 --- /dev/null +++ b/terraform/resource.go @@ -0,0 +1,12 @@ +package terraform + +// Resource encapsulates a resource, its configuration, its provider, +// its current state, and potentially a desired diff from the state it +// wants to reach. +type Resource struct { + Id string + Config *ResourceConfig + Diff *ResourceDiff + Provider ResourceProvider + State *ResourceState +} diff --git a/terraform/resource_provider.go b/terraform/resource_provider.go index dfbed2940..b435142b1 100644 --- a/terraform/resource_provider.go +++ b/terraform/resource_provider.go @@ -37,7 +37,9 @@ type ResourceProvider interface { // // If the resource state given has an empty ID, then a new resource // is expected to be created. - //Apply(ResourceState, ResourceDiff) (ResourceState, error) + Apply( + *ResourceState, + *ResourceDiff) (*ResourceState, error) // Diff diffs a resource versus a desired state and returns // a diff. diff --git a/terraform/resource_provider_mock.go b/terraform/resource_provider_mock.go index a973f7927..1e4174cc3 100644 --- a/terraform/resource_provider_mock.go +++ b/terraform/resource_provider_mock.go @@ -6,6 +6,12 @@ type MockResourceProvider struct { // Anything you want, in case you need to store extra data with the mock. Meta interface{} + ApplyCalled bool + ApplyState *ResourceState + ApplyDiff *ResourceDiff + ApplyFn func(*ResourceState, *ResourceDiff) (*ResourceState, error) + ApplyReturn *ResourceState + ApplyReturnError error ConfigureCalled bool ConfigureConfig *ResourceConfig ConfigureReturnError error @@ -35,6 +41,19 @@ func (p *MockResourceProvider) Configure(c *ResourceConfig) error { return p.ConfigureReturnError } +func (p *MockResourceProvider) Apply( + state *ResourceState, + diff *ResourceDiff) (*ResourceState, error) { + p.ApplyCalled = true + p.ApplyState = state + p.ApplyDiff = diff + if p.ApplyFn != nil { + return p.ApplyFn(state, diff) + } + + return p.ApplyReturn, p.ApplyReturnError +} + func (p *MockResourceProvider) Diff( state *ResourceState, desired *ResourceConfig) (*ResourceDiff, error) { diff --git a/terraform/state.go b/terraform/state.go index 6f56b85af..ba591449a 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -10,13 +10,14 @@ import ( // can use to keep track of what real world resources it is actually // managing. type State struct { - resources map[string]*ResourceState - once sync.Once + Resources map[string]*ResourceState + + once sync.Once } func (s *State) init() { s.once.Do(func() { - s.resources = make(map[string]*ResourceState) + s.Resources = make(map[string]*ResourceState) }) } diff --git a/terraform/terraform.go b/terraform/terraform.go index edff33a99..fae41e5d3 100644 --- a/terraform/terraform.go +++ b/terraform/terraform.go @@ -27,6 +27,10 @@ type terraformProvider struct { sync.Once } +// This is a function type used to implement a walker for the resource +// tree internally on the Terraform structure. +type genericWalkFunc func(*Resource) (map[string]string, error) + // Config is the configuration that must be given to instantiate // a Terraform structure. type Config struct { @@ -99,8 +103,14 @@ func New(c *Config) (*Terraform, error) { }, nil } -func (t *Terraform) Apply(*State, *Diff) (*State, error) { - return nil, nil +func (t *Terraform) Apply(s *State, d *Diff) (*State, error) { + result := new(State) + err := t.graph.Walk(t.applyWalkFn(s, d, result)) + if err != nil { + return nil, err + } + + return result, nil } func (t *Terraform) Diff(s *State) (*Diff, error) { @@ -117,13 +127,80 @@ func (t *Terraform) Refresh(*State) (*State, error) { return nil, nil } +func (t *Terraform) applyWalkFn( + state *State, + diff *Diff, + result *State) depgraph.WalkFunc { + var l sync.Mutex + + // Initialize the result + result.init() + + cb := func(r *Resource) (map[string]string, error) { + rs, err := r.Provider.Apply(r.State, r.Diff) + if err != nil { + return nil, err + } + + // Update the resulting diff + l.Lock() + result.Resources[r.Id] = rs + l.Unlock() + + // Determine the new state and update variables + vars := make(map[string]string) + for ak, av := range rs.Attributes { + vars[fmt.Sprintf("%s.%s", r.Id, ak)] = av + } + + return vars, nil + } + + return t.genericWalkFn(state, diff, cb) +} + func (t *Terraform) diffWalkFn( state *State, result *Diff) depgraph.WalkFunc { - var l sync.RWMutex + var l sync.Mutex // Initialize the result diff so we can write to it result.init() + cb := func(r *Resource) (map[string]string, error) { + diff, err := r.Provider.Diff(r.State, r.Config) + if err != nil { + return nil, err + } + + // If there were no diff items, return right away + if diff == nil || len(diff.Attributes) == 0 { + return nil, nil + } + + // Update the resulting diff + l.Lock() + result.Resources[r.Id] = diff + l.Unlock() + + // Determine the new state and update variables + vars := make(map[string]string) + rs := r.State.MergeDiff(diff.Attributes) + for ak, av := range rs.Attributes { + vars[fmt.Sprintf("%s.%s", r.Id, ak)] = av + } + + return vars, nil + } + + return t.genericWalkFn(state, nil, cb) +} + +func (t *Terraform) genericWalkFn( + state *State, + diff *Diff, + cb genericWalkFunc) depgraph.WalkFunc { + var l sync.Mutex + // Initialize the variables for application vars := make(map[string]string) for k, v := range t.variables { @@ -157,12 +234,17 @@ func (t *Terraform) diffWalkFn( return err } - l.RLock() + // Get the resource state var rs *ResourceState if state != nil { - rs = state.resources[r.Id()] + rs = state.Resources[r.Id()] + } + + // Get the resource diff + var rd *ResourceDiff + if diff != nil { + rd = diff.Resources[r.Id()] } - l.RUnlock() if len(vars) > 0 { if err := r.RawConfig.Interpolate(vars); err != nil { @@ -177,30 +259,30 @@ func (t *Terraform) diffWalkFn( } rs.Type = r.Type - diff, err := p.Provider.Diff(rs, &ResourceConfig{ - ComputedKeys: r.RawConfig.UnknownKeys(), - Raw: r.RawConfig.Config(), + // Call the callack + newVars, err := cb(&Resource{ + Id: r.Id(), + Config: &ResourceConfig{ + ComputedKeys: r.RawConfig.UnknownKeys(), + Raw: r.RawConfig.Config(), + }, + Diff: rd, + Provider: p.Provider, + State: rs, }) if err != nil { return err } - // If there were no diff items, return right away - if diff == nil || len(diff.Attributes) == 0 { - return nil - } + if len(newVars) > 0 { + // Acquire a lock since this function is called in parallel + l.Lock() + defer l.Unlock() - // Acquire a lock since this function is called in parallel - l.Lock() - defer l.Unlock() - - // Update the resulting diff - result.Resources[r.Id()] = diff - - // Determine the new state and update variables - rs = rs.MergeDiff(diff.Attributes) - for ak, av := range rs.Attributes { - vars[fmt.Sprintf("%s.%s", r.Id(), ak)] = av + // Update variables + for k, v := range newVars { + vars[k] = v + } } return nil diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 860da2daf..9dcfbaf3c 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -203,6 +203,22 @@ func TestNew_variables(t *testing.T) { } } +func TestTerraformApply(t *testing.T) { + tf := testTerraform(t, "apply-good") + + s := &State{} + d := &Diff{} + + state, err := tf.Apply(s, d) + if err != nil { + t.Fatalf("err: %s", err) + } + + if len(state.Resources) < 2 { + t.Fatalf("bad: %#v", state.Resources) + } +} + func TestTerraformDiff(t *testing.T) { tf := testTerraform(t, "diff-good") @@ -291,6 +307,14 @@ func testProviderFunc(n string, rs []string) ResourceProviderFactory { } return func() (ResourceProvider, error) { + applyFn := func( + s *ResourceState, + d *ResourceDiff) (*ResourceState, error) { + return &ResourceState{ + ID: "foo", + }, nil + } + diffFn := func( s *ResourceState, c *ResourceConfig) (*ResourceDiff, error) { @@ -343,6 +367,7 @@ func testProviderFunc(n string, rs []string) ResourceProviderFactory { result := &MockResourceProvider{ Meta: n, + ApplyFn: applyFn, DiffFn: diffFn, ResourcesReturn: resources, } diff --git a/terraform/test-fixtures/apply-good/main.tf b/terraform/test-fixtures/apply-good/main.tf new file mode 100644 index 000000000..94ed55478 --- /dev/null +++ b/terraform/test-fixtures/apply-good/main.tf @@ -0,0 +1,7 @@ +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "${aws_instance.foo.num}" +}