]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
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 | } |