terraform: Refresh tests

This commit is contained in:
Mitchell Hashimoto 2014-06-25 15:39:44 -07:00
parent abe205fc27
commit 8a44ca984e
4 changed files with 128 additions and 102 deletions

View File

@ -110,9 +110,11 @@ func graphAddConfigResources(g *depgraph.Graph, c *config.Config) {
noun := &depgraph.Noun{ noun := &depgraph.Noun{
Name: r.Id(), Name: r.Id(),
Meta: &GraphNodeResource{ Meta: &GraphNodeResource{
Type: r.Type, Type: r.Type,
Config: r, Config: r,
Resource: new(Resource), Resource: &Resource{
Id: r.Id(),
},
}, },
} }
nouns[noun.Name] = noun nouns[noun.Name] = noun
@ -197,6 +199,7 @@ func graphAddOrphans(g *depgraph.Graph, c *config.Config, s *State) {
Type: rs.Type, Type: rs.Type,
Orphan: true, Orphan: true,
Resource: &Resource{ Resource: &Resource{
Id: k,
State: rs, State: rs,
}, },
}, },

View File

@ -2,6 +2,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log"
"sync" "sync"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
@ -13,7 +14,6 @@ import (
// all resources, a resource tree, a specific resource, etc. // all resources, a resource tree, a specific resource, etc.
type Terraform struct { type Terraform struct {
providers map[string]ResourceProviderFactory providers map[string]ResourceProviderFactory
variables map[string]string
} }
// terraformProvider contains internal state information about a resource // terraformProvider contains internal state information about a resource
@ -85,7 +85,6 @@ func New(c *Config) (*Terraform, error) {
return &Terraform{ return &Terraform{
providers: c.Providers, providers: c.Providers,
variables: c.Variables,
}, nil }, nil
} }
@ -117,6 +116,11 @@ func (t *Terraform) Graph(c *config.Config, s *State) (*depgraph.Graph, error) {
return nil, err return nil, err
} }
// Validate the graph so that it can setup a root and such
if err := g.Validate(); err != nil {
return nil, err
}
return g, nil return g, nil
} }
@ -138,15 +142,42 @@ func (t *Terraform) Plan(s *State) (*Plan, error) {
// Refresh goes through all the resources in the state and refreshes them // Refresh goes through all the resources in the state and refreshes them
// to their latest status. // to their latest status.
func (t *Terraform) Refresh(c *config.Config, s *State) (*State, error) { func (t *Terraform) Refresh(
_, err := t.Graph(c, s) c *config.Config, s *State, vs map[string]string) (*State, error) {
g, err := t.Graph(c, s)
if err != nil { if err != nil {
return s, err return s, err
} }
result := new(State) return t.refresh(g, vs)
//err = graph.Walk(t.refreshWalkFn(s, result)) }
return result, err
func (t *Terraform) refresh(g *depgraph.Graph, vars map[string]string) (*State, error) {
s := new(State)
err := g.Walk(t.refreshWalkFn(vars, s))
return s, err
}
func (t *Terraform) refreshWalkFn(vars map[string]string, result *State) depgraph.WalkFunc {
var l sync.Mutex
// Initialize the result so we don't have to nil check everywhere
result.init()
cb := func(r *Resource) (map[string]string, error) {
rs, err := r.Provider.Refresh(r.State)
if err != nil {
return nil, err
}
l.Lock()
result.Resources[r.Id] = rs
l.Unlock()
return nil, nil
}
return t.genericWalkFn(vars, cb)
} }
func (t *Terraform) applyWalkFn( func (t *Terraform) applyWalkFn(
@ -209,7 +240,7 @@ func (t *Terraform) applyWalkFn(
return vars, err return vars, err
} }
return t.genericWalkFn(p.State, p.Diff, p.Vars, cb) return t.genericWalkFn(p.Vars, cb)
} }
func (t *Terraform) planWalkFn( func (t *Terraform) planWalkFn(
@ -223,10 +254,12 @@ func (t *Terraform) planWalkFn(
//result.Config = t.config //result.Config = t.config
// Copy the variables // Copy the variables
result.Vars = make(map[string]string) /*
for k, v := range t.variables { result.Vars = make(map[string]string)
result.Vars[k] = v for k, v := range t.variables {
} result.Vars[k] = v
}
*/
cb := func(r *Resource) (map[string]string, error) { cb := func(r *Resource) (map[string]string, error) {
// Refresh the state so we're working with the latest resource info // Refresh the state so we're working with the latest resource info
@ -271,15 +304,13 @@ func (t *Terraform) planWalkFn(
return vars, nil return vars, nil
} }
return t.genericWalkFn(state, nil, t.variables, cb) return t.genericWalkFn(nil, cb)
} }
func (t *Terraform) genericWalkFn( func (t *Terraform) genericWalkFn(
state *State,
diff *Diff,
invars map[string]string, invars map[string]string,
cb genericWalkFunc) depgraph.WalkFunc { cb genericWalkFunc) depgraph.WalkFunc {
//var l sync.Mutex var l sync.Mutex
// Initialize the variables for application // Initialize the variables for application
vars := make(map[string]string) vars := make(map[string]string)
@ -288,99 +319,60 @@ func (t *Terraform) genericWalkFn(
} }
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 if n.Name == GraphRootNode {
if n.Meta == nil { return nil
return nil }
switch m := n.Meta.(type) {
case *GraphNodeResource:
case *GraphNodeResourceProvider:
var rc *ResourceConfig
if m.Config != nil {
if err := m.Config.RawConfig.Interpolate(vars); err != nil {
panic(err)
}
rc = NewResourceConfig(m.Config.RawConfig)
} }
switch n.Meta.(type) { for k, p := range m.Providers {
case *config.ProviderConfig: log.Printf("Configuring provider: %s", k)
// Ignore, we don't treat this any differently since we always err := p.Configure(rc)
// initialize the provider on first use and use a lock to make if err != nil {
// sure we only do this once. return err
return nil
case *config.Resource:
// Continue
}
r := n.Meta.(*config.Resource)
p := t.mapping[r]
if p == nil {
panic(fmt.Sprintf("No provider for resource: %s", r.Id()))
}
// Initialize the provider if we haven't already
if err := p.init(vars); err != nil {
return err
}
// Get the resource state
var rs *ResourceState
if state != nil {
rs = state.Resources[r.Id()]
}
// Get the resource diff
var rd *ResourceDiff
if diff != nil {
rd = diff.Resources[r.Id()]
}
if len(vars) > 0 {
if err := r.RawConfig.Interpolate(vars); err != nil {
panic(fmt.Sprintf("Interpolate error: %s", err))
} }
} }
// If we have no state, then create an empty state with the return nil
// type fulfilled at the least. }
if rs == nil {
rs = new(ResourceState)
}
rs.Type = r.Type
// Call the callack rn := n.Meta.(*GraphNodeResource)
newVars, err := cb(&Resource{ if len(vars) > 0 && rn.Config != nil {
Id: r.Id(), if err := rn.Config.RawConfig.Interpolate(vars); err != nil {
Config: NewResourceConfig(r.RawConfig), panic(fmt.Sprintf("Interpolate error: %s", err))
Diff: rd,
Provider: p.Provider,
State: rs,
})
if err != nil {
return err
} }
if len(newVars) > 0 { // Set the config
// Acquire a lock since this function is called in parallel rn.Resource.Config = NewResourceConfig(rn.Config.RawConfig)
l.Lock() }
defer l.Unlock()
// Update variables // Call the callack
for k, v := range newVars { newVars, err := cb(rn.Resource)
vars[k] = v if err != nil {
} return err
}
if len(newVars) > 0 {
// Acquire a lock since this function is called in parallel
l.Lock()
defer l.Unlock()
// Update variables
for k, v := range newVars {
vars[k] = v
} }
*/ }
return nil return nil
} }
} }
func (t *terraformProvider) init(vars map[string]string) (err error) {
t.Once.Do(func() {
var rc *ResourceConfig
if t.Config != nil {
if err := t.Config.RawConfig.Interpolate(vars); err != nil {
panic(err)
}
rc = NewResourceConfig(t.Config.RawConfig)
}
err = t.Provider.Configure(rc)
})
return
}

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
@ -87,7 +88,7 @@ func TestTerraformApply_unknownAttribute(t *testing.T) {
func TestTerraformApply_vars(t *testing.T) { func TestTerraformApply_vars(t *testing.T) {
tf := testTerraform(t, "apply-vars") tf := testTerraform(t, "apply-vars")
tf.variables = map[string]string{"foo": "baz"} //tf.variables = map[string]string{"foo": "baz"}
s := &State{} s := &State{}
p, err := tf.Plan(s) p, err := tf.Plan(s)
@ -208,6 +209,35 @@ func TestTerraformPlan_refreshNil(t *testing.T) {
} }
} }
func TestTerraformRefresh(t *testing.T) {
rpAWS := new(MockResourceProvider)
rpAWS.ResourcesReturn = []ResourceType{
ResourceType{Name: "aws_instance"},
}
c := testConfig(t, "refresh-basic")
tf := testTerraform2(t, &Config{
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(rpAWS),
},
})
rpAWS.RefreshReturn = &ResourceState{
ID: "foo",
}
s, err := tf.Refresh(c, nil, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if !rpAWS.RefreshCalled {
t.Fatal("refresh should be called")
}
if !reflect.DeepEqual(s.Resources["aws_instance.web"], rpAWS.RefreshReturn) {
t.Fatalf("bad: %#v", s.Resources)
}
}
func testConfig(t *testing.T, name string) *config.Config { func testConfig(t *testing.T, name string) *config.Config {
c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf")) c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf"))
if err != nil { if err != nil {

View File

@ -0,0 +1 @@
resource "aws_instance" "web" {}