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