diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/resource')
9 files changed, 1741 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/error.go b/vendor/github.com/hashicorp/terraform/helper/resource/error.go new file mode 100644 index 0000000..7ee2161 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/error.go | |||
@@ -0,0 +1,79 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strings" | ||
6 | "time" | ||
7 | ) | ||
8 | |||
9 | type NotFoundError struct { | ||
10 | LastError error | ||
11 | LastRequest interface{} | ||
12 | LastResponse interface{} | ||
13 | Message string | ||
14 | Retries int | ||
15 | } | ||
16 | |||
17 | func (e *NotFoundError) Error() string { | ||
18 | if e.Message != "" { | ||
19 | return e.Message | ||
20 | } | ||
21 | |||
22 | if e.Retries > 0 { | ||
23 | return fmt.Sprintf("couldn't find resource (%d retries)", e.Retries) | ||
24 | } | ||
25 | |||
26 | return "couldn't find resource" | ||
27 | } | ||
28 | |||
29 | // UnexpectedStateError is returned when Refresh returns a state that's neither in Target nor Pending | ||
30 | type UnexpectedStateError struct { | ||
31 | LastError error | ||
32 | State string | ||
33 | ExpectedState []string | ||
34 | } | ||
35 | |||
36 | func (e *UnexpectedStateError) Error() string { | ||
37 | return fmt.Sprintf( | ||
38 | "unexpected state '%s', wanted target '%s'. last error: %s", | ||
39 | e.State, | ||
40 | strings.Join(e.ExpectedState, ", "), | ||
41 | e.LastError, | ||
42 | ) | ||
43 | } | ||
44 | |||
45 | // TimeoutError is returned when WaitForState times out | ||
46 | type TimeoutError struct { | ||
47 | LastError error | ||
48 | LastState string | ||
49 | Timeout time.Duration | ||
50 | ExpectedState []string | ||
51 | } | ||
52 | |||
53 | func (e *TimeoutError) Error() string { | ||
54 | expectedState := "resource to be gone" | ||
55 | if len(e.ExpectedState) > 0 { | ||
56 | expectedState = fmt.Sprintf("state to become '%s'", strings.Join(e.ExpectedState, ", ")) | ||
57 | } | ||
58 | |||
59 | extraInfo := make([]string, 0) | ||
60 | if e.LastState != "" { | ||
61 | extraInfo = append(extraInfo, fmt.Sprintf("last state: '%s'", e.LastState)) | ||
62 | } | ||
63 | if e.Timeout > 0 { | ||
64 | extraInfo = append(extraInfo, fmt.Sprintf("timeout: %s", e.Timeout.String())) | ||
65 | } | ||
66 | |||
67 | suffix := "" | ||
68 | if len(extraInfo) > 0 { | ||
69 | suffix = fmt.Sprintf(" (%s)", strings.Join(extraInfo, ", ")) | ||
70 | } | ||
71 | |||
72 | if e.LastError != nil { | ||
73 | return fmt.Sprintf("timeout while waiting for %s%s: %s", | ||
74 | expectedState, suffix, e.LastError) | ||
75 | } | ||
76 | |||
77 | return fmt.Sprintf("timeout while waiting for %s%s", | ||
78 | expectedState, suffix) | ||
79 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/id.go b/vendor/github.com/hashicorp/terraform/helper/resource/id.go new file mode 100644 index 0000000..629582b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/id.go | |||
@@ -0,0 +1,39 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "crypto/rand" | ||
5 | "fmt" | ||
6 | "math/big" | ||
7 | "sync" | ||
8 | ) | ||
9 | |||
10 | const UniqueIdPrefix = `terraform-` | ||
11 | |||
12 | // idCounter is a randomly seeded monotonic counter for generating ordered | ||
13 | // unique ids. It uses a big.Int so we can easily increment a long numeric | ||
14 | // string. The max possible hex value here with 12 random bytes is | ||
15 | // "01000000000000000000000000", so there's no chance of rollover during | ||
16 | // operation. | ||
17 | var idMutex sync.Mutex | ||
18 | var idCounter = big.NewInt(0).SetBytes(randomBytes(12)) | ||
19 | |||
20 | // Helper for a resource to generate a unique identifier w/ default prefix | ||
21 | func UniqueId() string { | ||
22 | return PrefixedUniqueId(UniqueIdPrefix) | ||
23 | } | ||
24 | |||
25 | // Helper for a resource to generate a unique identifier w/ given prefix | ||
26 | // | ||
27 | // After the prefix, the ID consists of an incrementing 26 digit value (to match | ||
28 | // previous timestamp output). | ||
29 | func PrefixedUniqueId(prefix string) string { | ||
30 | idMutex.Lock() | ||
31 | defer idMutex.Unlock() | ||
32 | return fmt.Sprintf("%s%026x", prefix, idCounter.Add(idCounter, big.NewInt(1))) | ||
33 | } | ||
34 | |||
35 | func randomBytes(n int) []byte { | ||
36 | b := make([]byte, n) | ||
37 | rand.Read(b) | ||
38 | return b | ||
39 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/map.go b/vendor/github.com/hashicorp/terraform/helper/resource/map.go new file mode 100644 index 0000000..a465136 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/map.go | |||
@@ -0,0 +1,140 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "sort" | ||
6 | |||
7 | "github.com/hashicorp/terraform/terraform" | ||
8 | ) | ||
9 | |||
10 | // Map is a map of resources that are supported, and provides helpers for | ||
11 | // more easily implementing a ResourceProvider. | ||
12 | type Map struct { | ||
13 | Mapping map[string]Resource | ||
14 | } | ||
15 | |||
16 | func (m *Map) Validate( | ||
17 | t string, c *terraform.ResourceConfig) ([]string, []error) { | ||
18 | r, ok := m.Mapping[t] | ||
19 | if !ok { | ||
20 | return nil, []error{fmt.Errorf("Unknown resource type: %s", t)} | ||
21 | } | ||
22 | |||
23 | // If there is no validator set, then it is valid | ||
24 | if r.ConfigValidator == nil { | ||
25 | return nil, nil | ||
26 | } | ||
27 | |||
28 | return r.ConfigValidator.Validate(c) | ||
29 | } | ||
30 | |||
31 | // Apply performs a create or update depending on the diff, and calls | ||
32 | // the proper function on the matching Resource. | ||
33 | func (m *Map) Apply( | ||
34 | info *terraform.InstanceInfo, | ||
35 | s *terraform.InstanceState, | ||
36 | d *terraform.InstanceDiff, | ||
37 | meta interface{}) (*terraform.InstanceState, error) { | ||
38 | r, ok := m.Mapping[info.Type] | ||
39 | if !ok { | ||
40 | return nil, fmt.Errorf("Unknown resource type: %s", info.Type) | ||
41 | } | ||
42 | |||
43 | if d.Destroy || d.RequiresNew() { | ||
44 | if s.ID != "" { | ||
45 | // Destroy the resource if it is created | ||
46 | err := r.Destroy(s, meta) | ||
47 | if err != nil { | ||
48 | return s, err | ||
49 | } | ||
50 | |||
51 | s.ID = "" | ||
52 | } | ||
53 | |||
54 | // If we're only destroying, and not creating, then return now. | ||
55 | // Otherwise, we continue so that we can create a new resource. | ||
56 | if !d.RequiresNew() { | ||
57 | return nil, nil | ||
58 | } | ||
59 | } | ||
60 | |||
61 | var result *terraform.InstanceState | ||
62 | var err error | ||
63 | if s.ID == "" { | ||
64 | result, err = r.Create(s, d, meta) | ||
65 | } else { | ||
66 | if r.Update == nil { | ||
67 | return s, fmt.Errorf( | ||
68 | "Resource type '%s' doesn't support update", | ||
69 | info.Type) | ||
70 | } | ||
71 | |||
72 | result, err = r.Update(s, d, meta) | ||
73 | } | ||
74 | if result != nil { | ||
75 | if result.Attributes == nil { | ||
76 | result.Attributes = make(map[string]string) | ||
77 | } | ||
78 | |||
79 | result.Attributes["id"] = result.ID | ||
80 | } | ||
81 | |||
82 | return result, err | ||
83 | } | ||
84 | |||
85 | // Diff performs a diff on the proper resource type. | ||
86 | func (m *Map) Diff( | ||
87 | info *terraform.InstanceInfo, | ||
88 | s *terraform.InstanceState, | ||
89 | c *terraform.ResourceConfig, | ||
90 | meta interface{}) (*terraform.InstanceDiff, error) { | ||
91 | r, ok := m.Mapping[info.Type] | ||
92 | if !ok { | ||
93 | return nil, fmt.Errorf("Unknown resource type: %s", info.Type) | ||
94 | } | ||
95 | |||
96 | return r.Diff(s, c, meta) | ||
97 | } | ||
98 | |||
99 | // Refresh performs a Refresh on the proper resource type. | ||
100 | // | ||
101 | // Refresh on the Resource won't be called if the state represents a | ||
102 | // non-created resource (ID is blank). | ||
103 | // | ||
104 | // An error is returned if the resource isn't registered. | ||
105 | func (m *Map) Refresh( | ||
106 | info *terraform.InstanceInfo, | ||
107 | s *terraform.InstanceState, | ||
108 | meta interface{}) (*terraform.InstanceState, error) { | ||
109 | // If the resource isn't created, don't refresh. | ||
110 | if s.ID == "" { | ||
111 | return s, nil | ||
112 | } | ||
113 | |||
114 | r, ok := m.Mapping[info.Type] | ||
115 | if !ok { | ||
116 | return nil, fmt.Errorf("Unknown resource type: %s", info.Type) | ||
117 | } | ||
118 | |||
119 | return r.Refresh(s, meta) | ||
120 | } | ||
121 | |||
122 | // Resources returns all the resources that are supported by this | ||
123 | // resource map and can be used to satisfy the Resources method of | ||
124 | // a ResourceProvider. | ||
125 | func (m *Map) Resources() []terraform.ResourceType { | ||
126 | ks := make([]string, 0, len(m.Mapping)) | ||
127 | for k, _ := range m.Mapping { | ||
128 | ks = append(ks, k) | ||
129 | } | ||
130 | sort.Strings(ks) | ||
131 | |||
132 | rs := make([]terraform.ResourceType, 0, len(m.Mapping)) | ||
133 | for _, k := range ks { | ||
134 | rs = append(rs, terraform.ResourceType{ | ||
135 | Name: k, | ||
136 | }) | ||
137 | } | ||
138 | |||
139 | return rs | ||
140 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/resource.go b/vendor/github.com/hashicorp/terraform/helper/resource/resource.go new file mode 100644 index 0000000..0d9c831 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/resource.go | |||
@@ -0,0 +1,49 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/terraform/helper/config" | ||
5 | "github.com/hashicorp/terraform/terraform" | ||
6 | ) | ||
7 | |||
8 | type Resource struct { | ||
9 | ConfigValidator *config.Validator | ||
10 | Create CreateFunc | ||
11 | Destroy DestroyFunc | ||
12 | Diff DiffFunc | ||
13 | Refresh RefreshFunc | ||
14 | Update UpdateFunc | ||
15 | } | ||
16 | |||
17 | // CreateFunc is a function that creates a resource that didn't previously | ||
18 | // exist. | ||
19 | type CreateFunc func( | ||
20 | *terraform.InstanceState, | ||
21 | *terraform.InstanceDiff, | ||
22 | interface{}) (*terraform.InstanceState, error) | ||
23 | |||
24 | // DestroyFunc is a function that destroys a resource that previously | ||
25 | // exists using the state. | ||
26 | type DestroyFunc func( | ||
27 | *terraform.InstanceState, | ||
28 | interface{}) error | ||
29 | |||
30 | // DiffFunc is a function that performs a diff of a resource. | ||
31 | type DiffFunc func( | ||
32 | *terraform.InstanceState, | ||
33 | *terraform.ResourceConfig, | ||
34 | interface{}) (*terraform.InstanceDiff, error) | ||
35 | |||
36 | // RefreshFunc is a function that performs a refresh of a specific type | ||
37 | // of resource. | ||
38 | type RefreshFunc func( | ||
39 | *terraform.InstanceState, | ||
40 | interface{}) (*terraform.InstanceState, error) | ||
41 | |||
42 | // UpdateFunc is a function that is called to update a resource that | ||
43 | // previously existed. The difference between this and CreateFunc is that | ||
44 | // the diff is guaranteed to only contain attributes that don't require | ||
45 | // a new resource. | ||
46 | type UpdateFunc func( | ||
47 | *terraform.InstanceState, | ||
48 | *terraform.InstanceDiff, | ||
49 | interface{}) (*terraform.InstanceState, error) | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/state.go b/vendor/github.com/hashicorp/terraform/helper/resource/state.go new file mode 100644 index 0000000..37c586a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/state.go | |||
@@ -0,0 +1,259 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "log" | ||
5 | "time" | ||
6 | ) | ||
7 | |||
8 | var refreshGracePeriod = 30 * time.Second | ||
9 | |||
10 | // StateRefreshFunc is a function type used for StateChangeConf that is | ||
11 | // responsible for refreshing the item being watched for a state change. | ||
12 | // | ||
13 | // It returns three results. `result` is any object that will be returned | ||
14 | // as the final object after waiting for state change. This allows you to | ||
15 | // return the final updated object, for example an EC2 instance after refreshing | ||
16 | // it. | ||
17 | // | ||
18 | // `state` is the latest state of that object. And `err` is any error that | ||
19 | // may have happened while refreshing the state. | ||
20 | type StateRefreshFunc func() (result interface{}, state string, err error) | ||
21 | |||
22 | // StateChangeConf is the configuration struct used for `WaitForState`. | ||
23 | type StateChangeConf struct { | ||
24 | Delay time.Duration // Wait this time before starting checks | ||
25 | Pending []string // States that are "allowed" and will continue trying | ||
26 | Refresh StateRefreshFunc // Refreshes the current state | ||
27 | Target []string // Target state | ||
28 | Timeout time.Duration // The amount of time to wait before timeout | ||
29 | MinTimeout time.Duration // Smallest time to wait before refreshes | ||
30 | PollInterval time.Duration // Override MinTimeout/backoff and only poll this often | ||
31 | NotFoundChecks int // Number of times to allow not found | ||
32 | |||
33 | // This is to work around inconsistent APIs | ||
34 | ContinuousTargetOccurence int // Number of times the Target state has to occur continuously | ||
35 | } | ||
36 | |||
37 | // WaitForState watches an object and waits for it to achieve the state | ||
38 | // specified in the configuration using the specified Refresh() func, | ||
39 | // waiting the number of seconds specified in the timeout configuration. | ||
40 | // | ||
41 | // If the Refresh function returns a error, exit immediately with that error. | ||
42 | // | ||
43 | // If the Refresh function returns a state other than the Target state or one | ||
44 | // listed in Pending, return immediately with an error. | ||
45 | // | ||
46 | // If the Timeout is exceeded before reaching the Target state, return an | ||
47 | // error. | ||
48 | // | ||
49 | // Otherwise, result the result of the first call to the Refresh function to | ||
50 | // reach the target state. | ||
51 | func (conf *StateChangeConf) WaitForState() (interface{}, error) { | ||
52 | log.Printf("[DEBUG] Waiting for state to become: %s", conf.Target) | ||
53 | |||
54 | notfoundTick := 0 | ||
55 | targetOccurence := 0 | ||
56 | |||
57 | // Set a default for times to check for not found | ||
58 | if conf.NotFoundChecks == 0 { | ||
59 | conf.NotFoundChecks = 20 | ||
60 | } | ||
61 | |||
62 | if conf.ContinuousTargetOccurence == 0 { | ||
63 | conf.ContinuousTargetOccurence = 1 | ||
64 | } | ||
65 | |||
66 | type Result struct { | ||
67 | Result interface{} | ||
68 | State string | ||
69 | Error error | ||
70 | Done bool | ||
71 | } | ||
72 | |||
73 | // Read every result from the refresh loop, waiting for a positive result.Done. | ||
74 | resCh := make(chan Result, 1) | ||
75 | // cancellation channel for the refresh loop | ||
76 | cancelCh := make(chan struct{}) | ||
77 | |||
78 | result := Result{} | ||
79 | |||
80 | go func() { | ||
81 | defer close(resCh) | ||
82 | |||
83 | time.Sleep(conf.Delay) | ||
84 | |||
85 | // start with 0 delay for the first loop | ||
86 | var wait time.Duration | ||
87 | |||
88 | for { | ||
89 | // store the last result | ||
90 | resCh <- result | ||
91 | |||
92 | // wait and watch for cancellation | ||
93 | select { | ||
94 | case <-cancelCh: | ||
95 | return | ||
96 | case <-time.After(wait): | ||
97 | // first round had no wait | ||
98 | if wait == 0 { | ||
99 | wait = 100 * time.Millisecond | ||
100 | } | ||
101 | } | ||
102 | |||
103 | res, currentState, err := conf.Refresh() | ||
104 | result = Result{ | ||
105 | Result: res, | ||
106 | State: currentState, | ||
107 | Error: err, | ||
108 | } | ||
109 | |||
110 | if err != nil { | ||
111 | resCh <- result | ||
112 | return | ||
113 | } | ||
114 | |||
115 | // If we're waiting for the absence of a thing, then return | ||
116 | if res == nil && len(conf.Target) == 0 { | ||
117 | targetOccurence++ | ||
118 | if conf.ContinuousTargetOccurence == targetOccurence { | ||
119 | result.Done = true | ||
120 | resCh <- result | ||
121 | return | ||
122 | } | ||
123 | continue | ||
124 | } | ||
125 | |||
126 | if res == nil { | ||
127 | // If we didn't find the resource, check if we have been | ||
128 | // not finding it for awhile, and if so, report an error. | ||
129 | notfoundTick++ | ||
130 | if notfoundTick > conf.NotFoundChecks { | ||
131 | result.Error = &NotFoundError{ | ||
132 | LastError: err, | ||
133 | Retries: notfoundTick, | ||
134 | } | ||
135 | resCh <- result | ||
136 | return | ||
137 | } | ||
138 | } else { | ||
139 | // Reset the counter for when a resource isn't found | ||
140 | notfoundTick = 0 | ||
141 | found := false | ||
142 | |||
143 | for _, allowed := range conf.Target { | ||
144 | if currentState == allowed { | ||
145 | found = true | ||
146 | targetOccurence++ | ||
147 | if conf.ContinuousTargetOccurence == targetOccurence { | ||
148 | result.Done = true | ||
149 | resCh <- result | ||
150 | return | ||
151 | } | ||
152 | continue | ||
153 | } | ||
154 | } | ||
155 | |||
156 | for _, allowed := range conf.Pending { | ||
157 | if currentState == allowed { | ||
158 | found = true | ||
159 | targetOccurence = 0 | ||
160 | break | ||
161 | } | ||
162 | } | ||
163 | |||
164 | if !found && len(conf.Pending) > 0 { | ||
165 | result.Error = &UnexpectedStateError{ | ||
166 | LastError: err, | ||
167 | State: result.State, | ||
168 | ExpectedState: conf.Target, | ||
169 | } | ||
170 | resCh <- result | ||
171 | return | ||
172 | } | ||
173 | } | ||
174 | |||
175 | // Wait between refreshes using exponential backoff, except when | ||
176 | // waiting for the target state to reoccur. | ||
177 | if targetOccurence == 0 { | ||
178 | wait *= 2 | ||
179 | } | ||
180 | |||
181 | // If a poll interval has been specified, choose that interval. | ||
182 | // Otherwise bound the default value. | ||
183 | if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second { | ||
184 | wait = conf.PollInterval | ||
185 | } else { | ||
186 | if wait < conf.MinTimeout { | ||
187 | wait = conf.MinTimeout | ||
188 | } else if wait > 10*time.Second { | ||
189 | wait = 10 * time.Second | ||
190 | } | ||
191 | } | ||
192 | |||
193 | log.Printf("[TRACE] Waiting %s before next try", wait) | ||
194 | } | ||
195 | }() | ||
196 | |||
197 | // store the last value result from the refresh loop | ||
198 | lastResult := Result{} | ||
199 | |||
200 | timeout := time.After(conf.Timeout) | ||
201 | for { | ||
202 | select { | ||
203 | case r, ok := <-resCh: | ||
204 | // channel closed, so return the last result | ||
205 | if !ok { | ||
206 | return lastResult.Result, lastResult.Error | ||
207 | } | ||
208 | |||
209 | // we reached the intended state | ||
210 | if r.Done { | ||
211 | return r.Result, r.Error | ||
212 | } | ||
213 | |||
214 | // still waiting, store the last result | ||
215 | lastResult = r | ||
216 | |||
217 | case <-timeout: | ||
218 | log.Printf("[WARN] WaitForState timeout after %s", conf.Timeout) | ||
219 | log.Printf("[WARN] WaitForState starting %s refresh grace period", refreshGracePeriod) | ||
220 | |||
221 | // cancel the goroutine and start our grace period timer | ||
222 | close(cancelCh) | ||
223 | timeout := time.After(refreshGracePeriod) | ||
224 | |||
225 | // we need a for loop and a label to break on, because we may have | ||
226 | // an extra response value to read, but still want to wait for the | ||
227 | // channel to close. | ||
228 | forSelect: | ||
229 | for { | ||
230 | select { | ||
231 | case r, ok := <-resCh: | ||
232 | if r.Done { | ||
233 | // the last refresh loop reached the desired state | ||
234 | return r.Result, r.Error | ||
235 | } | ||
236 | |||
237 | if !ok { | ||
238 | // the goroutine returned | ||
239 | break forSelect | ||
240 | } | ||
241 | |||
242 | // target state not reached, save the result for the | ||
243 | // TimeoutError and wait for the channel to close | ||
244 | lastResult = r | ||
245 | case <-timeout: | ||
246 | log.Println("[ERROR] WaitForState exceeded refresh grace period") | ||
247 | break forSelect | ||
248 | } | ||
249 | } | ||
250 | |||
251 | return nil, &TimeoutError{ | ||
252 | LastError: lastResult.Error, | ||
253 | LastState: lastResult.State, | ||
254 | Timeout: conf.Timeout, | ||
255 | ExpectedState: conf.Target, | ||
256 | } | ||
257 | } | ||
258 | } | ||
259 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go new file mode 100644 index 0000000..04367c5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go | |||
@@ -0,0 +1,790 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "io" | ||
6 | "io/ioutil" | ||
7 | "log" | ||
8 | "os" | ||
9 | "path/filepath" | ||
10 | "reflect" | ||
11 | "regexp" | ||
12 | "strings" | ||
13 | "testing" | ||
14 | |||
15 | "github.com/davecgh/go-spew/spew" | ||
16 | "github.com/hashicorp/go-getter" | ||
17 | "github.com/hashicorp/go-multierror" | ||
18 | "github.com/hashicorp/terraform/config/module" | ||
19 | "github.com/hashicorp/terraform/helper/logging" | ||
20 | "github.com/hashicorp/terraform/terraform" | ||
21 | ) | ||
22 | |||
23 | const TestEnvVar = "TF_ACC" | ||
24 | |||
25 | // TestProvider can be implemented by any ResourceProvider to provide custom | ||
26 | // reset functionality at the start of an acceptance test. | ||
27 | // The helper/schema Provider implements this interface. | ||
28 | type TestProvider interface { | ||
29 | TestReset() error | ||
30 | } | ||
31 | |||
32 | // TestCheckFunc is the callback type used with acceptance tests to check | ||
33 | // the state of a resource. The state passed in is the latest state known, | ||
34 | // or in the case of being after a destroy, it is the last known state when | ||
35 | // it was created. | ||
36 | type TestCheckFunc func(*terraform.State) error | ||
37 | |||
38 | // ImportStateCheckFunc is the check function for ImportState tests | ||
39 | type ImportStateCheckFunc func([]*terraform.InstanceState) error | ||
40 | |||
41 | // TestCase is a single acceptance test case used to test the apply/destroy | ||
42 | // lifecycle of a resource in a specific configuration. | ||
43 | // | ||
44 | // When the destroy plan is executed, the config from the last TestStep | ||
45 | // is used to plan it. | ||
46 | type TestCase struct { | ||
47 | // IsUnitTest allows a test to run regardless of the TF_ACC | ||
48 | // environment variable. This should be used with care - only for | ||
49 | // fast tests on local resources (e.g. remote state with a local | ||
50 | // backend) but can be used to increase confidence in correct | ||
51 | // operation of Terraform without waiting for a full acctest run. | ||
52 | IsUnitTest bool | ||
53 | |||
54 | // PreCheck, if non-nil, will be called before any test steps are | ||
55 | // executed. It will only be executed in the case that the steps | ||
56 | // would run, so it can be used for some validation before running | ||
57 | // acceptance tests, such as verifying that keys are setup. | ||
58 | PreCheck func() | ||
59 | |||
60 | // Providers is the ResourceProvider that will be under test. | ||
61 | // | ||
62 | // Alternately, ProviderFactories can be specified for the providers | ||
63 | // that are valid. This takes priority over Providers. | ||
64 | // | ||
65 | // The end effect of each is the same: specifying the providers that | ||
66 | // are used within the tests. | ||
67 | Providers map[string]terraform.ResourceProvider | ||
68 | ProviderFactories map[string]terraform.ResourceProviderFactory | ||
69 | |||
70 | // PreventPostDestroyRefresh can be set to true for cases where data sources | ||
71 | // are tested alongside real resources | ||
72 | PreventPostDestroyRefresh bool | ||
73 | |||
74 | // CheckDestroy is called after the resource is finally destroyed | ||
75 | // to allow the tester to test that the resource is truly gone. | ||
76 | CheckDestroy TestCheckFunc | ||
77 | |||
78 | // Steps are the apply sequences done within the context of the | ||
79 | // same state. Each step can have its own check to verify correctness. | ||
80 | Steps []TestStep | ||
81 | |||
82 | // The settings below control the "ID-only refresh test." This is | ||
83 | // an enabled-by-default test that tests that a refresh can be | ||
84 | // refreshed with only an ID to result in the same attributes. | ||
85 | // This validates completeness of Refresh. | ||
86 | // | ||
87 | // IDRefreshName is the name of the resource to check. This will | ||
88 | // default to the first non-nil primary resource in the state. | ||
89 | // | ||
90 | // IDRefreshIgnore is a list of configuration keys that will be ignored. | ||
91 | IDRefreshName string | ||
92 | IDRefreshIgnore []string | ||
93 | } | ||
94 | |||
95 | // TestStep is a single apply sequence of a test, done within the | ||
96 | // context of a state. | ||
97 | // | ||
98 | // Multiple TestSteps can be sequenced in a Test to allow testing | ||
99 | // potentially complex update logic. In general, simply create/destroy | ||
100 | // tests will only need one step. | ||
101 | type TestStep struct { | ||
102 | // ResourceName should be set to the name of the resource | ||
103 | // that is being tested. Example: "aws_instance.foo". Various test | ||
104 | // modes use this to auto-detect state information. | ||
105 | // | ||
106 | // This is only required if the test mode settings below say it is | ||
107 | // for the mode you're using. | ||
108 | ResourceName string | ||
109 | |||
110 | // PreConfig is called before the Config is applied to perform any per-step | ||
111 | // setup that needs to happen. This is called regardless of "test mode" | ||
112 | // below. | ||
113 | PreConfig func() | ||
114 | |||
115 | //--------------------------------------------------------------- | ||
116 | // Test modes. One of the following groups of settings must be | ||
117 | // set to determine what the test step will do. Ideally we would've | ||
118 | // used Go interfaces here but there are now hundreds of tests we don't | ||
119 | // want to re-type so instead we just determine which step logic | ||
120 | // to run based on what settings below are set. | ||
121 | //--------------------------------------------------------------- | ||
122 | |||
123 | //--------------------------------------------------------------- | ||
124 | // Plan, Apply testing | ||
125 | //--------------------------------------------------------------- | ||
126 | |||
127 | // Config a string of the configuration to give to Terraform. If this | ||
128 | // is set, then the TestCase will execute this step with the same logic | ||
129 | // as a `terraform apply`. | ||
130 | Config string | ||
131 | |||
132 | // Check is called after the Config is applied. Use this step to | ||
133 | // make your own API calls to check the status of things, and to | ||
134 | // inspect the format of the ResourceState itself. | ||
135 | // | ||
136 | // If an error is returned, the test will fail. In this case, a | ||
137 | // destroy plan will still be attempted. | ||
138 | // | ||
139 | // If this is nil, no check is done on this step. | ||
140 | Check TestCheckFunc | ||
141 | |||
142 | // Destroy will create a destroy plan if set to true. | ||
143 | Destroy bool | ||
144 | |||
145 | // ExpectNonEmptyPlan can be set to true for specific types of tests that are | ||
146 | // looking to verify that a diff occurs | ||
147 | ExpectNonEmptyPlan bool | ||
148 | |||
149 | // ExpectError allows the construction of test cases that we expect to fail | ||
150 | // with an error. The specified regexp must match against the error for the | ||
151 | // test to pass. | ||
152 | ExpectError *regexp.Regexp | ||
153 | |||
154 | // PlanOnly can be set to only run `plan` with this configuration, and not | ||
155 | // actually apply it. This is useful for ensuring config changes result in | ||
156 | // no-op plans | ||
157 | PlanOnly bool | ||
158 | |||
159 | // PreventPostDestroyRefresh can be set to true for cases where data sources | ||
160 | // are tested alongside real resources | ||
161 | PreventPostDestroyRefresh bool | ||
162 | |||
163 | //--------------------------------------------------------------- | ||
164 | // ImportState testing | ||
165 | //--------------------------------------------------------------- | ||
166 | |||
167 | // ImportState, if true, will test the functionality of ImportState | ||
168 | // by importing the resource with ResourceName (must be set) and the | ||
169 | // ID of that resource. | ||
170 | ImportState bool | ||
171 | |||
172 | // ImportStateId is the ID to perform an ImportState operation with. | ||
173 | // This is optional. If it isn't set, then the resource ID is automatically | ||
174 | // determined by inspecting the state for ResourceName's ID. | ||
175 | ImportStateId string | ||
176 | |||
177 | // ImportStateIdPrefix is the prefix added in front of ImportStateId. | ||
178 | // This can be useful in complex import cases, where more than one | ||
179 | // attribute needs to be passed on as the Import ID. Mainly in cases | ||
180 | // where the ID is not known, and a known prefix needs to be added to | ||
181 | // the unset ImportStateId field. | ||
182 | ImportStateIdPrefix string | ||
183 | |||
184 | // ImportStateCheck checks the results of ImportState. It should be | ||
185 | // used to verify that the resulting value of ImportState has the | ||
186 | // proper resources, IDs, and attributes. | ||
187 | ImportStateCheck ImportStateCheckFunc | ||
188 | |||
189 | // ImportStateVerify, if true, will also check that the state values | ||
190 | // that are finally put into the state after import match for all the | ||
191 | // IDs returned by the Import. | ||
192 | // | ||
193 | // ImportStateVerifyIgnore are fields that should not be verified to | ||
194 | // be equal. These can be set to ephemeral fields or fields that can't | ||
195 | // be refreshed and don't matter. | ||
196 | ImportStateVerify bool | ||
197 | ImportStateVerifyIgnore []string | ||
198 | } | ||
199 | |||
200 | // Test performs an acceptance test on a resource. | ||
201 | // | ||
202 | // Tests are not run unless an environmental variable "TF_ACC" is | ||
203 | // set to some non-empty value. This is to avoid test cases surprising | ||
204 | // a user by creating real resources. | ||
205 | // | ||
206 | // Tests will fail unless the verbose flag (`go test -v`, or explicitly | ||
207 | // the "-test.v" flag) is set. Because some acceptance tests take quite | ||
208 | // long, we require the verbose flag so users are able to see progress | ||
209 | // output. | ||
210 | func Test(t TestT, c TestCase) { | ||
211 | // We only run acceptance tests if an env var is set because they're | ||
212 | // slow and generally require some outside configuration. You can opt out | ||
213 | // of this with OverrideEnvVar on individual TestCases. | ||
214 | if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest { | ||
215 | t.Skip(fmt.Sprintf( | ||
216 | "Acceptance tests skipped unless env '%s' set", | ||
217 | TestEnvVar)) | ||
218 | return | ||
219 | } | ||
220 | |||
221 | logWriter, err := logging.LogOutput() | ||
222 | if err != nil { | ||
223 | t.Error(fmt.Errorf("error setting up logging: %s", err)) | ||
224 | } | ||
225 | log.SetOutput(logWriter) | ||
226 | |||
227 | // We require verbose mode so that the user knows what is going on. | ||
228 | if !testTesting && !testing.Verbose() && !c.IsUnitTest { | ||
229 | t.Fatal("Acceptance tests must be run with the -v flag on tests") | ||
230 | return | ||
231 | } | ||
232 | |||
233 | // Run the PreCheck if we have it | ||
234 | if c.PreCheck != nil { | ||
235 | c.PreCheck() | ||
236 | } | ||
237 | |||
238 | ctxProviders, err := testProviderFactories(c) | ||
239 | if err != nil { | ||
240 | t.Fatal(err) | ||
241 | } | ||
242 | opts := terraform.ContextOpts{Providers: ctxProviders} | ||
243 | |||
244 | // A single state variable to track the lifecycle, starting with no state | ||
245 | var state *terraform.State | ||
246 | |||
247 | // Go through each step and run it | ||
248 | var idRefreshCheck *terraform.ResourceState | ||
249 | idRefresh := c.IDRefreshName != "" | ||
250 | errored := false | ||
251 | for i, step := range c.Steps { | ||
252 | var err error | ||
253 | log.Printf("[WARN] Test: Executing step %d", i) | ||
254 | |||
255 | // Determine the test mode to execute | ||
256 | if step.Config != "" { | ||
257 | state, err = testStepConfig(opts, state, step) | ||
258 | } else if step.ImportState { | ||
259 | state, err = testStepImportState(opts, state, step) | ||
260 | } else { | ||
261 | err = fmt.Errorf( | ||
262 | "unknown test mode for step. Please see TestStep docs\n\n%#v", | ||
263 | step) | ||
264 | } | ||
265 | |||
266 | // If there was an error, exit | ||
267 | if err != nil { | ||
268 | // Perhaps we expected an error? Check if it matches | ||
269 | if step.ExpectError != nil { | ||
270 | if !step.ExpectError.MatchString(err.Error()) { | ||
271 | errored = true | ||
272 | t.Error(fmt.Sprintf( | ||
273 | "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", | ||
274 | i, err, step.ExpectError)) | ||
275 | break | ||
276 | } | ||
277 | } else { | ||
278 | errored = true | ||
279 | t.Error(fmt.Sprintf( | ||
280 | "Step %d error: %s", i, err)) | ||
281 | break | ||
282 | } | ||
283 | } | ||
284 | |||
285 | // If we've never checked an id-only refresh and our state isn't | ||
286 | // empty, find the first resource and test it. | ||
287 | if idRefresh && idRefreshCheck == nil && !state.Empty() { | ||
288 | // Find the first non-nil resource in the state | ||
289 | for _, m := range state.Modules { | ||
290 | if len(m.Resources) > 0 { | ||
291 | if v, ok := m.Resources[c.IDRefreshName]; ok { | ||
292 | idRefreshCheck = v | ||
293 | } | ||
294 | |||
295 | break | ||
296 | } | ||
297 | } | ||
298 | |||
299 | // If we have an instance to check for refreshes, do it | ||
300 | // immediately. We do it in the middle of another test | ||
301 | // because it shouldn't affect the overall state (refresh | ||
302 | // is read-only semantically) and we want to fail early if | ||
303 | // this fails. If refresh isn't read-only, then this will have | ||
304 | // caught a different bug. | ||
305 | if idRefreshCheck != nil { | ||
306 | log.Printf( | ||
307 | "[WARN] Test: Running ID-only refresh check on %s", | ||
308 | idRefreshCheck.Primary.ID) | ||
309 | if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { | ||
310 | log.Printf("[ERROR] Test: ID-only test failed: %s", err) | ||
311 | t.Error(fmt.Sprintf( | ||
312 | "[ERROR] Test: ID-only test failed: %s", err)) | ||
313 | break | ||
314 | } | ||
315 | } | ||
316 | } | ||
317 | } | ||
318 | |||
319 | // If we never checked an id-only refresh, it is a failure. | ||
320 | if idRefresh { | ||
321 | if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { | ||
322 | t.Error("ID-only refresh check never ran.") | ||
323 | } | ||
324 | } | ||
325 | |||
326 | // If we have a state, then run the destroy | ||
327 | if state != nil { | ||
328 | lastStep := c.Steps[len(c.Steps)-1] | ||
329 | destroyStep := TestStep{ | ||
330 | Config: lastStep.Config, | ||
331 | Check: c.CheckDestroy, | ||
332 | Destroy: true, | ||
333 | PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, | ||
334 | } | ||
335 | |||
336 | log.Printf("[WARN] Test: Executing destroy step") | ||
337 | state, err := testStep(opts, state, destroyStep) | ||
338 | if err != nil { | ||
339 | t.Error(fmt.Sprintf( | ||
340 | "Error destroying resource! WARNING: Dangling resources\n"+ | ||
341 | "may exist. The full state and error is shown below.\n\n"+ | ||
342 | "Error: %s\n\nState: %s", | ||
343 | err, | ||
344 | state)) | ||
345 | } | ||
346 | } else { | ||
347 | log.Printf("[WARN] Skipping destroy test since there is no state.") | ||
348 | } | ||
349 | } | ||
350 | |||
351 | // testProviderFactories is a helper to build the ResourceProviderFactory map | ||
352 | // with pre instantiated ResourceProviders, so that we can reset them for the | ||
353 | // test, while only calling the factory function once. | ||
354 | // Any errors are stored so that they can be returned by the factory in | ||
355 | // terraform to match non-test behavior. | ||
356 | func testProviderFactories(c TestCase) (map[string]terraform.ResourceProviderFactory, error) { | ||
357 | ctxProviders := c.ProviderFactories // make(map[string]terraform.ResourceProviderFactory) | ||
358 | if ctxProviders == nil { | ||
359 | ctxProviders = make(map[string]terraform.ResourceProviderFactory) | ||
360 | } | ||
361 | // add any fixed providers | ||
362 | for k, p := range c.Providers { | ||
363 | ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) | ||
364 | } | ||
365 | |||
366 | // reset the providers if needed | ||
367 | for k, pf := range ctxProviders { | ||
368 | // we can ignore any errors here, if we don't have a provider to reset | ||
369 | // the error will be handled later | ||
370 | p, err := pf() | ||
371 | if err != nil { | ||
372 | return nil, err | ||
373 | } | ||
374 | if p, ok := p.(TestProvider); ok { | ||
375 | err := p.TestReset() | ||
376 | if err != nil { | ||
377 | return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err) | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | |||
382 | return ctxProviders, nil | ||
383 | } | ||
384 | |||
385 | // UnitTest is a helper to force the acceptance testing harness to run in the | ||
386 | // normal unit test suite. This should only be used for resource that don't | ||
387 | // have any external dependencies. | ||
388 | func UnitTest(t TestT, c TestCase) { | ||
389 | c.IsUnitTest = true | ||
390 | Test(t, c) | ||
391 | } | ||
392 | |||
393 | func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { | ||
394 | // TODO: We guard by this right now so master doesn't explode. We | ||
395 | // need to remove this eventually to make this part of the normal tests. | ||
396 | if os.Getenv("TF_ACC_IDONLY") == "" { | ||
397 | return nil | ||
398 | } | ||
399 | |||
400 | name := fmt.Sprintf("%s.foo", r.Type) | ||
401 | |||
402 | // Build the state. The state is just the resource with an ID. There | ||
403 | // are no attributes. We only set what is needed to perform a refresh. | ||
404 | state := terraform.NewState() | ||
405 | state.RootModule().Resources[name] = &terraform.ResourceState{ | ||
406 | Type: r.Type, | ||
407 | Primary: &terraform.InstanceState{ | ||
408 | ID: r.Primary.ID, | ||
409 | }, | ||
410 | } | ||
411 | |||
412 | // Create the config module. We use the full config because Refresh | ||
413 | // doesn't have access to it and we may need things like provider | ||
414 | // configurations. The initial implementation of id-only checks used | ||
415 | // an empty config module, but that caused the aforementioned problems. | ||
416 | mod, err := testModule(opts, step) | ||
417 | if err != nil { | ||
418 | return err | ||
419 | } | ||
420 | |||
421 | // Initialize the context | ||
422 | opts.Module = mod | ||
423 | opts.State = state | ||
424 | ctx, err := terraform.NewContext(&opts) | ||
425 | if err != nil { | ||
426 | return err | ||
427 | } | ||
428 | if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { | ||
429 | if len(es) > 0 { | ||
430 | estrs := make([]string, len(es)) | ||
431 | for i, e := range es { | ||
432 | estrs[i] = e.Error() | ||
433 | } | ||
434 | return fmt.Errorf( | ||
435 | "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", | ||
436 | ws, estrs) | ||
437 | } | ||
438 | |||
439 | log.Printf("[WARN] Config warnings: %#v", ws) | ||
440 | } | ||
441 | |||
442 | // Refresh! | ||
443 | state, err = ctx.Refresh() | ||
444 | if err != nil { | ||
445 | return fmt.Errorf("Error refreshing: %s", err) | ||
446 | } | ||
447 | |||
448 | // Verify attribute equivalence. | ||
449 | actualR := state.RootModule().Resources[name] | ||
450 | if actualR == nil { | ||
451 | return fmt.Errorf("Resource gone!") | ||
452 | } | ||
453 | if actualR.Primary == nil { | ||
454 | return fmt.Errorf("Resource has no primary instance") | ||
455 | } | ||
456 | actual := actualR.Primary.Attributes | ||
457 | expected := r.Primary.Attributes | ||
458 | // Remove fields we're ignoring | ||
459 | for _, v := range c.IDRefreshIgnore { | ||
460 | for k, _ := range actual { | ||
461 | if strings.HasPrefix(k, v) { | ||
462 | delete(actual, k) | ||
463 | } | ||
464 | } | ||
465 | for k, _ := range expected { | ||
466 | if strings.HasPrefix(k, v) { | ||
467 | delete(expected, k) | ||
468 | } | ||
469 | } | ||
470 | } | ||
471 | |||
472 | if !reflect.DeepEqual(actual, expected) { | ||
473 | // Determine only the different attributes | ||
474 | for k, v := range expected { | ||
475 | if av, ok := actual[k]; ok && v == av { | ||
476 | delete(expected, k) | ||
477 | delete(actual, k) | ||
478 | } | ||
479 | } | ||
480 | |||
481 | spewConf := spew.NewDefaultConfig() | ||
482 | spewConf.SortKeys = true | ||
483 | return fmt.Errorf( | ||
484 | "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ | ||
485 | "\n\n%s\n\n%s", | ||
486 | spewConf.Sdump(actual), spewConf.Sdump(expected)) | ||
487 | } | ||
488 | |||
489 | return nil | ||
490 | } | ||
491 | |||
492 | func testModule( | ||
493 | opts terraform.ContextOpts, | ||
494 | step TestStep) (*module.Tree, error) { | ||
495 | if step.PreConfig != nil { | ||
496 | step.PreConfig() | ||
497 | } | ||
498 | |||
499 | cfgPath, err := ioutil.TempDir("", "tf-test") | ||
500 | if err != nil { | ||
501 | return nil, fmt.Errorf( | ||
502 | "Error creating temporary directory for config: %s", err) | ||
503 | } | ||
504 | defer os.RemoveAll(cfgPath) | ||
505 | |||
506 | // Write the configuration | ||
507 | cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) | ||
508 | if err != nil { | ||
509 | return nil, fmt.Errorf( | ||
510 | "Error creating temporary file for config: %s", err) | ||
511 | } | ||
512 | |||
513 | _, err = io.Copy(cfgF, strings.NewReader(step.Config)) | ||
514 | cfgF.Close() | ||
515 | if err != nil { | ||
516 | return nil, fmt.Errorf( | ||
517 | "Error creating temporary file for config: %s", err) | ||
518 | } | ||
519 | |||
520 | // Parse the configuration | ||
521 | mod, err := module.NewTreeModule("", cfgPath) | ||
522 | if err != nil { | ||
523 | return nil, fmt.Errorf( | ||
524 | "Error loading configuration: %s", err) | ||
525 | } | ||
526 | |||
527 | // Load the modules | ||
528 | modStorage := &getter.FolderStorage{ | ||
529 | StorageDir: filepath.Join(cfgPath, ".tfmodules"), | ||
530 | } | ||
531 | err = mod.Load(modStorage, module.GetModeGet) | ||
532 | if err != nil { | ||
533 | return nil, fmt.Errorf("Error downloading modules: %s", err) | ||
534 | } | ||
535 | |||
536 | return mod, nil | ||
537 | } | ||
538 | |||
539 | func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { | ||
540 | if c.ResourceName == "" { | ||
541 | return nil, fmt.Errorf("ResourceName must be set in TestStep") | ||
542 | } | ||
543 | |||
544 | for _, m := range state.Modules { | ||
545 | if len(m.Resources) > 0 { | ||
546 | if v, ok := m.Resources[c.ResourceName]; ok { | ||
547 | return v, nil | ||
548 | } | ||
549 | } | ||
550 | } | ||
551 | |||
552 | return nil, fmt.Errorf( | ||
553 | "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) | ||
554 | } | ||
555 | |||
556 | // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into | ||
557 | // a single TestCheckFunc. | ||
558 | // | ||
559 | // As a user testing their provider, this lets you decompose your checks | ||
560 | // into smaller pieces more easily. | ||
561 | func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { | ||
562 | return func(s *terraform.State) error { | ||
563 | for i, f := range fs { | ||
564 | if err := f(s); err != nil { | ||
565 | return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) | ||
566 | } | ||
567 | } | ||
568 | |||
569 | return nil | ||
570 | } | ||
571 | } | ||
572 | |||
573 | // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into | ||
574 | // a single TestCheckFunc. | ||
575 | // | ||
576 | // As a user testing their provider, this lets you decompose your checks | ||
577 | // into smaller pieces more easily. | ||
578 | // | ||
579 | // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the | ||
580 | // TestCheckFuncs and aggregates failures. | ||
581 | func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { | ||
582 | return func(s *terraform.State) error { | ||
583 | var result *multierror.Error | ||
584 | |||
585 | for i, f := range fs { | ||
586 | if err := f(s); err != nil { | ||
587 | result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) | ||
588 | } | ||
589 | } | ||
590 | |||
591 | return result.ErrorOrNil() | ||
592 | } | ||
593 | } | ||
594 | |||
595 | // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value | ||
596 | // exists in state for the given name/key combination. It is useful when | ||
597 | // testing that computed values were set, when it is not possible to | ||
598 | // know ahead of time what the values will be. | ||
599 | func TestCheckResourceAttrSet(name, key string) TestCheckFunc { | ||
600 | return func(s *terraform.State) error { | ||
601 | is, err := primaryInstanceState(s, name) | ||
602 | if err != nil { | ||
603 | return err | ||
604 | } | ||
605 | |||
606 | if val, ok := is.Attributes[key]; ok && val != "" { | ||
607 | return nil | ||
608 | } | ||
609 | |||
610 | return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) | ||
611 | } | ||
612 | } | ||
613 | |||
614 | // TestCheckResourceAttr is a TestCheckFunc which validates | ||
615 | // the value in state for the given name/key combination. | ||
616 | func TestCheckResourceAttr(name, key, value string) TestCheckFunc { | ||
617 | return func(s *terraform.State) error { | ||
618 | is, err := primaryInstanceState(s, name) | ||
619 | if err != nil { | ||
620 | return err | ||
621 | } | ||
622 | |||
623 | if v, ok := is.Attributes[key]; !ok || v != value { | ||
624 | if !ok { | ||
625 | return fmt.Errorf("%s: Attribute '%s' not found", name, key) | ||
626 | } | ||
627 | |||
628 | return fmt.Errorf( | ||
629 | "%s: Attribute '%s' expected %#v, got %#v", | ||
630 | name, | ||
631 | key, | ||
632 | value, | ||
633 | v) | ||
634 | } | ||
635 | |||
636 | return nil | ||
637 | } | ||
638 | } | ||
639 | |||
640 | // TestCheckNoResourceAttr is a TestCheckFunc which ensures that | ||
641 | // NO value exists in state for the given name/key combination. | ||
642 | func TestCheckNoResourceAttr(name, key string) TestCheckFunc { | ||
643 | return func(s *terraform.State) error { | ||
644 | is, err := primaryInstanceState(s, name) | ||
645 | if err != nil { | ||
646 | return err | ||
647 | } | ||
648 | |||
649 | if _, ok := is.Attributes[key]; ok { | ||
650 | return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) | ||
651 | } | ||
652 | |||
653 | return nil | ||
654 | } | ||
655 | } | ||
656 | |||
657 | // TestMatchResourceAttr is a TestCheckFunc which checks that the value | ||
658 | // in state for the given name/key combination matches the given regex. | ||
659 | func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { | ||
660 | return func(s *terraform.State) error { | ||
661 | is, err := primaryInstanceState(s, name) | ||
662 | if err != nil { | ||
663 | return err | ||
664 | } | ||
665 | |||
666 | if !r.MatchString(is.Attributes[key]) { | ||
667 | return fmt.Errorf( | ||
668 | "%s: Attribute '%s' didn't match %q, got %#v", | ||
669 | name, | ||
670 | key, | ||
671 | r.String(), | ||
672 | is.Attributes[key]) | ||
673 | } | ||
674 | |||
675 | return nil | ||
676 | } | ||
677 | } | ||
678 | |||
679 | // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the | ||
680 | // value is a pointer so that it can be updated while the test is running. | ||
681 | // It will only be dereferenced at the point this step is run. | ||
682 | func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { | ||
683 | return func(s *terraform.State) error { | ||
684 | return TestCheckResourceAttr(name, key, *value)(s) | ||
685 | } | ||
686 | } | ||
687 | |||
688 | // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values | ||
689 | // in state for a pair of name/key combinations are equal. | ||
690 | func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { | ||
691 | return func(s *terraform.State) error { | ||
692 | isFirst, err := primaryInstanceState(s, nameFirst) | ||
693 | if err != nil { | ||
694 | return err | ||
695 | } | ||
696 | vFirst, ok := isFirst.Attributes[keyFirst] | ||
697 | if !ok { | ||
698 | return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) | ||
699 | } | ||
700 | |||
701 | isSecond, err := primaryInstanceState(s, nameSecond) | ||
702 | if err != nil { | ||
703 | return err | ||
704 | } | ||
705 | vSecond, ok := isSecond.Attributes[keySecond] | ||
706 | if !ok { | ||
707 | return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) | ||
708 | } | ||
709 | |||
710 | if vFirst != vSecond { | ||
711 | return fmt.Errorf( | ||
712 | "%s: Attribute '%s' expected %#v, got %#v", | ||
713 | nameFirst, | ||
714 | keyFirst, | ||
715 | vSecond, | ||
716 | vFirst) | ||
717 | } | ||
718 | |||
719 | return nil | ||
720 | } | ||
721 | } | ||
722 | |||
723 | // TestCheckOutput checks an output in the Terraform configuration | ||
724 | func TestCheckOutput(name, value string) TestCheckFunc { | ||
725 | return func(s *terraform.State) error { | ||
726 | ms := s.RootModule() | ||
727 | rs, ok := ms.Outputs[name] | ||
728 | if !ok { | ||
729 | return fmt.Errorf("Not found: %s", name) | ||
730 | } | ||
731 | |||
732 | if rs.Value != value { | ||
733 | return fmt.Errorf( | ||
734 | "Output '%s': expected %#v, got %#v", | ||
735 | name, | ||
736 | value, | ||
737 | rs) | ||
738 | } | ||
739 | |||
740 | return nil | ||
741 | } | ||
742 | } | ||
743 | |||
744 | func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { | ||
745 | return func(s *terraform.State) error { | ||
746 | ms := s.RootModule() | ||
747 | rs, ok := ms.Outputs[name] | ||
748 | if !ok { | ||
749 | return fmt.Errorf("Not found: %s", name) | ||
750 | } | ||
751 | |||
752 | if !r.MatchString(rs.Value.(string)) { | ||
753 | return fmt.Errorf( | ||
754 | "Output '%s': %#v didn't match %q", | ||
755 | name, | ||
756 | rs, | ||
757 | r.String()) | ||
758 | } | ||
759 | |||
760 | return nil | ||
761 | } | ||
762 | } | ||
763 | |||
764 | // TestT is the interface used to handle the test lifecycle of a test. | ||
765 | // | ||
766 | // Users should just use a *testing.T object, which implements this. | ||
767 | type TestT interface { | ||
768 | Error(args ...interface{}) | ||
769 | Fatal(args ...interface{}) | ||
770 | Skip(args ...interface{}) | ||
771 | } | ||
772 | |||
773 | // This is set to true by unit tests to alter some behavior | ||
774 | var testTesting = false | ||
775 | |||
776 | // primaryInstanceState returns the primary instance state for the given resource name. | ||
777 | func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { | ||
778 | ms := s.RootModule() | ||
779 | rs, ok := ms.Resources[name] | ||
780 | if !ok { | ||
781 | return nil, fmt.Errorf("Not found: %s", name) | ||
782 | } | ||
783 | |||
784 | is := rs.Primary | ||
785 | if is == nil { | ||
786 | return nil, fmt.Errorf("No primary instance: %s", name) | ||
787 | } | ||
788 | |||
789 | return is, nil | ||
790 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go new file mode 100644 index 0000000..537a11c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go | |||
@@ -0,0 +1,160 @@ | |||
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 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go new file mode 100644 index 0000000..28ad105 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go | |||
@@ -0,0 +1,141 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "log" | ||
6 | "reflect" | ||
7 | "strings" | ||
8 | |||
9 | "github.com/davecgh/go-spew/spew" | ||
10 | "github.com/hashicorp/terraform/terraform" | ||
11 | ) | ||
12 | |||
13 | // testStepImportState runs an imort state test step | ||
14 | func testStepImportState( | ||
15 | opts terraform.ContextOpts, | ||
16 | state *terraform.State, | ||
17 | step TestStep) (*terraform.State, error) { | ||
18 | // Determine the ID to import | ||
19 | importId := step.ImportStateId | ||
20 | if importId == "" { | ||
21 | resource, err := testResource(step, state) | ||
22 | if err != nil { | ||
23 | return state, err | ||
24 | } | ||
25 | |||
26 | importId = resource.Primary.ID | ||
27 | } | ||
28 | importPrefix := step.ImportStateIdPrefix | ||
29 | if importPrefix != "" { | ||
30 | importId = fmt.Sprintf("%s%s", importPrefix, importId) | ||
31 | } | ||
32 | |||
33 | // Setup the context. We initialize with an empty state. We use the | ||
34 | // full config for provider configurations. | ||
35 | mod, err := testModule(opts, step) | ||
36 | if err != nil { | ||
37 | return state, err | ||
38 | } | ||
39 | |||
40 | opts.Module = mod | ||
41 | opts.State = terraform.NewState() | ||
42 | ctx, err := terraform.NewContext(&opts) | ||
43 | if err != nil { | ||
44 | return state, err | ||
45 | } | ||
46 | |||
47 | // Do the import! | ||
48 | newState, err := ctx.Import(&terraform.ImportOpts{ | ||
49 | // Set the module so that any provider config is loaded | ||
50 | Module: mod, | ||
51 | |||
52 | Targets: []*terraform.ImportTarget{ | ||
53 | &terraform.ImportTarget{ | ||
54 | Addr: step.ResourceName, | ||
55 | ID: importId, | ||
56 | }, | ||
57 | }, | ||
58 | }) | ||
59 | if err != nil { | ||
60 | log.Printf("[ERROR] Test: ImportState failure: %s", err) | ||
61 | return state, err | ||
62 | } | ||
63 | |||
64 | // Go through the new state and verify | ||
65 | if step.ImportStateCheck != nil { | ||
66 | var states []*terraform.InstanceState | ||
67 | for _, r := range newState.RootModule().Resources { | ||
68 | if r.Primary != nil { | ||
69 | states = append(states, r.Primary) | ||
70 | } | ||
71 | } | ||
72 | if err := step.ImportStateCheck(states); err != nil { | ||
73 | return state, err | ||
74 | } | ||
75 | } | ||
76 | |||
77 | // Verify that all the states match | ||
78 | if step.ImportStateVerify { | ||
79 | new := newState.RootModule().Resources | ||
80 | old := state.RootModule().Resources | ||
81 | for _, r := range new { | ||
82 | // Find the existing resource | ||
83 | var oldR *terraform.ResourceState | ||
84 | for _, r2 := range old { | ||
85 | if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type { | ||
86 | oldR = r2 | ||
87 | break | ||
88 | } | ||
89 | } | ||
90 | if oldR == nil { | ||
91 | return state, fmt.Errorf( | ||
92 | "Failed state verification, resource with ID %s not found", | ||
93 | r.Primary.ID) | ||
94 | } | ||
95 | |||
96 | // Compare their attributes | ||
97 | actual := make(map[string]string) | ||
98 | for k, v := range r.Primary.Attributes { | ||
99 | actual[k] = v | ||
100 | } | ||
101 | expected := make(map[string]string) | ||
102 | for k, v := range oldR.Primary.Attributes { | ||
103 | expected[k] = v | ||
104 | } | ||
105 | |||
106 | // Remove fields we're ignoring | ||
107 | for _, v := range step.ImportStateVerifyIgnore { | ||
108 | for k, _ := range actual { | ||
109 | if strings.HasPrefix(k, v) { | ||
110 | delete(actual, k) | ||
111 | } | ||
112 | } | ||
113 | for k, _ := range expected { | ||
114 | if strings.HasPrefix(k, v) { | ||
115 | delete(expected, k) | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | |||
120 | if !reflect.DeepEqual(actual, expected) { | ||
121 | // Determine only the different attributes | ||
122 | for k, v := range expected { | ||
123 | if av, ok := actual[k]; ok && v == av { | ||
124 | delete(expected, k) | ||
125 | delete(actual, k) | ||
126 | } | ||
127 | } | ||
128 | |||
129 | spewConf := spew.NewDefaultConfig() | ||
130 | spewConf.SortKeys = true | ||
131 | return state, fmt.Errorf( | ||
132 | "ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ | ||
133 | "\n\n%s\n\n%s", | ||
134 | spewConf.Sdump(actual), spewConf.Sdump(expected)) | ||
135 | } | ||
136 | } | ||
137 | } | ||
138 | |||
139 | // Return the old state (non-imported) so we don't change anything. | ||
140 | return state, nil | ||
141 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/wait.go b/vendor/github.com/hashicorp/terraform/helper/resource/wait.go new file mode 100644 index 0000000..ca50e29 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/wait.go | |||
@@ -0,0 +1,84 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "sync" | ||
5 | "time" | ||
6 | ) | ||
7 | |||
8 | // Retry is a basic wrapper around StateChangeConf that will just retry | ||
9 | // a function until it no longer returns an error. | ||
10 | func Retry(timeout time.Duration, f RetryFunc) error { | ||
11 | // These are used to pull the error out of the function; need a mutex to | ||
12 | // avoid a data race. | ||
13 | var resultErr error | ||
14 | var resultErrMu sync.Mutex | ||
15 | |||
16 | c := &StateChangeConf{ | ||
17 | Pending: []string{"retryableerror"}, | ||
18 | Target: []string{"success"}, | ||
19 | Timeout: timeout, | ||
20 | MinTimeout: 500 * time.Millisecond, | ||
21 | Refresh: func() (interface{}, string, error) { | ||
22 | rerr := f() | ||
23 | |||
24 | resultErrMu.Lock() | ||
25 | defer resultErrMu.Unlock() | ||
26 | |||
27 | if rerr == nil { | ||
28 | resultErr = nil | ||
29 | return 42, "success", nil | ||
30 | } | ||
31 | |||
32 | resultErr = rerr.Err | ||
33 | |||
34 | if rerr.Retryable { | ||
35 | return 42, "retryableerror", nil | ||
36 | } | ||
37 | return nil, "quit", rerr.Err | ||
38 | }, | ||
39 | } | ||
40 | |||
41 | _, waitErr := c.WaitForState() | ||
42 | |||
43 | // Need to acquire the lock here to be able to avoid race using resultErr as | ||
44 | // the return value | ||
45 | resultErrMu.Lock() | ||
46 | defer resultErrMu.Unlock() | ||
47 | |||
48 | // resultErr may be nil because the wait timed out and resultErr was never | ||
49 | // set; this is still an error | ||
50 | if resultErr == nil { | ||
51 | return waitErr | ||
52 | } | ||
53 | // resultErr takes precedence over waitErr if both are set because it is | ||
54 | // more likely to be useful | ||
55 | return resultErr | ||
56 | } | ||
57 | |||
58 | // RetryFunc is the function retried until it succeeds. | ||
59 | type RetryFunc func() *RetryError | ||
60 | |||
61 | // RetryError is the required return type of RetryFunc. It forces client code | ||
62 | // to choose whether or not a given error is retryable. | ||
63 | type RetryError struct { | ||
64 | Err error | ||
65 | Retryable bool | ||
66 | } | ||
67 | |||
68 | // RetryableError is a helper to create a RetryError that's retryable from a | ||
69 | // given error. | ||
70 | func RetryableError(err error) *RetryError { | ||
71 | if err == nil { | ||
72 | return nil | ||
73 | } | ||
74 | return &RetryError{Err: err, Retryable: true} | ||
75 | } | ||
76 | |||
77 | // NonRetryableError is a helper to create a RetryError that's _not)_ retryable | ||
78 | // from a given error. | ||
79 | func NonRetryableError(err error) *RetryError { | ||
80 | if err == nil { | ||
81 | return nil | ||
82 | } | ||
83 | return &RetryError{Err: err, Retryable: false} | ||
84 | } | ||