terraform: Context.Plan
This commit is contained in:
parent
2e10ddb878
commit
403876fff3
|
@ -52,6 +52,29 @@ func NewContext(opts *ContextOpts) *Context {
|
|||
}
|
||||
}
|
||||
|
||||
// Plan generates an execution plan for the given context.
|
||||
//
|
||||
// The execution plan encapsulates the context and can be stored
|
||||
// in order to reinstantiate a context later for Apply.
|
||||
func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: c.config,
|
||||
Providers: c.providers,
|
||||
State: c.state,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Plan{
|
||||
Config: c.config,
|
||||
Vars: c.variables,
|
||||
State: c.state,
|
||||
}
|
||||
err = g.Walk(c.planWalkFn(p, opts))
|
||||
return p, err
|
||||
}
|
||||
|
||||
// Refresh goes through all the resources in the state and refreshes them
|
||||
// to their latest state. This will update the state that this context
|
||||
// works with, along with returning it.
|
||||
|
@ -96,6 +119,68 @@ func (c *Context) Validate() ([]string, []error) {
|
|||
return nil, errs
|
||||
}
|
||||
|
||||
func (c *Context) planWalkFn(result *Plan, opts *PlanOpts) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
// If we were given nil options, instantiate it
|
||||
if opts == nil {
|
||||
opts = new(PlanOpts)
|
||||
}
|
||||
|
||||
// Initialize the result
|
||||
result.init()
|
||||
|
||||
cb := func(r *Resource) (map[string]string, error) {
|
||||
var diff *ResourceDiff
|
||||
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PreDiff(r.Id, r.State))
|
||||
}
|
||||
|
||||
if opts.Destroy {
|
||||
if r.State.ID != "" {
|
||||
log.Printf("[DEBUG] %s: Making for destroy", r.Id)
|
||||
diff = &ResourceDiff{Destroy: true}
|
||||
} else {
|
||||
log.Printf("[DEBUG] %s: Not marking for destroy, no ID", r.Id)
|
||||
}
|
||||
} else if r.Config == nil {
|
||||
log.Printf("[DEBUG] %s: Orphan, marking for destroy", r.Id)
|
||||
|
||||
// This is an orphan (no config), so we mark it to be destroyed
|
||||
diff = &ResourceDiff{Destroy: true}
|
||||
} else {
|
||||
log.Printf("[DEBUG] %s: Executing diff", r.Id)
|
||||
|
||||
// Get a diff from the newest state
|
||||
var err error
|
||||
diff, err = r.Provider.Diff(r.State, r.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
if !diff.Empty() {
|
||||
result.Diff.Resources[r.Id] = diff
|
||||
}
|
||||
l.Unlock()
|
||||
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PostDiff(r.Id, diff))
|
||||
}
|
||||
|
||||
// Determine the new state and update variables
|
||||
if !diff.Empty() {
|
||||
r.State = r.State.MergeDiff(diff)
|
||||
}
|
||||
|
||||
return r.Vars(), nil
|
||||
}
|
||||
|
||||
return c.genericWalkFn(c.variables, cb)
|
||||
}
|
||||
|
||||
func (c *Context) refreshWalkFn(result *State) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package terraform
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -51,6 +52,213 @@ func TestContextValidate_requiredVar(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContextPlan(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
Config: c,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextPlan_nil(t *testing.T) {
|
||||
c := testConfig(t, "plan-nil")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
Config: c,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(plan.Diff.Resources) != 0 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextPlan_computed(t *testing.T) {
|
||||
c := testConfig(t, "plan-computed")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
Config: c,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanComputedStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextPlan_destroy(t *testing.T) {
|
||||
c := testConfig(t, "plan-destroy")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.one": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
"aws_instance.two": &ResourceState{
|
||||
ID: "baz",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
Config: c,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
State: s,
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan(&PlanOpts{Destroy: true})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) != 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanDestroyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextPlan_hook(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
h := new(MockHook)
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
Config: c,
|
||||
Hooks: []Hook{h},
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
_, err := ctx.Plan(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !h.PreDiffCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if !h.PostDiffCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextPlan_orphan(t *testing.T) {
|
||||
c := testConfig(t, "plan-orphan")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.baz": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
Config: c,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
State: s,
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanOrphanStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextPlan_state(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
p := testProvider("aws")
|
||||
p.DiffFn = testDiffFn
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := testContext(t, &ContextOpts{
|
||||
Config: c,
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(p),
|
||||
},
|
||||
State: s,
|
||||
})
|
||||
|
||||
plan, err := ctx.Plan(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanStateStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextRefresh(t *testing.T) {
|
||||
p := testProvider("aws")
|
||||
c := testConfig(t, "refresh-basic")
|
||||
|
@ -158,6 +366,79 @@ func testContext(t *testing.T, opts *ContextOpts) *Context {
|
|||
return NewContext(opts)
|
||||
}
|
||||
|
||||
func testDiffFn(
|
||||
s *ResourceState,
|
||||
c *ResourceConfig) (*ResourceDiff, error) {
|
||||
var diff ResourceDiff
|
||||
diff.Attributes = make(map[string]*ResourceAttrDiff)
|
||||
diff.Attributes["type"] = &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: s.Type,
|
||||
}
|
||||
|
||||
for k, v := range c.Raw {
|
||||
if _, ok := v.(string); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if k == "nil" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// This key is used for other purposes
|
||||
if k == "compute_value" {
|
||||
continue
|
||||
}
|
||||
|
||||
if k == "compute" {
|
||||
attrDiff := &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
|
||||
if cv, ok := c.Config["compute_value"]; ok {
|
||||
if cv.(string) == "1" {
|
||||
attrDiff.NewComputed = false
|
||||
attrDiff.New = fmt.Sprintf("computed_%s", v.(string))
|
||||
}
|
||||
}
|
||||
|
||||
diff.Attributes[v.(string)] = attrDiff
|
||||
continue
|
||||
}
|
||||
|
||||
// If this key is not computed, then look it up in the
|
||||
// cleaned config.
|
||||
found := false
|
||||
for _, ck := range c.ComputedKeys {
|
||||
if ck == k {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
v = c.Config[k]
|
||||
}
|
||||
|
||||
attrDiff := &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: v.(string),
|
||||
}
|
||||
|
||||
diff.Attributes[k] = attrDiff
|
||||
}
|
||||
|
||||
for _, k := range c.ComputedKeys {
|
||||
diff.Attributes[k] = &ResourceAttrDiff{
|
||||
Old: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
}
|
||||
|
||||
return &diff, nil
|
||||
}
|
||||
|
||||
func testProvider(prefix string) *MockResourceProvider {
|
||||
p := new(MockResourceProvider)
|
||||
p.RefreshFn = func(s *ResourceState) (*ResourceState, error) {
|
||||
|
|
|
@ -442,173 +442,6 @@ func TestTerraformApply_vars(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_nil(t *testing.T) {
|
||||
c := testConfig(t, "plan-nil")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(plan.Diff.Resources) != 0 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_computed(t *testing.T) {
|
||||
c := testConfig(t, "plan-computed")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanComputedStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_destroy(t *testing.T) {
|
||||
c := testConfig(t, "plan-destroy")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.one": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
"aws_instance.two": &ResourceState{
|
||||
ID: "baz",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{
|
||||
Destroy: true,
|
||||
Config: c,
|
||||
State: s,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) != 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanDestroyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_hook(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
h := new(MockHook)
|
||||
tf := testTerraform2(t, &Config{
|
||||
Hooks: []Hook{h},
|
||||
})
|
||||
|
||||
if _, err := tf.Plan(&PlanOpts{Config: c}); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !h.PreDiffCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if !h.PostDiffCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_orphan(t *testing.T) {
|
||||
c := testConfig(t, "plan-orphan")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.baz": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{
|
||||
Config: c,
|
||||
State: s,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanOrphanStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_state(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{
|
||||
Config: c,
|
||||
State: s,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanStateStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformRefresh(t *testing.T) {
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
|
|
Loading…
Reference in New Issue