terraform: basic apply, more tests needed

This commit is contained in:
Mitchell Hashimoto 2014-06-18 15:35:03 -07:00
parent 4711850cf3
commit b3e20a3e85
7 changed files with 176 additions and 28 deletions

12
terraform/resource.go Normal file
View File

@ -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
}

View File

@ -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.

View File

@ -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) {

View File

@ -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)
})
}

View File

@ -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

View File

@ -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,
}

View File

@ -0,0 +1,7 @@
resource "aws_instance" "foo" {
num = "2"
}
resource "aws_instance" "bar" {
foo = "${aws_instance.foo.num}"
}