]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
Merge pull request #27 from terraform-providers/go-modules-2019-02-22
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / helper / resource / testing_config.go
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 }