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