aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/helper/resource
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/resource')
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/error.go79
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/id.go39
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/map.go140
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/resource.go49
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/state.go259
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing.go790
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go160
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go141
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/wait.go84
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 @@
1package resource
2
3import (
4 "fmt"
5 "strings"
6 "time"
7)
8
9type NotFoundError struct {
10 LastError error
11 LastRequest interface{}
12 LastResponse interface{}
13 Message string
14 Retries int
15}
16
17func (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
30type UnexpectedStateError struct {
31 LastError error
32 State string
33 ExpectedState []string
34}
35
36func (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
46type TimeoutError struct {
47 LastError error
48 LastState string
49 Timeout time.Duration
50 ExpectedState []string
51}
52
53func (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 @@
1package resource
2
3import (
4 "crypto/rand"
5 "fmt"
6 "math/big"
7 "sync"
8)
9
10const 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.
17var idMutex sync.Mutex
18var idCounter = big.NewInt(0).SetBytes(randomBytes(12))
19
20// Helper for a resource to generate a unique identifier w/ default prefix
21func 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).
29func 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
35func 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 @@
1package resource
2
3import (
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.
12type Map struct {
13 Mapping map[string]Resource
14}
15
16func (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.
33func (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.
86func (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.
105func (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.
125func (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 @@
1package resource
2
3import (
4 "github.com/hashicorp/terraform/helper/config"
5 "github.com/hashicorp/terraform/terraform"
6)
7
8type 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.
19type 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.
26type DestroyFunc func(
27 *terraform.InstanceState,
28 interface{}) error
29
30// DiffFunc is a function that performs a diff of a resource.
31type 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.
38type 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.
46type 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 @@
1package resource
2
3import (
4 "log"
5 "time"
6)
7
8var 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.
20type StateRefreshFunc func() (result interface{}, state string, err error)
21
22// StateChangeConf is the configuration struct used for `WaitForState`.
23type 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.
51func (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 @@
1package resource
2
3import (
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
23const 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.
28type 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.
36type TestCheckFunc func(*terraform.State) error
37
38// ImportStateCheckFunc is the check function for ImportState tests
39type 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.
46type 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.
101type 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.
210func 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.
356func 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.
388func UnitTest(t TestT, c TestCase) {
389 c.IsUnitTest = true
390 Test(t, c)
391}
392
393func 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
492func 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
539func 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.
561func 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.
581func 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.
599func 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.
616func 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.
642func 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.
659func 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.
682func 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.
690func 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
724func 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
744func 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.
767type 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
774var testTesting = false
775
776// primaryInstanceState returns the primary instance state for the given resource name.
777func 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 @@
1package resource
2
3import (
4 "fmt"
5 "log"
6 "strings"
7
8 "github.com/hashicorp/terraform/terraform"
9)
10
11// testStepConfig runs a config-mode test step
12func testStepConfig(
13 opts terraform.ContextOpts,
14 state *terraform.State,
15 step TestStep) (*terraform.State, error) {
16 return testStep(opts, state, step)
17}
18
19func 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 @@
1package resource
2
3import (
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
14func 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 @@
1package resource
2
3import (
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.
10func 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.
59type 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.
63type 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.
70func 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.
79func NonRetryableError(err error) *RetryError {
80 if err == nil {
81 return nil
82 }
83 return &RetryError{Err: err, Retryable: false}
84}