github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/helper/resource/testing_config.go (about) 1 package resource 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/errwrap" 9 "github.com/hashicorp/terraform/terraform" 10 ) 11 12 // testStepConfig runs a config-mode test step 13 func testStepConfig( 14 opts terraform.ContextOpts, 15 state *terraform.State, 16 step TestStep) (*terraform.State, error) { 17 return testStep(opts, state, step) 18 } 19 20 func testStep( 21 opts terraform.ContextOpts, 22 state *terraform.State, 23 step TestStep) (*terraform.State, error) { 24 mod, err := testModule(opts, step) 25 if err != nil { 26 return state, err 27 } 28 29 // Build the context 30 opts.Module = mod 31 opts.State = state 32 opts.Destroy = step.Destroy 33 ctx, err := terraform.NewContext(&opts) 34 if err != nil { 35 return state, fmt.Errorf("Error initializing context: %s", err) 36 } 37 if diags := ctx.Validate(); len(diags) > 0 { 38 if diags.HasErrors() { 39 return nil, errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) 40 } 41 42 log.Printf("[WARN] Config warnings:\n%s", diags) 43 } 44 45 // Refresh! 46 state, err = ctx.Refresh() 47 if err != nil { 48 return state, fmt.Errorf( 49 "Error refreshing: %s", err) 50 } 51 52 // If this step is a PlanOnly step, skip over this first Plan and subsequent 53 // Apply, and use the follow up Plan that checks for perpetual diffs 54 if !step.PlanOnly { 55 // Plan! 56 if p, err := ctx.Plan(); err != nil { 57 return state, fmt.Errorf( 58 "Error planning: %s", err) 59 } else { 60 log.Printf("[WARN] Test: Step plan: %s", p) 61 } 62 63 // We need to keep a copy of the state prior to destroying 64 // such that destroy steps can verify their behaviour in the check 65 // function 66 stateBeforeApplication := state.DeepCopy() 67 68 // Apply! 69 state, err = ctx.Apply() 70 if err != nil { 71 return state, fmt.Errorf("Error applying: %s", err) 72 } 73 74 // Check! Excitement! 75 if step.Check != nil { 76 if step.Destroy { 77 if err := step.Check(stateBeforeApplication); err != nil { 78 return state, fmt.Errorf("Check failed: %s", err) 79 } 80 } else { 81 if err := step.Check(state); err != nil { 82 return state, fmt.Errorf("Check failed: %s", err) 83 } 84 } 85 } 86 } 87 88 // Now, verify that Plan is now empty and we don't have a perpetual diff issue 89 // We do this with TWO plans. One without a refresh. 90 var p *terraform.Plan 91 if p, err = ctx.Plan(); err != nil { 92 return state, fmt.Errorf("Error on follow-up plan: %s", err) 93 } 94 if p.Diff != nil && !p.Diff.Empty() { 95 if step.ExpectNonEmptyPlan { 96 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) 97 } else { 98 return state, fmt.Errorf( 99 "After applying this step, the plan was not empty:\n\n%s", p) 100 } 101 } 102 103 // And another after a Refresh. 104 if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) { 105 state, err = ctx.Refresh() 106 if err != nil { 107 return state, fmt.Errorf( 108 "Error on follow-up refresh: %s", err) 109 } 110 } 111 if p, err = ctx.Plan(); err != nil { 112 return state, fmt.Errorf("Error on second follow-up plan: %s", err) 113 } 114 empty := p.Diff == nil || p.Diff.Empty() 115 116 // Data resources are tricky because they legitimately get instantiated 117 // during refresh so that they will be already populated during the 118 // plan walk. Because of this, if we have any data resources in the 119 // config we'll end up wanting to destroy them again here. This is 120 // acceptable and expected, and we'll treat it as "empty" for the 121 // sake of this testing. 122 if step.Destroy { 123 empty = true 124 125 for _, moduleDiff := range p.Diff.Modules { 126 for k, instanceDiff := range moduleDiff.Resources { 127 if !strings.HasPrefix(k, "data.") { 128 empty = false 129 break 130 } 131 132 if !instanceDiff.Destroy { 133 empty = false 134 } 135 } 136 } 137 } 138 139 if !empty { 140 if step.ExpectNonEmptyPlan { 141 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) 142 } else { 143 return state, fmt.Errorf( 144 "After applying this step and refreshing, "+ 145 "the plan was not empty:\n\n%s", p) 146 } 147 } 148 149 // Made it here, but expected a non-empty plan, fail! 150 if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) { 151 return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") 152 } 153 154 // Made it here? Good job test step! 155 return state, nil 156 }