diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper')
55 files changed, 8700 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/acctest/acctest.go b/vendor/github.com/hashicorp/terraform/helper/acctest/acctest.go new file mode 100644 index 0000000..9d31031 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/acctest/acctest.go | |||
@@ -0,0 +1,2 @@ | |||
1 | // Package acctest contains for Terraform Acceptance Tests | ||
2 | package acctest | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/acctest/random.go b/vendor/github.com/hashicorp/terraform/helper/acctest/random.go new file mode 100644 index 0000000..3ddc078 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/acctest/random.go | |||
@@ -0,0 +1,93 @@ | |||
1 | package acctest | ||
2 | |||
3 | import ( | ||
4 | "bufio" | ||
5 | "bytes" | ||
6 | crand "crypto/rand" | ||
7 | "crypto/rsa" | ||
8 | "crypto/x509" | ||
9 | "encoding/pem" | ||
10 | "fmt" | ||
11 | "math/rand" | ||
12 | "strings" | ||
13 | "time" | ||
14 | |||
15 | "golang.org/x/crypto/ssh" | ||
16 | ) | ||
17 | |||
18 | // Helpers for generating random tidbits for use in identifiers to prevent | ||
19 | // collisions in acceptance tests. | ||
20 | |||
21 | // RandInt generates a random integer | ||
22 | func RandInt() int { | ||
23 | reseed() | ||
24 | return rand.New(rand.NewSource(time.Now().UnixNano())).Int() | ||
25 | } | ||
26 | |||
27 | // RandomWithPrefix is used to generate a unique name with a prefix, for | ||
28 | // randomizing names in acceptance tests | ||
29 | func RandomWithPrefix(name string) string { | ||
30 | reseed() | ||
31 | return fmt.Sprintf("%s-%d", name, rand.New(rand.NewSource(time.Now().UnixNano())).Int()) | ||
32 | } | ||
33 | |||
34 | func RandIntRange(min int, max int) int { | ||
35 | reseed() | ||
36 | source := rand.New(rand.NewSource(time.Now().UnixNano())) | ||
37 | rangeMax := max - min | ||
38 | |||
39 | return int(source.Int31n(int32(rangeMax))) | ||
40 | } | ||
41 | |||
42 | // RandString generates a random alphanumeric string of the length specified | ||
43 | func RandString(strlen int) string { | ||
44 | return RandStringFromCharSet(strlen, CharSetAlphaNum) | ||
45 | } | ||
46 | |||
47 | // RandStringFromCharSet generates a random string by selecting characters from | ||
48 | // the charset provided | ||
49 | func RandStringFromCharSet(strlen int, charSet string) string { | ||
50 | reseed() | ||
51 | result := make([]byte, strlen) | ||
52 | for i := 0; i < strlen; i++ { | ||
53 | result[i] = charSet[rand.Intn(len(charSet))] | ||
54 | } | ||
55 | return string(result) | ||
56 | } | ||
57 | |||
58 | // RandSSHKeyPair generates a public and private SSH key pair. The public key is | ||
59 | // returned in OpenSSH format, and the private key is PEM encoded. | ||
60 | func RandSSHKeyPair(comment string) (string, string, error) { | ||
61 | privateKey, err := rsa.GenerateKey(crand.Reader, 1024) | ||
62 | if err != nil { | ||
63 | return "", "", err | ||
64 | } | ||
65 | |||
66 | var privateKeyBuffer bytes.Buffer | ||
67 | privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} | ||
68 | if err := pem.Encode(bufio.NewWriter(&privateKeyBuffer), privateKeyPEM); err != nil { | ||
69 | return "", "", err | ||
70 | } | ||
71 | |||
72 | publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey) | ||
73 | if err != nil { | ||
74 | return "", "", err | ||
75 | } | ||
76 | keyMaterial := strings.TrimSpace(string(ssh.MarshalAuthorizedKey(publicKey))) | ||
77 | return fmt.Sprintf("%s %s", keyMaterial, comment), privateKeyBuffer.String(), nil | ||
78 | } | ||
79 | |||
80 | // Seeds random with current timestamp | ||
81 | func reseed() { | ||
82 | rand.Seed(time.Now().UTC().UnixNano()) | ||
83 | } | ||
84 | |||
85 | const ( | ||
86 | // CharSetAlphaNum is the alphanumeric character set for use with | ||
87 | // RandStringFromCharSet | ||
88 | CharSetAlphaNum = "abcdefghijklmnopqrstuvwxyz012346789" | ||
89 | |||
90 | // CharSetAlpha is the alphabetical character set for use with | ||
91 | // RandStringFromCharSet | ||
92 | CharSetAlpha = "abcdefghijklmnopqrstuvwxyz" | ||
93 | ) | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/acctest/remotetests.go b/vendor/github.com/hashicorp/terraform/helper/acctest/remotetests.go new file mode 100644 index 0000000..87c60b8 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/acctest/remotetests.go | |||
@@ -0,0 +1,27 @@ | |||
1 | package acctest | ||
2 | |||
3 | import ( | ||
4 | "net/http" | ||
5 | "os" | ||
6 | "testing" | ||
7 | ) | ||
8 | |||
9 | // SkipRemoteTestsEnvVar is an environment variable that can be set by a user | ||
10 | // running the tests in an environment with limited network connectivity. By | ||
11 | // default, tests requiring internet connectivity make an effort to skip if no | ||
12 | // internet is available, but in some cases the smoke test will pass even | ||
13 | // though the test should still be skipped. | ||
14 | const SkipRemoteTestsEnvVar = "TF_SKIP_REMOTE_TESTS" | ||
15 | |||
16 | // RemoteTestPrecheck is meant to be run by any unit test that requires | ||
17 | // outbound internet connectivity. The test will be skipped if it's | ||
18 | // unavailable. | ||
19 | func RemoteTestPrecheck(t *testing.T) { | ||
20 | if os.Getenv(SkipRemoteTestsEnvVar) != "" { | ||
21 | t.Skipf("skipping test, %s was set", SkipRemoteTestsEnvVar) | ||
22 | } | ||
23 | |||
24 | if _, err := http.Get("http://google.com"); err != nil { | ||
25 | t.Skipf("skipping, internet seems to not be available: %s", err) | ||
26 | } | ||
27 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/config/decode.go b/vendor/github.com/hashicorp/terraform/helper/config/decode.go new file mode 100644 index 0000000..f470c9b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/config/decode.go | |||
@@ -0,0 +1,28 @@ | |||
1 | package config | ||
2 | |||
3 | import ( | ||
4 | "github.com/mitchellh/mapstructure" | ||
5 | ) | ||
6 | |||
7 | func Decode(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) { | ||
8 | var md mapstructure.Metadata | ||
9 | decoderConfig := &mapstructure.DecoderConfig{ | ||
10 | Metadata: &md, | ||
11 | Result: target, | ||
12 | WeaklyTypedInput: true, | ||
13 | } | ||
14 | |||
15 | decoder, err := mapstructure.NewDecoder(decoderConfig) | ||
16 | if err != nil { | ||
17 | return nil, err | ||
18 | } | ||
19 | |||
20 | for _, raw := range raws { | ||
21 | err := decoder.Decode(raw) | ||
22 | if err != nil { | ||
23 | return nil, err | ||
24 | } | ||
25 | } | ||
26 | |||
27 | return &md, nil | ||
28 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/config/validator.go b/vendor/github.com/hashicorp/terraform/helper/config/validator.go new file mode 100644 index 0000000..1a6e023 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/config/validator.go | |||
@@ -0,0 +1,214 @@ | |||
1 | package config | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strconv" | ||
6 | "strings" | ||
7 | |||
8 | "github.com/hashicorp/terraform/flatmap" | ||
9 | "github.com/hashicorp/terraform/terraform" | ||
10 | ) | ||
11 | |||
12 | // Validator is a helper that helps you validate the configuration | ||
13 | // of your resource, resource provider, etc. | ||
14 | // | ||
15 | // At the most basic level, set the Required and Optional lists to be | ||
16 | // specifiers of keys that are required or optional. If a key shows up | ||
17 | // that isn't in one of these two lists, then an error is generated. | ||
18 | // | ||
19 | // The "specifiers" allowed in this is a fairly rich syntax to help | ||
20 | // describe the format of your configuration: | ||
21 | // | ||
22 | // * Basic keys are just strings. For example: "foo" will match the | ||
23 | // "foo" key. | ||
24 | // | ||
25 | // * Nested structure keys can be matched by doing | ||
26 | // "listener.*.foo". This will verify that there is at least one | ||
27 | // listener element that has the "foo" key set. | ||
28 | // | ||
29 | // * The existence of a nested structure can be checked by simply | ||
30 | // doing "listener.*" which will verify that there is at least | ||
31 | // one element in the "listener" structure. This is NOT | ||
32 | // validating that "listener" is an array. It is validating | ||
33 | // that it is a nested structure in the configuration. | ||
34 | // | ||
35 | type Validator struct { | ||
36 | Required []string | ||
37 | Optional []string | ||
38 | } | ||
39 | |||
40 | func (v *Validator) Validate( | ||
41 | c *terraform.ResourceConfig) (ws []string, es []error) { | ||
42 | // Flatten the configuration so it is easier to reason about | ||
43 | flat := flatmap.Flatten(c.Raw) | ||
44 | |||
45 | keySet := make(map[string]validatorKey) | ||
46 | for i, vs := range [][]string{v.Required, v.Optional} { | ||
47 | req := i == 0 | ||
48 | for _, k := range vs { | ||
49 | vk, err := newValidatorKey(k, req) | ||
50 | if err != nil { | ||
51 | es = append(es, err) | ||
52 | continue | ||
53 | } | ||
54 | |||
55 | keySet[k] = vk | ||
56 | } | ||
57 | } | ||
58 | |||
59 | purged := make([]string, 0) | ||
60 | for _, kv := range keySet { | ||
61 | p, w, e := kv.Validate(flat) | ||
62 | if len(w) > 0 { | ||
63 | ws = append(ws, w...) | ||
64 | } | ||
65 | if len(e) > 0 { | ||
66 | es = append(es, e...) | ||
67 | } | ||
68 | |||
69 | purged = append(purged, p...) | ||
70 | } | ||
71 | |||
72 | // Delete all the keys we processed in order to find | ||
73 | // the unknown keys. | ||
74 | for _, p := range purged { | ||
75 | delete(flat, p) | ||
76 | } | ||
77 | |||
78 | // The rest are unknown | ||
79 | for k, _ := range flat { | ||
80 | es = append(es, fmt.Errorf("Unknown configuration: %s", k)) | ||
81 | } | ||
82 | |||
83 | return | ||
84 | } | ||
85 | |||
86 | type validatorKey interface { | ||
87 | // Validate validates the given configuration and returns viewed keys, | ||
88 | // warnings, and errors. | ||
89 | Validate(map[string]string) ([]string, []string, []error) | ||
90 | } | ||
91 | |||
92 | func newValidatorKey(k string, req bool) (validatorKey, error) { | ||
93 | var result validatorKey | ||
94 | |||
95 | parts := strings.Split(k, ".") | ||
96 | if len(parts) > 1 && parts[1] == "*" { | ||
97 | result = &nestedValidatorKey{ | ||
98 | Parts: parts, | ||
99 | Required: req, | ||
100 | } | ||
101 | } else { | ||
102 | result = &basicValidatorKey{ | ||
103 | Key: k, | ||
104 | Required: req, | ||
105 | } | ||
106 | } | ||
107 | |||
108 | return result, nil | ||
109 | } | ||
110 | |||
111 | // basicValidatorKey validates keys that are basic such as "foo" | ||
112 | type basicValidatorKey struct { | ||
113 | Key string | ||
114 | Required bool | ||
115 | } | ||
116 | |||
117 | func (v *basicValidatorKey) Validate( | ||
118 | m map[string]string) ([]string, []string, []error) { | ||
119 | for k, _ := range m { | ||
120 | // If we have the exact key its a match | ||
121 | if k == v.Key { | ||
122 | return []string{k}, nil, nil | ||
123 | } | ||
124 | } | ||
125 | |||
126 | if !v.Required { | ||
127 | return nil, nil, nil | ||
128 | } | ||
129 | |||
130 | return nil, nil, []error{fmt.Errorf( | ||
131 | "Key not found: %s", v.Key)} | ||
132 | } | ||
133 | |||
134 | type nestedValidatorKey struct { | ||
135 | Parts []string | ||
136 | Required bool | ||
137 | } | ||
138 | |||
139 | func (v *nestedValidatorKey) validate( | ||
140 | m map[string]string, | ||
141 | prefix string, | ||
142 | offset int) ([]string, []string, []error) { | ||
143 | if offset >= len(v.Parts) { | ||
144 | // We're at the end. Look for a specific key. | ||
145 | v2 := &basicValidatorKey{Key: prefix, Required: v.Required} | ||
146 | return v2.Validate(m) | ||
147 | } | ||
148 | |||
149 | current := v.Parts[offset] | ||
150 | |||
151 | // If we're at offset 0, special case to start at the next one. | ||
152 | if offset == 0 { | ||
153 | return v.validate(m, current, offset+1) | ||
154 | } | ||
155 | |||
156 | // Determine if we're doing a "for all" or a specific key | ||
157 | if current != "*" { | ||
158 | // We're looking at a specific key, continue on. | ||
159 | return v.validate(m, prefix+"."+current, offset+1) | ||
160 | } | ||
161 | |||
162 | // We're doing a "for all", so we loop over. | ||
163 | countStr, ok := m[prefix+".#"] | ||
164 | if !ok { | ||
165 | if !v.Required { | ||
166 | // It wasn't required, so its no problem. | ||
167 | return nil, nil, nil | ||
168 | } | ||
169 | |||
170 | return nil, nil, []error{fmt.Errorf( | ||
171 | "Key not found: %s", prefix)} | ||
172 | } | ||
173 | |||
174 | count, err := strconv.ParseInt(countStr, 0, 0) | ||
175 | if err != nil { | ||
176 | // This shouldn't happen if flatmap works properly | ||
177 | panic("invalid flatmap array") | ||
178 | } | ||
179 | |||
180 | var e []error | ||
181 | var w []string | ||
182 | u := make([]string, 1, count+1) | ||
183 | u[0] = prefix + ".#" | ||
184 | for i := 0; i < int(count); i++ { | ||
185 | prefix := fmt.Sprintf("%s.%d", prefix, i) | ||
186 | |||
187 | // Mark that we saw this specific key | ||
188 | u = append(u, prefix) | ||
189 | |||
190 | // Mark all prefixes of this | ||
191 | for k, _ := range m { | ||
192 | if !strings.HasPrefix(k, prefix+".") { | ||
193 | continue | ||
194 | } | ||
195 | u = append(u, k) | ||
196 | } | ||
197 | |||
198 | // If we have more parts, then validate deeper | ||
199 | if offset+1 < len(v.Parts) { | ||
200 | u2, w2, e2 := v.validate(m, prefix, offset+1) | ||
201 | |||
202 | u = append(u, u2...) | ||
203 | w = append(w, w2...) | ||
204 | e = append(e, e2...) | ||
205 | } | ||
206 | } | ||
207 | |||
208 | return u, w, e | ||
209 | } | ||
210 | |||
211 | func (v *nestedValidatorKey) Validate( | ||
212 | m map[string]string) ([]string, []string, []error) { | ||
213 | return v.validate(m, "", 0) | ||
214 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/experiment/experiment.go b/vendor/github.com/hashicorp/terraform/helper/experiment/experiment.go new file mode 100644 index 0000000..18b8837 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/experiment/experiment.go | |||
@@ -0,0 +1,154 @@ | |||
1 | // experiment package contains helper functions for tracking experimental | ||
2 | // features throughout Terraform. | ||
3 | // | ||
4 | // This package should be used for creating, enabling, querying, and deleting | ||
5 | // experimental features. By unifying all of that onto a single interface, | ||
6 | // we can have the Go compiler help us by enforcing every place we touch | ||
7 | // an experimental feature. | ||
8 | // | ||
9 | // To create a new experiment: | ||
10 | // | ||
11 | // 1. Add the experiment to the global vars list below, prefixed with X_ | ||
12 | // | ||
13 | // 2. Add the experiment variable to the All listin the init() function | ||
14 | // | ||
15 | // 3. Use it! | ||
16 | // | ||
17 | // To remove an experiment: | ||
18 | // | ||
19 | // 1. Delete the experiment global var. | ||
20 | // | ||
21 | // 2. Try to compile and fix all the places where the var was referenced. | ||
22 | // | ||
23 | // To use an experiment: | ||
24 | // | ||
25 | // 1. Use Flag() if you want the experiment to be available from the CLI. | ||
26 | // | ||
27 | // 2. Use Enabled() to check whether it is enabled. | ||
28 | // | ||
29 | // As a general user: | ||
30 | // | ||
31 | // 1. The `-Xexperiment-name` flag | ||
32 | // 2. The `TF_X_<experiment-name>` env var. | ||
33 | // 3. The `TF_X_FORCE` env var can be set to force an experimental feature | ||
34 | // without human verifications. | ||
35 | // | ||
36 | package experiment | ||
37 | |||
38 | import ( | ||
39 | "flag" | ||
40 | "fmt" | ||
41 | "os" | ||
42 | "strconv" | ||
43 | "strings" | ||
44 | "sync" | ||
45 | ) | ||
46 | |||
47 | // The experiments that are available are listed below. Any package in | ||
48 | // Terraform defining an experiment should define the experiments below. | ||
49 | // By keeping them all within the experiment package we force a single point | ||
50 | // of definition and use. This allows the compiler to enforce references | ||
51 | // so it becomes easy to remove the features. | ||
52 | var ( | ||
53 | // Shadow graph. This is already on by default. Disabling it will be | ||
54 | // allowed for awhile in order for it to not block operations. | ||
55 | X_shadow = newBasicID("shadow", "SHADOW", false) | ||
56 | ) | ||
57 | |||
58 | // Global variables this package uses because we are a package | ||
59 | // with global state. | ||
60 | var ( | ||
61 | // all is the list of all experiements. Do not modify this. | ||
62 | All []ID | ||
63 | |||
64 | // enabled keeps track of what flags have been enabled | ||
65 | enabled map[string]bool | ||
66 | enabledLock sync.Mutex | ||
67 | |||
68 | // Hidden "experiment" that forces all others to be on without verification | ||
69 | x_force = newBasicID("force", "FORCE", false) | ||
70 | ) | ||
71 | |||
72 | func init() { | ||
73 | // The list of all experiments, update this when an experiment is added. | ||
74 | All = []ID{ | ||
75 | X_shadow, | ||
76 | x_force, | ||
77 | } | ||
78 | |||
79 | // Load | ||
80 | reload() | ||
81 | } | ||
82 | |||
83 | // reload is used by tests to reload the global state. This is called by | ||
84 | // init publicly. | ||
85 | func reload() { | ||
86 | // Initialize | ||
87 | enabledLock.Lock() | ||
88 | enabled = make(map[string]bool) | ||
89 | enabledLock.Unlock() | ||
90 | |||
91 | // Set defaults and check env vars | ||
92 | for _, id := range All { | ||
93 | // Get the default value | ||
94 | def := id.Default() | ||
95 | |||
96 | // If we set it in the env var, default it to true | ||
97 | key := fmt.Sprintf("TF_X_%s", strings.ToUpper(id.Env())) | ||
98 | if v := os.Getenv(key); v != "" { | ||
99 | def = v != "0" | ||
100 | } | ||
101 | |||
102 | // Set the default | ||
103 | SetEnabled(id, def) | ||
104 | } | ||
105 | } | ||
106 | |||
107 | // Enabled returns whether an experiment has been enabled or not. | ||
108 | func Enabled(id ID) bool { | ||
109 | enabledLock.Lock() | ||
110 | defer enabledLock.Unlock() | ||
111 | return enabled[id.Flag()] | ||
112 | } | ||
113 | |||
114 | // SetEnabled sets an experiment to enabled/disabled. Please check with | ||
115 | // the experiment docs for when calling this actually affects the experiment. | ||
116 | func SetEnabled(id ID, v bool) { | ||
117 | enabledLock.Lock() | ||
118 | defer enabledLock.Unlock() | ||
119 | enabled[id.Flag()] = v | ||
120 | } | ||
121 | |||
122 | // Force returns true if the -Xforce of TF_X_FORCE flag is present, which | ||
123 | // advises users of this package to not verify with the user that they want | ||
124 | // experimental behavior and to just continue with it. | ||
125 | func Force() bool { | ||
126 | return Enabled(x_force) | ||
127 | } | ||
128 | |||
129 | // Flag configures the given FlagSet with the flags to configure | ||
130 | // all active experiments. | ||
131 | func Flag(fs *flag.FlagSet) { | ||
132 | for _, id := range All { | ||
133 | desc := id.Flag() | ||
134 | key := fmt.Sprintf("X%s", id.Flag()) | ||
135 | fs.Var(&idValue{X: id}, key, desc) | ||
136 | } | ||
137 | } | ||
138 | |||
139 | // idValue implements flag.Value for setting the enabled/disabled state | ||
140 | // of an experiment from the CLI. | ||
141 | type idValue struct { | ||
142 | X ID | ||
143 | } | ||
144 | |||
145 | func (v *idValue) IsBoolFlag() bool { return true } | ||
146 | func (v *idValue) String() string { return strconv.FormatBool(Enabled(v.X)) } | ||
147 | func (v *idValue) Set(raw string) error { | ||
148 | b, err := strconv.ParseBool(raw) | ||
149 | if err == nil { | ||
150 | SetEnabled(v.X, b) | ||
151 | } | ||
152 | |||
153 | return err | ||
154 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/experiment/id.go b/vendor/github.com/hashicorp/terraform/helper/experiment/id.go new file mode 100644 index 0000000..8e2f707 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/experiment/id.go | |||
@@ -0,0 +1,34 @@ | |||
1 | package experiment | ||
2 | |||
3 | // ID represents an experimental feature. | ||
4 | // | ||
5 | // The global vars defined on this package should be used as ID values. | ||
6 | // This interface is purposely not implement-able outside of this package | ||
7 | // so that we can rely on the Go compiler to enforce all experiment references. | ||
8 | type ID interface { | ||
9 | Env() string | ||
10 | Flag() string | ||
11 | Default() bool | ||
12 | |||
13 | unexported() // So the ID can't be implemented externally. | ||
14 | } | ||
15 | |||
16 | // basicID implements ID. | ||
17 | type basicID struct { | ||
18 | EnvValue string | ||
19 | FlagValue string | ||
20 | DefaultValue bool | ||
21 | } | ||
22 | |||
23 | func newBasicID(flag, env string, def bool) ID { | ||
24 | return &basicID{ | ||
25 | EnvValue: env, | ||
26 | FlagValue: flag, | ||
27 | DefaultValue: def, | ||
28 | } | ||
29 | } | ||
30 | |||
31 | func (id *basicID) Env() string { return id.EnvValue } | ||
32 | func (id *basicID) Flag() string { return id.FlagValue } | ||
33 | func (id *basicID) Default() bool { return id.DefaultValue } | ||
34 | func (id *basicID) unexported() {} | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/hashcode/hashcode.go b/vendor/github.com/hashicorp/terraform/helper/hashcode/hashcode.go new file mode 100644 index 0000000..64d8263 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/hashcode/hashcode.go | |||
@@ -0,0 +1,22 @@ | |||
1 | package hashcode | ||
2 | |||
3 | import ( | ||
4 | "hash/crc32" | ||
5 | ) | ||
6 | |||
7 | // String hashes a string to a unique hashcode. | ||
8 | // | ||
9 | // crc32 returns a uint32, but for our use we need | ||
10 | // and non negative integer. Here we cast to an integer | ||
11 | // and invert it if the result is negative. | ||
12 | func String(s string) int { | ||
13 | v := int(crc32.ChecksumIEEE([]byte(s))) | ||
14 | if v >= 0 { | ||
15 | return v | ||
16 | } | ||
17 | if -v >= 0 { | ||
18 | return -v | ||
19 | } | ||
20 | // v == MinInt | ||
21 | return 0 | ||
22 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/hilmapstructure/hilmapstructure.go b/vendor/github.com/hashicorp/terraform/helper/hilmapstructure/hilmapstructure.go new file mode 100644 index 0000000..67be1df --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/hilmapstructure/hilmapstructure.go | |||
@@ -0,0 +1,41 @@ | |||
1 | package hilmapstructure | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "reflect" | ||
6 | |||
7 | "github.com/mitchellh/mapstructure" | ||
8 | ) | ||
9 | |||
10 | var hilMapstructureDecodeHookEmptySlice []interface{} | ||
11 | var hilMapstructureDecodeHookStringSlice []string | ||
12 | var hilMapstructureDecodeHookEmptyMap map[string]interface{} | ||
13 | |||
14 | // WeakDecode behaves in the same way as mapstructure.WeakDecode but has a | ||
15 | // DecodeHook which defeats the backward compatibility mode of mapstructure | ||
16 | // which WeakDecodes []interface{}{} into an empty map[string]interface{}. This | ||
17 | // allows us to use WeakDecode (desirable), but not fail on empty lists. | ||
18 | func WeakDecode(m interface{}, rawVal interface{}) error { | ||
19 | config := &mapstructure.DecoderConfig{ | ||
20 | DecodeHook: func(source reflect.Type, target reflect.Type, val interface{}) (interface{}, error) { | ||
21 | sliceType := reflect.TypeOf(hilMapstructureDecodeHookEmptySlice) | ||
22 | stringSliceType := reflect.TypeOf(hilMapstructureDecodeHookStringSlice) | ||
23 | mapType := reflect.TypeOf(hilMapstructureDecodeHookEmptyMap) | ||
24 | |||
25 | if (source == sliceType || source == stringSliceType) && target == mapType { | ||
26 | return nil, fmt.Errorf("Cannot convert a []interface{} into a map[string]interface{}") | ||
27 | } | ||
28 | |||
29 | return val, nil | ||
30 | }, | ||
31 | WeaklyTypedInput: true, | ||
32 | Result: rawVal, | ||
33 | } | ||
34 | |||
35 | decoder, err := mapstructure.NewDecoder(config) | ||
36 | if err != nil { | ||
37 | return err | ||
38 | } | ||
39 | |||
40 | return decoder.Decode(m) | ||
41 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/logging/logging.go b/vendor/github.com/hashicorp/terraform/helper/logging/logging.go new file mode 100644 index 0000000..433cd77 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/logging/logging.go | |||
@@ -0,0 +1,100 @@ | |||
1 | package logging | ||
2 | |||
3 | import ( | ||
4 | "io" | ||
5 | "io/ioutil" | ||
6 | "log" | ||
7 | "os" | ||
8 | "strings" | ||
9 | "syscall" | ||
10 | |||
11 | "github.com/hashicorp/logutils" | ||
12 | ) | ||
13 | |||
14 | // These are the environmental variables that determine if we log, and if | ||
15 | // we log whether or not the log should go to a file. | ||
16 | const ( | ||
17 | EnvLog = "TF_LOG" // Set to True | ||
18 | EnvLogFile = "TF_LOG_PATH" // Set to a file | ||
19 | ) | ||
20 | |||
21 | var validLevels = []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"} | ||
22 | |||
23 | // LogOutput determines where we should send logs (if anywhere) and the log level. | ||
24 | func LogOutput() (logOutput io.Writer, err error) { | ||
25 | logOutput = ioutil.Discard | ||
26 | |||
27 | logLevel := LogLevel() | ||
28 | if logLevel == "" { | ||
29 | return | ||
30 | } | ||
31 | |||
32 | logOutput = os.Stderr | ||
33 | if logPath := os.Getenv(EnvLogFile); logPath != "" { | ||
34 | var err error | ||
35 | logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) | ||
36 | if err != nil { | ||
37 | return nil, err | ||
38 | } | ||
39 | } | ||
40 | |||
41 | // This was the default since the beginning | ||
42 | logOutput = &logutils.LevelFilter{ | ||
43 | Levels: validLevels, | ||
44 | MinLevel: logutils.LogLevel(logLevel), | ||
45 | Writer: logOutput, | ||
46 | } | ||
47 | |||
48 | return | ||
49 | } | ||
50 | |||
51 | // SetOutput checks for a log destination with LogOutput, and calls | ||
52 | // log.SetOutput with the result. If LogOutput returns nil, SetOutput uses | ||
53 | // ioutil.Discard. Any error from LogOutout is fatal. | ||
54 | func SetOutput() { | ||
55 | out, err := LogOutput() | ||
56 | if err != nil { | ||
57 | log.Fatal(err) | ||
58 | } | ||
59 | |||
60 | if out == nil { | ||
61 | out = ioutil.Discard | ||
62 | } | ||
63 | |||
64 | log.SetOutput(out) | ||
65 | } | ||
66 | |||
67 | // LogLevel returns the current log level string based the environment vars | ||
68 | func LogLevel() string { | ||
69 | envLevel := os.Getenv(EnvLog) | ||
70 | if envLevel == "" { | ||
71 | return "" | ||
72 | } | ||
73 | |||
74 | logLevel := "TRACE" | ||
75 | if isValidLogLevel(envLevel) { | ||
76 | // allow following for better ux: info, Info or INFO | ||
77 | logLevel = strings.ToUpper(envLevel) | ||
78 | } else { | ||
79 | log.Printf("[WARN] Invalid log level: %q. Defaulting to level: TRACE. Valid levels are: %+v", | ||
80 | envLevel, validLevels) | ||
81 | } | ||
82 | |||
83 | return logLevel | ||
84 | } | ||
85 | |||
86 | // IsDebugOrHigher returns whether or not the current log level is debug or trace | ||
87 | func IsDebugOrHigher() bool { | ||
88 | level := string(LogLevel()) | ||
89 | return level == "DEBUG" || level == "TRACE" | ||
90 | } | ||
91 | |||
92 | func isValidLogLevel(level string) bool { | ||
93 | for _, l := range validLevels { | ||
94 | if strings.ToUpper(level) == string(l) { | ||
95 | return true | ||
96 | } | ||
97 | } | ||
98 | |||
99 | return false | ||
100 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/logging/transport.go b/vendor/github.com/hashicorp/terraform/helper/logging/transport.go new file mode 100644 index 0000000..4477924 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/logging/transport.go | |||
@@ -0,0 +1,53 @@ | |||
1 | package logging | ||
2 | |||
3 | import ( | ||
4 | "log" | ||
5 | "net/http" | ||
6 | "net/http/httputil" | ||
7 | ) | ||
8 | |||
9 | type transport struct { | ||
10 | name string | ||
11 | transport http.RoundTripper | ||
12 | } | ||
13 | |||
14 | func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { | ||
15 | if IsDebugOrHigher() { | ||
16 | reqData, err := httputil.DumpRequestOut(req, true) | ||
17 | if err == nil { | ||
18 | log.Printf("[DEBUG] "+logReqMsg, t.name, string(reqData)) | ||
19 | } else { | ||
20 | log.Printf("[ERROR] %s API Request error: %#v", t.name, err) | ||
21 | } | ||
22 | } | ||
23 | |||
24 | resp, err := t.transport.RoundTrip(req) | ||
25 | if err != nil { | ||
26 | return resp, err | ||
27 | } | ||
28 | |||
29 | if IsDebugOrHigher() { | ||
30 | respData, err := httputil.DumpResponse(resp, true) | ||
31 | if err == nil { | ||
32 | log.Printf("[DEBUG] "+logRespMsg, t.name, string(respData)) | ||
33 | } else { | ||
34 | log.Printf("[ERROR] %s API Response error: %#v", t.name, err) | ||
35 | } | ||
36 | } | ||
37 | |||
38 | return resp, nil | ||
39 | } | ||
40 | |||
41 | func NewTransport(name string, t http.RoundTripper) *transport { | ||
42 | return &transport{name, t} | ||
43 | } | ||
44 | |||
45 | const logReqMsg = `%s API Request Details: | ||
46 | ---[ REQUEST ]--------------------------------------- | ||
47 | %s | ||
48 | -----------------------------------------------------` | ||
49 | |||
50 | const logRespMsg = `%s API Response Details: | ||
51 | ---[ RESPONSE ]-------------------------------------- | ||
52 | %s | ||
53 | -----------------------------------------------------` | ||
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 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/README.md b/vendor/github.com/hashicorp/terraform/helper/schema/README.md new file mode 100644 index 0000000..28c8362 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/README.md | |||
@@ -0,0 +1,11 @@ | |||
1 | # Terraform Helper Lib: schema | ||
2 | |||
3 | The `schema` package provides a high-level interface for writing resource | ||
4 | providers for Terraform. | ||
5 | |||
6 | If you're writing a resource provider, we recommend you use this package. | ||
7 | |||
8 | The interface exposed by this package is much friendlier than trying to | ||
9 | write to the Terraform API directly. The core Terraform API is low-level | ||
10 | and built for maximum flexibility and control, whereas this library is built | ||
11 | as a framework around that to more easily write common providers. | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/backend.go b/vendor/github.com/hashicorp/terraform/helper/schema/backend.go new file mode 100644 index 0000000..a0729c0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/backend.go | |||
@@ -0,0 +1,94 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | |||
6 | "github.com/hashicorp/terraform/terraform" | ||
7 | ) | ||
8 | |||
9 | // Backend represents a partial backend.Backend implementation and simplifies | ||
10 | // the creation of configuration loading and validation. | ||
11 | // | ||
12 | // Unlike other schema structs such as Provider, this struct is meant to be | ||
13 | // embedded within your actual implementation. It provides implementations | ||
14 | // only for Input and Configure and gives you a method for accessing the | ||
15 | // configuration in the form of a ResourceData that you're expected to call | ||
16 | // from the other implementation funcs. | ||
17 | type Backend struct { | ||
18 | // Schema is the schema for the configuration of this backend. If this | ||
19 | // Backend has no configuration this can be omitted. | ||
20 | Schema map[string]*Schema | ||
21 | |||
22 | // ConfigureFunc is called to configure the backend. Use the | ||
23 | // FromContext* methods to extract information from the context. | ||
24 | // This can be nil, in which case nothing will be called but the | ||
25 | // config will still be stored. | ||
26 | ConfigureFunc func(context.Context) error | ||
27 | |||
28 | config *ResourceData | ||
29 | } | ||
30 | |||
31 | var ( | ||
32 | backendConfigKey = contextKey("backend config") | ||
33 | ) | ||
34 | |||
35 | // FromContextBackendConfig extracts a ResourceData with the configuration | ||
36 | // from the context. This should only be called by Backend functions. | ||
37 | func FromContextBackendConfig(ctx context.Context) *ResourceData { | ||
38 | return ctx.Value(backendConfigKey).(*ResourceData) | ||
39 | } | ||
40 | |||
41 | func (b *Backend) Input( | ||
42 | input terraform.UIInput, | ||
43 | c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { | ||
44 | if b == nil { | ||
45 | return c, nil | ||
46 | } | ||
47 | |||
48 | return schemaMap(b.Schema).Input(input, c) | ||
49 | } | ||
50 | |||
51 | func (b *Backend) Validate(c *terraform.ResourceConfig) ([]string, []error) { | ||
52 | if b == nil { | ||
53 | return nil, nil | ||
54 | } | ||
55 | |||
56 | return schemaMap(b.Schema).Validate(c) | ||
57 | } | ||
58 | |||
59 | func (b *Backend) Configure(c *terraform.ResourceConfig) error { | ||
60 | if b == nil { | ||
61 | return nil | ||
62 | } | ||
63 | |||
64 | sm := schemaMap(b.Schema) | ||
65 | |||
66 | // Get a ResourceData for this configuration. To do this, we actually | ||
67 | // generate an intermediary "diff" although that is never exposed. | ||
68 | diff, err := sm.Diff(nil, c) | ||
69 | if err != nil { | ||
70 | return err | ||
71 | } | ||
72 | |||
73 | data, err := sm.Data(nil, diff) | ||
74 | if err != nil { | ||
75 | return err | ||
76 | } | ||
77 | b.config = data | ||
78 | |||
79 | if b.ConfigureFunc != nil { | ||
80 | err = b.ConfigureFunc(context.WithValue( | ||
81 | context.Background(), backendConfigKey, data)) | ||
82 | if err != nil { | ||
83 | return err | ||
84 | } | ||
85 | } | ||
86 | |||
87 | return nil | ||
88 | } | ||
89 | |||
90 | // Config returns the configuration. This is available after Configure is | ||
91 | // called. | ||
92 | func (b *Backend) Config() *ResourceData { | ||
93 | return b.config | ||
94 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go b/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go new file mode 100644 index 0000000..5a03d2d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go | |||
@@ -0,0 +1,59 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | ) | ||
6 | |||
7 | // DataSourceResourceShim takes a Resource instance describing a data source | ||
8 | // (with a Read implementation and a Schema, at least) and returns a new | ||
9 | // Resource instance with additional Create and Delete implementations that | ||
10 | // allow the data source to be used as a resource. | ||
11 | // | ||
12 | // This is a backward-compatibility layer for data sources that were formerly | ||
13 | // read-only resources before the data source concept was added. It should not | ||
14 | // be used for any *new* data sources. | ||
15 | // | ||
16 | // The Read function for the data source *must* call d.SetId with a non-empty | ||
17 | // id in order for this shim to function as expected. | ||
18 | // | ||
19 | // The provided Resource instance, and its schema, will be modified in-place | ||
20 | // to make it suitable for use as a full resource. | ||
21 | func DataSourceResourceShim(name string, dataSource *Resource) *Resource { | ||
22 | // Recursively, in-place adjust the schema so that it has ForceNew | ||
23 | // on any user-settable resource. | ||
24 | dataSourceResourceShimAdjustSchema(dataSource.Schema) | ||
25 | |||
26 | dataSource.Create = CreateFunc(dataSource.Read) | ||
27 | dataSource.Delete = func(d *ResourceData, meta interface{}) error { | ||
28 | d.SetId("") | ||
29 | return nil | ||
30 | } | ||
31 | dataSource.Update = nil // should already be nil, but let's make sure | ||
32 | |||
33 | // FIXME: Link to some further docs either on the website or in the | ||
34 | // changelog, once such a thing exists. | ||
35 | dataSource.deprecationMessage = fmt.Sprintf( | ||
36 | "using %s as a resource is deprecated; consider using the data source instead", | ||
37 | name, | ||
38 | ) | ||
39 | |||
40 | return dataSource | ||
41 | } | ||
42 | |||
43 | func dataSourceResourceShimAdjustSchema(schema map[string]*Schema) { | ||
44 | for _, s := range schema { | ||
45 | // If the attribute is configurable then it must be ForceNew, | ||
46 | // since we have no Update implementation. | ||
47 | if s.Required || s.Optional { | ||
48 | s.ForceNew = true | ||
49 | } | ||
50 | |||
51 | // If the attribute is a nested resource, we need to recursively | ||
52 | // apply these same adjustments to it. | ||
53 | if s.Elem != nil { | ||
54 | if r, ok := s.Elem.(*Resource); ok { | ||
55 | dataSourceResourceShimAdjustSchema(r.Schema) | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/equal.go b/vendor/github.com/hashicorp/terraform/helper/schema/equal.go new file mode 100644 index 0000000..d5e20e0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/equal.go | |||
@@ -0,0 +1,6 @@ | |||
1 | package schema | ||
2 | |||
3 | // Equal is an interface that checks for deep equality between two objects. | ||
4 | type Equal interface { | ||
5 | Equal(interface{}) bool | ||
6 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go new file mode 100644 index 0000000..1660a67 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go | |||
@@ -0,0 +1,334 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strconv" | ||
6 | ) | ||
7 | |||
8 | // FieldReaders are responsible for decoding fields out of data into | ||
9 | // the proper typed representation. ResourceData uses this to query data | ||
10 | // out of multiple sources: config, state, diffs, etc. | ||
11 | type FieldReader interface { | ||
12 | ReadField([]string) (FieldReadResult, error) | ||
13 | } | ||
14 | |||
15 | // FieldReadResult encapsulates all the resulting data from reading | ||
16 | // a field. | ||
17 | type FieldReadResult struct { | ||
18 | // Value is the actual read value. NegValue is the _negative_ value | ||
19 | // or the items that should be removed (if they existed). NegValue | ||
20 | // doesn't make sense for primitives but is important for any | ||
21 | // container types such as maps, sets, lists. | ||
22 | Value interface{} | ||
23 | ValueProcessed interface{} | ||
24 | |||
25 | // Exists is true if the field was found in the data. False means | ||
26 | // it wasn't found if there was no error. | ||
27 | Exists bool | ||
28 | |||
29 | // Computed is true if the field was found but the value | ||
30 | // is computed. | ||
31 | Computed bool | ||
32 | } | ||
33 | |||
34 | // ValueOrZero returns the value of this result or the zero value of the | ||
35 | // schema type, ensuring a consistent non-nil return value. | ||
36 | func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} { | ||
37 | if r.Value != nil { | ||
38 | return r.Value | ||
39 | } | ||
40 | |||
41 | return s.ZeroValue() | ||
42 | } | ||
43 | |||
44 | // addrToSchema finds the final element schema for the given address | ||
45 | // and the given schema. It returns all the schemas that led to the final | ||
46 | // schema. These are in order of the address (out to in). | ||
47 | func addrToSchema(addr []string, schemaMap map[string]*Schema) []*Schema { | ||
48 | current := &Schema{ | ||
49 | Type: typeObject, | ||
50 | Elem: schemaMap, | ||
51 | } | ||
52 | |||
53 | // If we aren't given an address, then the user is requesting the | ||
54 | // full object, so we return the special value which is the full object. | ||
55 | if len(addr) == 0 { | ||
56 | return []*Schema{current} | ||
57 | } | ||
58 | |||
59 | result := make([]*Schema, 0, len(addr)) | ||
60 | for len(addr) > 0 { | ||
61 | k := addr[0] | ||
62 | addr = addr[1:] | ||
63 | |||
64 | REPEAT: | ||
65 | // We want to trim off the first "typeObject" since its not a | ||
66 | // real lookup that people do. i.e. []string{"foo"} in a structure | ||
67 | // isn't {typeObject, typeString}, its just a {typeString}. | ||
68 | if len(result) > 0 || current.Type != typeObject { | ||
69 | result = append(result, current) | ||
70 | } | ||
71 | |||
72 | switch t := current.Type; t { | ||
73 | case TypeBool, TypeInt, TypeFloat, TypeString: | ||
74 | if len(addr) > 0 { | ||
75 | return nil | ||
76 | } | ||
77 | case TypeList, TypeSet: | ||
78 | isIndex := len(addr) > 0 && addr[0] == "#" | ||
79 | |||
80 | switch v := current.Elem.(type) { | ||
81 | case *Resource: | ||
82 | current = &Schema{ | ||
83 | Type: typeObject, | ||
84 | Elem: v.Schema, | ||
85 | } | ||
86 | case *Schema: | ||
87 | current = v | ||
88 | case ValueType: | ||
89 | current = &Schema{Type: v} | ||
90 | default: | ||
91 | // we may not know the Elem type and are just looking for the | ||
92 | // index | ||
93 | if isIndex { | ||
94 | break | ||
95 | } | ||
96 | |||
97 | if len(addr) == 0 { | ||
98 | // we've processed the address, so return what we've | ||
99 | // collected | ||
100 | return result | ||
101 | } | ||
102 | |||
103 | if len(addr) == 1 { | ||
104 | if _, err := strconv.Atoi(addr[0]); err == nil { | ||
105 | // we're indexing a value without a schema. This can | ||
106 | // happen if the list is nested in another schema type. | ||
107 | // Default to a TypeString like we do with a map | ||
108 | current = &Schema{Type: TypeString} | ||
109 | break | ||
110 | } | ||
111 | } | ||
112 | |||
113 | return nil | ||
114 | } | ||
115 | |||
116 | // If we only have one more thing and the next thing | ||
117 | // is a #, then we're accessing the index which is always | ||
118 | // an int. | ||
119 | if isIndex { | ||
120 | current = &Schema{Type: TypeInt} | ||
121 | break | ||
122 | } | ||
123 | |||
124 | case TypeMap: | ||
125 | if len(addr) > 0 { | ||
126 | switch v := current.Elem.(type) { | ||
127 | case ValueType: | ||
128 | current = &Schema{Type: v} | ||
129 | default: | ||
130 | // maps default to string values. This is all we can have | ||
131 | // if this is nested in another list or map. | ||
132 | current = &Schema{Type: TypeString} | ||
133 | } | ||
134 | } | ||
135 | case typeObject: | ||
136 | // If we're already in the object, then we want to handle Sets | ||
137 | // and Lists specially. Basically, their next key is the lookup | ||
138 | // key (the set value or the list element). For these scenarios, | ||
139 | // we just want to skip it and move to the next element if there | ||
140 | // is one. | ||
141 | if len(result) > 0 { | ||
142 | lastType := result[len(result)-2].Type | ||
143 | if lastType == TypeSet || lastType == TypeList { | ||
144 | if len(addr) == 0 { | ||
145 | break | ||
146 | } | ||
147 | |||
148 | k = addr[0] | ||
149 | addr = addr[1:] | ||
150 | } | ||
151 | } | ||
152 | |||
153 | m := current.Elem.(map[string]*Schema) | ||
154 | val, ok := m[k] | ||
155 | if !ok { | ||
156 | return nil | ||
157 | } | ||
158 | |||
159 | current = val | ||
160 | goto REPEAT | ||
161 | } | ||
162 | } | ||
163 | |||
164 | return result | ||
165 | } | ||
166 | |||
167 | // readListField is a generic method for reading a list field out of a | ||
168 | // a FieldReader. It does this based on the assumption that there is a key | ||
169 | // "foo.#" for a list "foo" and that the indexes are "foo.0", "foo.1", etc. | ||
170 | // after that point. | ||
171 | func readListField( | ||
172 | r FieldReader, addr []string, schema *Schema) (FieldReadResult, error) { | ||
173 | addrPadded := make([]string, len(addr)+1) | ||
174 | copy(addrPadded, addr) | ||
175 | addrPadded[len(addrPadded)-1] = "#" | ||
176 | |||
177 | // Get the number of elements in the list | ||
178 | countResult, err := r.ReadField(addrPadded) | ||
179 | if err != nil { | ||
180 | return FieldReadResult{}, err | ||
181 | } | ||
182 | if !countResult.Exists { | ||
183 | // No count, means we have no list | ||
184 | countResult.Value = 0 | ||
185 | } | ||
186 | |||
187 | // If we have an empty list, then return an empty list | ||
188 | if countResult.Computed || countResult.Value.(int) == 0 { | ||
189 | return FieldReadResult{ | ||
190 | Value: []interface{}{}, | ||
191 | Exists: countResult.Exists, | ||
192 | Computed: countResult.Computed, | ||
193 | }, nil | ||
194 | } | ||
195 | |||
196 | // Go through each count, and get the item value out of it | ||
197 | result := make([]interface{}, countResult.Value.(int)) | ||
198 | for i, _ := range result { | ||
199 | is := strconv.FormatInt(int64(i), 10) | ||
200 | addrPadded[len(addrPadded)-1] = is | ||
201 | rawResult, err := r.ReadField(addrPadded) | ||
202 | if err != nil { | ||
203 | return FieldReadResult{}, err | ||
204 | } | ||
205 | if !rawResult.Exists { | ||
206 | // This should never happen, because by the time the data | ||
207 | // gets to the FieldReaders, all the defaults should be set by | ||
208 | // Schema. | ||
209 | rawResult.Value = nil | ||
210 | } | ||
211 | |||
212 | result[i] = rawResult.Value | ||
213 | } | ||
214 | |||
215 | return FieldReadResult{ | ||
216 | Value: result, | ||
217 | Exists: true, | ||
218 | }, nil | ||
219 | } | ||
220 | |||
221 | // readObjectField is a generic method for reading objects out of FieldReaders | ||
222 | // based on the assumption that building an address of []string{k, FIELD} | ||
223 | // will result in the proper field data. | ||
224 | func readObjectField( | ||
225 | r FieldReader, | ||
226 | addr []string, | ||
227 | schema map[string]*Schema) (FieldReadResult, error) { | ||
228 | result := make(map[string]interface{}) | ||
229 | exists := false | ||
230 | for field, s := range schema { | ||
231 | addrRead := make([]string, len(addr), len(addr)+1) | ||
232 | copy(addrRead, addr) | ||
233 | addrRead = append(addrRead, field) | ||
234 | rawResult, err := r.ReadField(addrRead) | ||
235 | if err != nil { | ||
236 | return FieldReadResult{}, err | ||
237 | } | ||
238 | if rawResult.Exists { | ||
239 | exists = true | ||
240 | } | ||
241 | |||
242 | result[field] = rawResult.ValueOrZero(s) | ||
243 | } | ||
244 | |||
245 | return FieldReadResult{ | ||
246 | Value: result, | ||
247 | Exists: exists, | ||
248 | }, nil | ||
249 | } | ||
250 | |||
251 | // convert map values to the proper primitive type based on schema.Elem | ||
252 | func mapValuesToPrimitive(m map[string]interface{}, schema *Schema) error { | ||
253 | |||
254 | elemType := TypeString | ||
255 | if et, ok := schema.Elem.(ValueType); ok { | ||
256 | elemType = et | ||
257 | } | ||
258 | |||
259 | switch elemType { | ||
260 | case TypeInt, TypeFloat, TypeBool: | ||
261 | for k, v := range m { | ||
262 | vs, ok := v.(string) | ||
263 | if !ok { | ||
264 | continue | ||
265 | } | ||
266 | |||
267 | v, err := stringToPrimitive(vs, false, &Schema{Type: elemType}) | ||
268 | if err != nil { | ||
269 | return err | ||
270 | } | ||
271 | |||
272 | m[k] = v | ||
273 | } | ||
274 | } | ||
275 | return nil | ||
276 | } | ||
277 | |||
278 | func stringToPrimitive( | ||
279 | value string, computed bool, schema *Schema) (interface{}, error) { | ||
280 | var returnVal interface{} | ||
281 | switch schema.Type { | ||
282 | case TypeBool: | ||
283 | if value == "" { | ||
284 | returnVal = false | ||
285 | break | ||
286 | } | ||
287 | if computed { | ||
288 | break | ||
289 | } | ||
290 | |||
291 | v, err := strconv.ParseBool(value) | ||
292 | if err != nil { | ||
293 | return nil, err | ||
294 | } | ||
295 | |||
296 | returnVal = v | ||
297 | case TypeFloat: | ||
298 | if value == "" { | ||
299 | returnVal = 0.0 | ||
300 | break | ||
301 | } | ||
302 | if computed { | ||
303 | break | ||
304 | } | ||
305 | |||
306 | v, err := strconv.ParseFloat(value, 64) | ||
307 | if err != nil { | ||
308 | return nil, err | ||
309 | } | ||
310 | |||
311 | returnVal = v | ||
312 | case TypeInt: | ||
313 | if value == "" { | ||
314 | returnVal = 0 | ||
315 | break | ||
316 | } | ||
317 | if computed { | ||
318 | break | ||
319 | } | ||
320 | |||
321 | v, err := strconv.ParseInt(value, 0, 0) | ||
322 | if err != nil { | ||
323 | return nil, err | ||
324 | } | ||
325 | |||
326 | returnVal = int(v) | ||
327 | case TypeString: | ||
328 | returnVal = value | ||
329 | default: | ||
330 | panic(fmt.Sprintf("Unknown type: %s", schema.Type)) | ||
331 | } | ||
332 | |||
333 | return returnVal, nil | ||
334 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go new file mode 100644 index 0000000..f958bbc --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go | |||
@@ -0,0 +1,333 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strconv" | ||
6 | "strings" | ||
7 | "sync" | ||
8 | |||
9 | "github.com/hashicorp/terraform/terraform" | ||
10 | "github.com/mitchellh/mapstructure" | ||
11 | ) | ||
12 | |||
13 | // ConfigFieldReader reads fields out of an untyped map[string]string to the | ||
14 | // best of its ability. It also applies defaults from the Schema. (The other | ||
15 | // field readers do not need default handling because they source fully | ||
16 | // populated data structures.) | ||
17 | type ConfigFieldReader struct { | ||
18 | Config *terraform.ResourceConfig | ||
19 | Schema map[string]*Schema | ||
20 | |||
21 | indexMaps map[string]map[string]int | ||
22 | once sync.Once | ||
23 | } | ||
24 | |||
25 | func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) { | ||
26 | r.once.Do(func() { r.indexMaps = make(map[string]map[string]int) }) | ||
27 | return r.readField(address, false) | ||
28 | } | ||
29 | |||
30 | func (r *ConfigFieldReader) readField( | ||
31 | address []string, nested bool) (FieldReadResult, error) { | ||
32 | schemaList := addrToSchema(address, r.Schema) | ||
33 | if len(schemaList) == 0 { | ||
34 | return FieldReadResult{}, nil | ||
35 | } | ||
36 | |||
37 | if !nested { | ||
38 | // If we have a set anywhere in the address, then we need to | ||
39 | // read that set out in order and actually replace that part of | ||
40 | // the address with the real list index. i.e. set.50 might actually | ||
41 | // map to set.12 in the config, since it is in list order in the | ||
42 | // config, not indexed by set value. | ||
43 | for i, v := range schemaList { | ||
44 | // Sets are the only thing that cause this issue. | ||
45 | if v.Type != TypeSet { | ||
46 | continue | ||
47 | } | ||
48 | |||
49 | // If we're at the end of the list, then we don't have to worry | ||
50 | // about this because we're just requesting the whole set. | ||
51 | if i == len(schemaList)-1 { | ||
52 | continue | ||
53 | } | ||
54 | |||
55 | // If we're looking for the count, then ignore... | ||
56 | if address[i+1] == "#" { | ||
57 | continue | ||
58 | } | ||
59 | |||
60 | indexMap, ok := r.indexMaps[strings.Join(address[:i+1], ".")] | ||
61 | if !ok { | ||
62 | // Get the set so we can get the index map that tells us the | ||
63 | // mapping of the hash code to the list index | ||
64 | _, err := r.readSet(address[:i+1], v) | ||
65 | if err != nil { | ||
66 | return FieldReadResult{}, err | ||
67 | } | ||
68 | indexMap = r.indexMaps[strings.Join(address[:i+1], ".")] | ||
69 | } | ||
70 | |||
71 | index, ok := indexMap[address[i+1]] | ||
72 | if !ok { | ||
73 | return FieldReadResult{}, nil | ||
74 | } | ||
75 | |||
76 | address[i+1] = strconv.FormatInt(int64(index), 10) | ||
77 | } | ||
78 | } | ||
79 | |||
80 | k := strings.Join(address, ".") | ||
81 | schema := schemaList[len(schemaList)-1] | ||
82 | |||
83 | // If we're getting the single element of a promoted list, then | ||
84 | // check to see if we have a single element we need to promote. | ||
85 | if address[len(address)-1] == "0" && len(schemaList) > 1 { | ||
86 | lastSchema := schemaList[len(schemaList)-2] | ||
87 | if lastSchema.Type == TypeList && lastSchema.PromoteSingle { | ||
88 | k := strings.Join(address[:len(address)-1], ".") | ||
89 | result, err := r.readPrimitive(k, schema) | ||
90 | if err == nil { | ||
91 | return result, nil | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | |||
96 | switch schema.Type { | ||
97 | case TypeBool, TypeFloat, TypeInt, TypeString: | ||
98 | return r.readPrimitive(k, schema) | ||
99 | case TypeList: | ||
100 | // If we support promotion then we first check if we have a lone | ||
101 | // value that we must promote. | ||
102 | // a value that is alone. | ||
103 | if schema.PromoteSingle { | ||
104 | result, err := r.readPrimitive(k, schema.Elem.(*Schema)) | ||
105 | if err == nil && result.Exists { | ||
106 | result.Value = []interface{}{result.Value} | ||
107 | return result, nil | ||
108 | } | ||
109 | } | ||
110 | |||
111 | return readListField(&nestedConfigFieldReader{r}, address, schema) | ||
112 | case TypeMap: | ||
113 | return r.readMap(k, schema) | ||
114 | case TypeSet: | ||
115 | return r.readSet(address, schema) | ||
116 | case typeObject: | ||
117 | return readObjectField( | ||
118 | &nestedConfigFieldReader{r}, | ||
119 | address, schema.Elem.(map[string]*Schema)) | ||
120 | default: | ||
121 | panic(fmt.Sprintf("Unknown type: %s", schema.Type)) | ||
122 | } | ||
123 | } | ||
124 | |||
125 | func (r *ConfigFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { | ||
126 | // We want both the raw value and the interpolated. We use the interpolated | ||
127 | // to store actual values and we use the raw one to check for | ||
128 | // computed keys. Actual values are obtained in the switch, depending on | ||
129 | // the type of the raw value. | ||
130 | mraw, ok := r.Config.GetRaw(k) | ||
131 | if !ok { | ||
132 | // check if this is from an interpolated field by seeing if it exists | ||
133 | // in the config | ||
134 | _, ok := r.Config.Get(k) | ||
135 | if !ok { | ||
136 | // this really doesn't exist | ||
137 | return FieldReadResult{}, nil | ||
138 | } | ||
139 | |||
140 | // We couldn't fetch the value from a nested data structure, so treat the | ||
141 | // raw value as an interpolation string. The mraw value is only used | ||
142 | // for the type switch below. | ||
143 | mraw = "${INTERPOLATED}" | ||
144 | } | ||
145 | |||
146 | result := make(map[string]interface{}) | ||
147 | computed := false | ||
148 | switch m := mraw.(type) { | ||
149 | case string: | ||
150 | // This is a map which has come out of an interpolated variable, so we | ||
151 | // can just get the value directly from config. Values cannot be computed | ||
152 | // currently. | ||
153 | v, _ := r.Config.Get(k) | ||
154 | |||
155 | // If this isn't a map[string]interface, it must be computed. | ||
156 | mapV, ok := v.(map[string]interface{}) | ||
157 | if !ok { | ||
158 | return FieldReadResult{ | ||
159 | Exists: true, | ||
160 | Computed: true, | ||
161 | }, nil | ||
162 | } | ||
163 | |||
164 | // Otherwise we can proceed as usual. | ||
165 | for i, iv := range mapV { | ||
166 | result[i] = iv | ||
167 | } | ||
168 | case []interface{}: | ||
169 | for i, innerRaw := range m { | ||
170 | for ik := range innerRaw.(map[string]interface{}) { | ||
171 | key := fmt.Sprintf("%s.%d.%s", k, i, ik) | ||
172 | if r.Config.IsComputed(key) { | ||
173 | computed = true | ||
174 | break | ||
175 | } | ||
176 | |||
177 | v, _ := r.Config.Get(key) | ||
178 | result[ik] = v | ||
179 | } | ||
180 | } | ||
181 | case []map[string]interface{}: | ||
182 | for i, innerRaw := range m { | ||
183 | for ik := range innerRaw { | ||
184 | key := fmt.Sprintf("%s.%d.%s", k, i, ik) | ||
185 | if r.Config.IsComputed(key) { | ||
186 | computed = true | ||
187 | break | ||
188 | } | ||
189 | |||
190 | v, _ := r.Config.Get(key) | ||
191 | result[ik] = v | ||
192 | } | ||
193 | } | ||
194 | case map[string]interface{}: | ||
195 | for ik := range m { | ||
196 | key := fmt.Sprintf("%s.%s", k, ik) | ||
197 | if r.Config.IsComputed(key) { | ||
198 | computed = true | ||
199 | break | ||
200 | } | ||
201 | |||
202 | v, _ := r.Config.Get(key) | ||
203 | result[ik] = v | ||
204 | } | ||
205 | default: | ||
206 | panic(fmt.Sprintf("unknown type: %#v", mraw)) | ||
207 | } | ||
208 | |||
209 | err := mapValuesToPrimitive(result, schema) | ||
210 | if err != nil { | ||
211 | return FieldReadResult{}, nil | ||
212 | } | ||
213 | |||
214 | var value interface{} | ||
215 | if !computed { | ||
216 | value = result | ||
217 | } | ||
218 | |||
219 | return FieldReadResult{ | ||
220 | Value: value, | ||
221 | Exists: true, | ||
222 | Computed: computed, | ||
223 | }, nil | ||
224 | } | ||
225 | |||
226 | func (r *ConfigFieldReader) readPrimitive( | ||
227 | k string, schema *Schema) (FieldReadResult, error) { | ||
228 | raw, ok := r.Config.Get(k) | ||
229 | if !ok { | ||
230 | // Nothing in config, but we might still have a default from the schema | ||
231 | var err error | ||
232 | raw, err = schema.DefaultValue() | ||
233 | if err != nil { | ||
234 | return FieldReadResult{}, fmt.Errorf("%s, error loading default: %s", k, err) | ||
235 | } | ||
236 | |||
237 | if raw == nil { | ||
238 | return FieldReadResult{}, nil | ||
239 | } | ||
240 | } | ||
241 | |||
242 | var result string | ||
243 | if err := mapstructure.WeakDecode(raw, &result); err != nil { | ||
244 | return FieldReadResult{}, err | ||
245 | } | ||
246 | |||
247 | computed := r.Config.IsComputed(k) | ||
248 | returnVal, err := stringToPrimitive(result, computed, schema) | ||
249 | if err != nil { | ||
250 | return FieldReadResult{}, err | ||
251 | } | ||
252 | |||
253 | return FieldReadResult{ | ||
254 | Value: returnVal, | ||
255 | Exists: true, | ||
256 | Computed: computed, | ||
257 | }, nil | ||
258 | } | ||
259 | |||
260 | func (r *ConfigFieldReader) readSet( | ||
261 | address []string, schema *Schema) (FieldReadResult, error) { | ||
262 | indexMap := make(map[string]int) | ||
263 | // Create the set that will be our result | ||
264 | set := schema.ZeroValue().(*Set) | ||
265 | |||
266 | raw, err := readListField(&nestedConfigFieldReader{r}, address, schema) | ||
267 | if err != nil { | ||
268 | return FieldReadResult{}, err | ||
269 | } | ||
270 | if !raw.Exists { | ||
271 | return FieldReadResult{Value: set}, nil | ||
272 | } | ||
273 | |||
274 | // If the list is computed, the set is necessarilly computed | ||
275 | if raw.Computed { | ||
276 | return FieldReadResult{ | ||
277 | Value: set, | ||
278 | Exists: true, | ||
279 | Computed: raw.Computed, | ||
280 | }, nil | ||
281 | } | ||
282 | |||
283 | // Build up the set from the list elements | ||
284 | for i, v := range raw.Value.([]interface{}) { | ||
285 | // Check if any of the keys in this item are computed | ||
286 | computed := r.hasComputedSubKeys( | ||
287 | fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema) | ||
288 | |||
289 | code := set.add(v, computed) | ||
290 | indexMap[code] = i | ||
291 | } | ||
292 | |||
293 | r.indexMaps[strings.Join(address, ".")] = indexMap | ||
294 | |||
295 | return FieldReadResult{ | ||
296 | Value: set, | ||
297 | Exists: true, | ||
298 | }, nil | ||
299 | } | ||
300 | |||
301 | // hasComputedSubKeys walks through a schema and returns whether or not the | ||
302 | // given key contains any subkeys that are computed. | ||
303 | func (r *ConfigFieldReader) hasComputedSubKeys(key string, schema *Schema) bool { | ||
304 | prefix := key + "." | ||
305 | |||
306 | switch t := schema.Elem.(type) { | ||
307 | case *Resource: | ||
308 | for k, schema := range t.Schema { | ||
309 | if r.Config.IsComputed(prefix + k) { | ||
310 | return true | ||
311 | } | ||
312 | |||
313 | if r.hasComputedSubKeys(prefix+k, schema) { | ||
314 | return true | ||
315 | } | ||
316 | } | ||
317 | } | ||
318 | |||
319 | return false | ||
320 | } | ||
321 | |||
322 | // nestedConfigFieldReader is a funny little thing that just wraps a | ||
323 | // ConfigFieldReader to call readField when ReadField is called so that | ||
324 | // we don't recalculate the set rewrites in the address, which leads to | ||
325 | // an infinite loop. | ||
326 | type nestedConfigFieldReader struct { | ||
327 | Reader *ConfigFieldReader | ||
328 | } | ||
329 | |||
330 | func (r *nestedConfigFieldReader) ReadField( | ||
331 | address []string) (FieldReadResult, error) { | ||
332 | return r.Reader.readField(address, true) | ||
333 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go new file mode 100644 index 0000000..16bbae2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go | |||
@@ -0,0 +1,208 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strings" | ||
6 | |||
7 | "github.com/hashicorp/terraform/terraform" | ||
8 | "github.com/mitchellh/mapstructure" | ||
9 | ) | ||
10 | |||
11 | // DiffFieldReader reads fields out of a diff structures. | ||
12 | // | ||
13 | // It also requires access to a Reader that reads fields from the structure | ||
14 | // that the diff was derived from. This is usually the state. This is required | ||
15 | // because a diff on its own doesn't have complete data about full objects | ||
16 | // such as maps. | ||
17 | // | ||
18 | // The Source MUST be the data that the diff was derived from. If it isn't, | ||
19 | // the behavior of this struct is undefined. | ||
20 | // | ||
21 | // Reading fields from a DiffFieldReader is identical to reading from | ||
22 | // Source except the diff will be applied to the end result. | ||
23 | // | ||
24 | // The "Exists" field on the result will be set to true if the complete | ||
25 | // field exists whether its from the source, diff, or a combination of both. | ||
26 | // It cannot be determined whether a retrieved value is composed of | ||
27 | // diff elements. | ||
28 | type DiffFieldReader struct { | ||
29 | Diff *terraform.InstanceDiff | ||
30 | Source FieldReader | ||
31 | Schema map[string]*Schema | ||
32 | } | ||
33 | |||
34 | func (r *DiffFieldReader) ReadField(address []string) (FieldReadResult, error) { | ||
35 | schemaList := addrToSchema(address, r.Schema) | ||
36 | if len(schemaList) == 0 { | ||
37 | return FieldReadResult{}, nil | ||
38 | } | ||
39 | |||
40 | schema := schemaList[len(schemaList)-1] | ||
41 | switch schema.Type { | ||
42 | case TypeBool, TypeInt, TypeFloat, TypeString: | ||
43 | return r.readPrimitive(address, schema) | ||
44 | case TypeList: | ||
45 | return readListField(r, address, schema) | ||
46 | case TypeMap: | ||
47 | return r.readMap(address, schema) | ||
48 | case TypeSet: | ||
49 | return r.readSet(address, schema) | ||
50 | case typeObject: | ||
51 | return readObjectField(r, address, schema.Elem.(map[string]*Schema)) | ||
52 | default: | ||
53 | panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) | ||
54 | } | ||
55 | } | ||
56 | |||
57 | func (r *DiffFieldReader) readMap( | ||
58 | address []string, schema *Schema) (FieldReadResult, error) { | ||
59 | result := make(map[string]interface{}) | ||
60 | resultSet := false | ||
61 | |||
62 | // First read the map from the underlying source | ||
63 | source, err := r.Source.ReadField(address) | ||
64 | if err != nil { | ||
65 | return FieldReadResult{}, err | ||
66 | } | ||
67 | if source.Exists { | ||
68 | result = source.Value.(map[string]interface{}) | ||
69 | resultSet = true | ||
70 | } | ||
71 | |||
72 | // Next, read all the elements we have in our diff, and apply | ||
73 | // the diff to our result. | ||
74 | prefix := strings.Join(address, ".") + "." | ||
75 | for k, v := range r.Diff.Attributes { | ||
76 | if !strings.HasPrefix(k, prefix) { | ||
77 | continue | ||
78 | } | ||
79 | if strings.HasPrefix(k, prefix+"%") { | ||
80 | // Ignore the count field | ||
81 | continue | ||
82 | } | ||
83 | |||
84 | resultSet = true | ||
85 | |||
86 | k = k[len(prefix):] | ||
87 | if v.NewRemoved { | ||
88 | delete(result, k) | ||
89 | continue | ||
90 | } | ||
91 | |||
92 | result[k] = v.New | ||
93 | } | ||
94 | |||
95 | err = mapValuesToPrimitive(result, schema) | ||
96 | if err != nil { | ||
97 | return FieldReadResult{}, nil | ||
98 | } | ||
99 | |||
100 | var resultVal interface{} | ||
101 | if resultSet { | ||
102 | resultVal = result | ||
103 | } | ||
104 | |||
105 | return FieldReadResult{ | ||
106 | Value: resultVal, | ||
107 | Exists: resultSet, | ||
108 | }, nil | ||
109 | } | ||
110 | |||
111 | func (r *DiffFieldReader) readPrimitive( | ||
112 | address []string, schema *Schema) (FieldReadResult, error) { | ||
113 | result, err := r.Source.ReadField(address) | ||
114 | if err != nil { | ||
115 | return FieldReadResult{}, err | ||
116 | } | ||
117 | |||
118 | attrD, ok := r.Diff.Attributes[strings.Join(address, ".")] | ||
119 | if !ok { | ||
120 | return result, nil | ||
121 | } | ||
122 | |||
123 | var resultVal string | ||
124 | if !attrD.NewComputed { | ||
125 | resultVal = attrD.New | ||
126 | if attrD.NewExtra != nil { | ||
127 | result.ValueProcessed = resultVal | ||
128 | if err := mapstructure.WeakDecode(attrD.NewExtra, &resultVal); err != nil { | ||
129 | return FieldReadResult{}, err | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | |||
134 | result.Computed = attrD.NewComputed | ||
135 | result.Exists = true | ||
136 | result.Value, err = stringToPrimitive(resultVal, false, schema) | ||
137 | if err != nil { | ||
138 | return FieldReadResult{}, err | ||
139 | } | ||
140 | |||
141 | return result, nil | ||
142 | } | ||
143 | |||
144 | func (r *DiffFieldReader) readSet( | ||
145 | address []string, schema *Schema) (FieldReadResult, error) { | ||
146 | prefix := strings.Join(address, ".") + "." | ||
147 | |||
148 | // Create the set that will be our result | ||
149 | set := schema.ZeroValue().(*Set) | ||
150 | |||
151 | // Go through the map and find all the set items | ||
152 | for k, d := range r.Diff.Attributes { | ||
153 | if d.NewRemoved { | ||
154 | // If the field is removed, we always ignore it | ||
155 | continue | ||
156 | } | ||
157 | if !strings.HasPrefix(k, prefix) { | ||
158 | continue | ||
159 | } | ||
160 | if strings.HasSuffix(k, "#") { | ||
161 | // Ignore any count field | ||
162 | continue | ||
163 | } | ||
164 | |||
165 | // Split the key, since it might be a sub-object like "idx.field" | ||
166 | parts := strings.Split(k[len(prefix):], ".") | ||
167 | idx := parts[0] | ||
168 | |||
169 | raw, err := r.ReadField(append(address, idx)) | ||
170 | if err != nil { | ||
171 | return FieldReadResult{}, err | ||
172 | } | ||
173 | if !raw.Exists { | ||
174 | // This shouldn't happen because we just verified it does exist | ||
175 | panic("missing field in set: " + k + "." + idx) | ||
176 | } | ||
177 | |||
178 | set.Add(raw.Value) | ||
179 | } | ||
180 | |||
181 | // Determine if the set "exists". It exists if there are items or if | ||
182 | // the diff explicitly wanted it empty. | ||
183 | exists := set.Len() > 0 | ||
184 | if !exists { | ||
185 | // We could check if the diff value is "0" here but I think the | ||
186 | // existence of "#" on its own is enough to show it existed. This | ||
187 | // protects us in the future from the zero value changing from | ||
188 | // "0" to "" breaking us (if that were to happen). | ||
189 | if _, ok := r.Diff.Attributes[prefix+"#"]; ok { | ||
190 | exists = true | ||
191 | } | ||
192 | } | ||
193 | |||
194 | if !exists { | ||
195 | result, err := r.Source.ReadField(address) | ||
196 | if err != nil { | ||
197 | return FieldReadResult{}, err | ||
198 | } | ||
199 | if result.Exists { | ||
200 | return result, nil | ||
201 | } | ||
202 | } | ||
203 | |||
204 | return FieldReadResult{ | ||
205 | Value: set, | ||
206 | Exists: exists, | ||
207 | }, nil | ||
208 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go new file mode 100644 index 0000000..9533981 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go | |||
@@ -0,0 +1,232 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "strings" | ||
6 | ) | ||
7 | |||
8 | // MapFieldReader reads fields out of an untyped map[string]string to | ||
9 | // the best of its ability. | ||
10 | type MapFieldReader struct { | ||
11 | Map MapReader | ||
12 | Schema map[string]*Schema | ||
13 | } | ||
14 | |||
15 | func (r *MapFieldReader) ReadField(address []string) (FieldReadResult, error) { | ||
16 | k := strings.Join(address, ".") | ||
17 | schemaList := addrToSchema(address, r.Schema) | ||
18 | if len(schemaList) == 0 { | ||
19 | return FieldReadResult{}, nil | ||
20 | } | ||
21 | |||
22 | schema := schemaList[len(schemaList)-1] | ||
23 | switch schema.Type { | ||
24 | case TypeBool, TypeInt, TypeFloat, TypeString: | ||
25 | return r.readPrimitive(address, schema) | ||
26 | case TypeList: | ||
27 | return readListField(r, address, schema) | ||
28 | case TypeMap: | ||
29 | return r.readMap(k, schema) | ||
30 | case TypeSet: | ||
31 | return r.readSet(address, schema) | ||
32 | case typeObject: | ||
33 | return readObjectField(r, address, schema.Elem.(map[string]*Schema)) | ||
34 | default: | ||
35 | panic(fmt.Sprintf("Unknown type: %s", schema.Type)) | ||
36 | } | ||
37 | } | ||
38 | |||
39 | func (r *MapFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { | ||
40 | result := make(map[string]interface{}) | ||
41 | resultSet := false | ||
42 | |||
43 | // If the name of the map field is directly in the map with an | ||
44 | // empty string, it means that the map is being deleted, so mark | ||
45 | // that is is set. | ||
46 | if v, ok := r.Map.Access(k); ok && v == "" { | ||
47 | resultSet = true | ||
48 | } | ||
49 | |||
50 | prefix := k + "." | ||
51 | r.Map.Range(func(k, v string) bool { | ||
52 | if strings.HasPrefix(k, prefix) { | ||
53 | resultSet = true | ||
54 | |||
55 | key := k[len(prefix):] | ||
56 | if key != "%" && key != "#" { | ||
57 | result[key] = v | ||
58 | } | ||
59 | } | ||
60 | |||
61 | return true | ||
62 | }) | ||
63 | |||
64 | err := mapValuesToPrimitive(result, schema) | ||
65 | if err != nil { | ||
66 | return FieldReadResult{}, nil | ||
67 | } | ||
68 | |||
69 | var resultVal interface{} | ||
70 | if resultSet { | ||
71 | resultVal = result | ||
72 | } | ||
73 | |||
74 | return FieldReadResult{ | ||
75 | Value: resultVal, | ||
76 | Exists: resultSet, | ||
77 | }, nil | ||
78 | } | ||
79 | |||
80 | func (r *MapFieldReader) readPrimitive( | ||
81 | address []string, schema *Schema) (FieldReadResult, error) { | ||
82 | k := strings.Join(address, ".") | ||
83 | result, ok := r.Map.Access(k) | ||
84 | if !ok { | ||
85 | return FieldReadResult{}, nil | ||
86 | } | ||
87 | |||
88 | returnVal, err := stringToPrimitive(result, false, schema) | ||
89 | if err != nil { | ||
90 | return FieldReadResult{}, err | ||
91 | } | ||
92 | |||
93 | return FieldReadResult{ | ||
94 | Value: returnVal, | ||
95 | Exists: true, | ||
96 | }, nil | ||
97 | } | ||
98 | |||
99 | func (r *MapFieldReader) readSet( | ||
100 | address []string, schema *Schema) (FieldReadResult, error) { | ||
101 | // Get the number of elements in the list | ||
102 | countRaw, err := r.readPrimitive( | ||
103 | append(address, "#"), &Schema{Type: TypeInt}) | ||
104 | if err != nil { | ||
105 | return FieldReadResult{}, err | ||
106 | } | ||
107 | if !countRaw.Exists { | ||
108 | // No count, means we have no list | ||
109 | countRaw.Value = 0 | ||
110 | } | ||
111 | |||
112 | // Create the set that will be our result | ||
113 | set := schema.ZeroValue().(*Set) | ||
114 | |||
115 | // If we have an empty list, then return an empty list | ||
116 | if countRaw.Computed || countRaw.Value.(int) == 0 { | ||
117 | return FieldReadResult{ | ||
118 | Value: set, | ||
119 | Exists: countRaw.Exists, | ||
120 | Computed: countRaw.Computed, | ||
121 | }, nil | ||
122 | } | ||
123 | |||
124 | // Go through the map and find all the set items | ||
125 | prefix := strings.Join(address, ".") + "." | ||
126 | countExpected := countRaw.Value.(int) | ||
127 | countActual := make(map[string]struct{}) | ||
128 | completed := r.Map.Range(func(k, _ string) bool { | ||
129 | if !strings.HasPrefix(k, prefix) { | ||
130 | return true | ||
131 | } | ||
132 | if strings.HasPrefix(k, prefix+"#") { | ||
133 | // Ignore the count field | ||
134 | return true | ||
135 | } | ||
136 | |||
137 | // Split the key, since it might be a sub-object like "idx.field" | ||
138 | parts := strings.Split(k[len(prefix):], ".") | ||
139 | idx := parts[0] | ||
140 | |||
141 | var raw FieldReadResult | ||
142 | raw, err = r.ReadField(append(address, idx)) | ||
143 | if err != nil { | ||
144 | return false | ||
145 | } | ||
146 | if !raw.Exists { | ||
147 | // This shouldn't happen because we just verified it does exist | ||
148 | panic("missing field in set: " + k + "." + idx) | ||
149 | } | ||
150 | |||
151 | set.Add(raw.Value) | ||
152 | |||
153 | // Due to the way multimap readers work, if we've seen the number | ||
154 | // of fields we expect, then exit so that we don't read later values. | ||
155 | // For example: the "set" map might have "ports.#", "ports.0", and | ||
156 | // "ports.1", but the "state" map might have those plus "ports.2". | ||
157 | // We don't want "ports.2" | ||
158 | countActual[idx] = struct{}{} | ||
159 | if len(countActual) >= countExpected { | ||
160 | return false | ||
161 | } | ||
162 | |||
163 | return true | ||
164 | }) | ||
165 | if !completed && err != nil { | ||
166 | return FieldReadResult{}, err | ||
167 | } | ||
168 | |||
169 | return FieldReadResult{ | ||
170 | Value: set, | ||
171 | Exists: true, | ||
172 | }, nil | ||
173 | } | ||
174 | |||
175 | // MapReader is an interface that is given to MapFieldReader for accessing | ||
176 | // a "map". This can be used to have alternate implementations. For a basic | ||
177 | // map[string]string, use BasicMapReader. | ||
178 | type MapReader interface { | ||
179 | Access(string) (string, bool) | ||
180 | Range(func(string, string) bool) bool | ||
181 | } | ||
182 | |||
183 | // BasicMapReader implements MapReader for a single map. | ||
184 | type BasicMapReader map[string]string | ||
185 | |||
186 | func (r BasicMapReader) Access(k string) (string, bool) { | ||
187 | v, ok := r[k] | ||
188 | return v, ok | ||
189 | } | ||
190 | |||
191 | func (r BasicMapReader) Range(f func(string, string) bool) bool { | ||
192 | for k, v := range r { | ||
193 | if cont := f(k, v); !cont { | ||
194 | return false | ||
195 | } | ||
196 | } | ||
197 | |||
198 | return true | ||
199 | } | ||
200 | |||
201 | // MultiMapReader reads over multiple maps, preferring keys that are | ||
202 | // founder earlier (lower number index) vs. later (higher number index) | ||
203 | type MultiMapReader []map[string]string | ||
204 | |||
205 | func (r MultiMapReader) Access(k string) (string, bool) { | ||
206 | for _, m := range r { | ||
207 | if v, ok := m[k]; ok { | ||
208 | return v, ok | ||
209 | } | ||
210 | } | ||
211 | |||
212 | return "", false | ||
213 | } | ||
214 | |||
215 | func (r MultiMapReader) Range(f func(string, string) bool) bool { | ||
216 | done := make(map[string]struct{}) | ||
217 | for _, m := range r { | ||
218 | for k, v := range m { | ||
219 | if _, ok := done[k]; ok { | ||
220 | continue | ||
221 | } | ||
222 | |||
223 | if cont := f(k, v); !cont { | ||
224 | return false | ||
225 | } | ||
226 | |||
227 | done[k] = struct{}{} | ||
228 | } | ||
229 | } | ||
230 | |||
231 | return true | ||
232 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_multi.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_multi.go new file mode 100644 index 0000000..89ad3a8 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_multi.go | |||
@@ -0,0 +1,63 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | ) | ||
6 | |||
7 | // MultiLevelFieldReader reads from other field readers, | ||
8 | // merging their results along the way in a specific order. You can specify | ||
9 | // "levels" and name them in order to read only an exact level or up to | ||
10 | // a specific level. | ||
11 | // | ||
12 | // This is useful for saying things such as "read the field from the state | ||
13 | // and config and merge them" or "read the latest value of the field". | ||
14 | type MultiLevelFieldReader struct { | ||
15 | Readers map[string]FieldReader | ||
16 | Levels []string | ||
17 | } | ||
18 | |||
19 | func (r *MultiLevelFieldReader) ReadField(address []string) (FieldReadResult, error) { | ||
20 | return r.ReadFieldMerge(address, r.Levels[len(r.Levels)-1]) | ||
21 | } | ||
22 | |||
23 | func (r *MultiLevelFieldReader) ReadFieldExact( | ||
24 | address []string, level string) (FieldReadResult, error) { | ||
25 | reader, ok := r.Readers[level] | ||
26 | if !ok { | ||
27 | return FieldReadResult{}, fmt.Errorf( | ||
28 | "Unknown reader level: %s", level) | ||
29 | } | ||
30 | |||
31 | result, err := reader.ReadField(address) | ||
32 | if err != nil { | ||
33 | return FieldReadResult{}, fmt.Errorf( | ||
34 | "Error reading level %s: %s", level, err) | ||
35 | } | ||
36 | |||
37 | return result, nil | ||
38 | } | ||
39 | |||
40 | func (r *MultiLevelFieldReader) ReadFieldMerge( | ||
41 | address []string, level string) (FieldReadResult, error) { | ||
42 | var result FieldReadResult | ||
43 | for _, l := range r.Levels { | ||
44 | if r, ok := r.Readers[l]; ok { | ||
45 | out, err := r.ReadField(address) | ||
46 | if err != nil { | ||
47 | return FieldReadResult{}, fmt.Errorf( | ||
48 | "Error reading level %s: %s", l, err) | ||
49 | } | ||
50 | |||
51 | // TODO: computed | ||
52 | if out.Exists { | ||
53 | result = out | ||
54 | } | ||
55 | } | ||
56 | |||
57 | if l == level { | ||
58 | break | ||
59 | } | ||
60 | } | ||
61 | |||
62 | return result, nil | ||
63 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_writer.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_writer.go new file mode 100644 index 0000000..9abc41b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_writer.go | |||
@@ -0,0 +1,8 @@ | |||
1 | package schema | ||
2 | |||
3 | // FieldWriters are responsible for writing fields by address into | ||
4 | // a proper typed representation. ResourceData uses this to write new data | ||
5 | // into existing sources. | ||
6 | type FieldWriter interface { | ||
7 | WriteField([]string, interface{}) error | ||
8 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go new file mode 100644 index 0000000..689ed8d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go | |||
@@ -0,0 +1,319 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "reflect" | ||
6 | "strconv" | ||
7 | "strings" | ||
8 | "sync" | ||
9 | |||
10 | "github.com/mitchellh/mapstructure" | ||
11 | ) | ||
12 | |||
13 | // MapFieldWriter writes data into a single map[string]string structure. | ||
14 | type MapFieldWriter struct { | ||
15 | Schema map[string]*Schema | ||
16 | |||
17 | lock sync.Mutex | ||
18 | result map[string]string | ||
19 | } | ||
20 | |||
21 | // Map returns the underlying map that is being written to. | ||
22 | func (w *MapFieldWriter) Map() map[string]string { | ||
23 | w.lock.Lock() | ||
24 | defer w.lock.Unlock() | ||
25 | if w.result == nil { | ||
26 | w.result = make(map[string]string) | ||
27 | } | ||
28 | |||
29 | return w.result | ||
30 | } | ||
31 | |||
32 | func (w *MapFieldWriter) unsafeWriteField(addr string, value string) { | ||
33 | w.lock.Lock() | ||
34 | defer w.lock.Unlock() | ||
35 | if w.result == nil { | ||
36 | w.result = make(map[string]string) | ||
37 | } | ||
38 | |||
39 | w.result[addr] = value | ||
40 | } | ||
41 | |||
42 | func (w *MapFieldWriter) WriteField(addr []string, value interface{}) error { | ||
43 | w.lock.Lock() | ||
44 | defer w.lock.Unlock() | ||
45 | if w.result == nil { | ||
46 | w.result = make(map[string]string) | ||
47 | } | ||
48 | |||
49 | schemaList := addrToSchema(addr, w.Schema) | ||
50 | if len(schemaList) == 0 { | ||
51 | return fmt.Errorf("Invalid address to set: %#v", addr) | ||
52 | } | ||
53 | |||
54 | // If we're setting anything other than a list root or set root, | ||
55 | // then disallow it. | ||
56 | for _, schema := range schemaList[:len(schemaList)-1] { | ||
57 | if schema.Type == TypeList { | ||
58 | return fmt.Errorf( | ||
59 | "%s: can only set full list", | ||
60 | strings.Join(addr, ".")) | ||
61 | } | ||
62 | |||
63 | if schema.Type == TypeMap { | ||
64 | return fmt.Errorf( | ||
65 | "%s: can only set full map", | ||
66 | strings.Join(addr, ".")) | ||
67 | } | ||
68 | |||
69 | if schema.Type == TypeSet { | ||
70 | return fmt.Errorf( | ||
71 | "%s: can only set full set", | ||
72 | strings.Join(addr, ".")) | ||
73 | } | ||
74 | } | ||
75 | |||
76 | return w.set(addr, value) | ||
77 | } | ||
78 | |||
79 | func (w *MapFieldWriter) set(addr []string, value interface{}) error { | ||
80 | schemaList := addrToSchema(addr, w.Schema) | ||
81 | if len(schemaList) == 0 { | ||
82 | return fmt.Errorf("Invalid address to set: %#v", addr) | ||
83 | } | ||
84 | |||
85 | schema := schemaList[len(schemaList)-1] | ||
86 | switch schema.Type { | ||
87 | case TypeBool, TypeInt, TypeFloat, TypeString: | ||
88 | return w.setPrimitive(addr, value, schema) | ||
89 | case TypeList: | ||
90 | return w.setList(addr, value, schema) | ||
91 | case TypeMap: | ||
92 | return w.setMap(addr, value, schema) | ||
93 | case TypeSet: | ||
94 | return w.setSet(addr, value, schema) | ||
95 | case typeObject: | ||
96 | return w.setObject(addr, value, schema) | ||
97 | default: | ||
98 | panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) | ||
99 | } | ||
100 | } | ||
101 | |||
102 | func (w *MapFieldWriter) setList( | ||
103 | addr []string, | ||
104 | v interface{}, | ||
105 | schema *Schema) error { | ||
106 | k := strings.Join(addr, ".") | ||
107 | setElement := func(idx string, value interface{}) error { | ||
108 | addrCopy := make([]string, len(addr), len(addr)+1) | ||
109 | copy(addrCopy, addr) | ||
110 | return w.set(append(addrCopy, idx), value) | ||
111 | } | ||
112 | |||
113 | var vs []interface{} | ||
114 | if err := mapstructure.Decode(v, &vs); err != nil { | ||
115 | return fmt.Errorf("%s: %s", k, err) | ||
116 | } | ||
117 | |||
118 | // Set the entire list. | ||
119 | var err error | ||
120 | for i, elem := range vs { | ||
121 | is := strconv.FormatInt(int64(i), 10) | ||
122 | err = setElement(is, elem) | ||
123 | if err != nil { | ||
124 | break | ||
125 | } | ||
126 | } | ||
127 | if err != nil { | ||
128 | for i, _ := range vs { | ||
129 | is := strconv.FormatInt(int64(i), 10) | ||
130 | setElement(is, nil) | ||
131 | } | ||
132 | |||
133 | return err | ||
134 | } | ||
135 | |||
136 | w.result[k+".#"] = strconv.FormatInt(int64(len(vs)), 10) | ||
137 | return nil | ||
138 | } | ||
139 | |||
140 | func (w *MapFieldWriter) setMap( | ||
141 | addr []string, | ||
142 | value interface{}, | ||
143 | schema *Schema) error { | ||
144 | k := strings.Join(addr, ".") | ||
145 | v := reflect.ValueOf(value) | ||
146 | vs := make(map[string]interface{}) | ||
147 | |||
148 | if value == nil { | ||
149 | // The empty string here means the map is removed. | ||
150 | w.result[k] = "" | ||
151 | return nil | ||
152 | } | ||
153 | |||
154 | if v.Kind() != reflect.Map { | ||
155 | return fmt.Errorf("%s: must be a map", k) | ||
156 | } | ||
157 | if v.Type().Key().Kind() != reflect.String { | ||
158 | return fmt.Errorf("%s: keys must strings", k) | ||
159 | } | ||
160 | for _, mk := range v.MapKeys() { | ||
161 | mv := v.MapIndex(mk) | ||
162 | vs[mk.String()] = mv.Interface() | ||
163 | } | ||
164 | |||
165 | // Remove the pure key since we're setting the full map value | ||
166 | delete(w.result, k) | ||
167 | |||
168 | // Set each subkey | ||
169 | addrCopy := make([]string, len(addr), len(addr)+1) | ||
170 | copy(addrCopy, addr) | ||
171 | for subKey, v := range vs { | ||
172 | if err := w.set(append(addrCopy, subKey), v); err != nil { | ||
173 | return err | ||
174 | } | ||
175 | } | ||
176 | |||
177 | // Set the count | ||
178 | w.result[k+".%"] = strconv.Itoa(len(vs)) | ||
179 | |||
180 | return nil | ||
181 | } | ||
182 | |||
183 | func (w *MapFieldWriter) setObject( | ||
184 | addr []string, | ||
185 | value interface{}, | ||
186 | schema *Schema) error { | ||
187 | // Set the entire object. First decode into a proper structure | ||
188 | var v map[string]interface{} | ||
189 | if err := mapstructure.Decode(value, &v); err != nil { | ||
190 | return fmt.Errorf("%s: %s", strings.Join(addr, "."), err) | ||
191 | } | ||
192 | |||
193 | // Make space for additional elements in the address | ||
194 | addrCopy := make([]string, len(addr), len(addr)+1) | ||
195 | copy(addrCopy, addr) | ||
196 | |||
197 | // Set each element in turn | ||
198 | var err error | ||
199 | for k1, v1 := range v { | ||
200 | if err = w.set(append(addrCopy, k1), v1); err != nil { | ||
201 | break | ||
202 | } | ||
203 | } | ||
204 | if err != nil { | ||
205 | for k1, _ := range v { | ||
206 | w.set(append(addrCopy, k1), nil) | ||
207 | } | ||
208 | } | ||
209 | |||
210 | return err | ||
211 | } | ||
212 | |||
213 | func (w *MapFieldWriter) setPrimitive( | ||
214 | addr []string, | ||
215 | v interface{}, | ||
216 | schema *Schema) error { | ||
217 | k := strings.Join(addr, ".") | ||
218 | |||
219 | if v == nil { | ||
220 | // The empty string here means the value is removed. | ||
221 | w.result[k] = "" | ||
222 | return nil | ||
223 | } | ||
224 | |||
225 | var set string | ||
226 | switch schema.Type { | ||
227 | case TypeBool: | ||
228 | var b bool | ||
229 | if err := mapstructure.Decode(v, &b); err != nil { | ||
230 | return fmt.Errorf("%s: %s", k, err) | ||
231 | } | ||
232 | |||
233 | set = strconv.FormatBool(b) | ||
234 | case TypeString: | ||
235 | if err := mapstructure.Decode(v, &set); err != nil { | ||
236 | return fmt.Errorf("%s: %s", k, err) | ||
237 | } | ||
238 | case TypeInt: | ||
239 | var n int | ||
240 | if err := mapstructure.Decode(v, &n); err != nil { | ||
241 | return fmt.Errorf("%s: %s", k, err) | ||
242 | } | ||
243 | set = strconv.FormatInt(int64(n), 10) | ||
244 | case TypeFloat: | ||
245 | var n float64 | ||
246 | if err := mapstructure.Decode(v, &n); err != nil { | ||
247 | return fmt.Errorf("%s: %s", k, err) | ||
248 | } | ||
249 | set = strconv.FormatFloat(float64(n), 'G', -1, 64) | ||
250 | default: | ||
251 | return fmt.Errorf("Unknown type: %#v", schema.Type) | ||
252 | } | ||
253 | |||
254 | w.result[k] = set | ||
255 | return nil | ||
256 | } | ||
257 | |||
258 | func (w *MapFieldWriter) setSet( | ||
259 | addr []string, | ||
260 | value interface{}, | ||
261 | schema *Schema) error { | ||
262 | addrCopy := make([]string, len(addr), len(addr)+1) | ||
263 | copy(addrCopy, addr) | ||
264 | k := strings.Join(addr, ".") | ||
265 | |||
266 | if value == nil { | ||
267 | w.result[k+".#"] = "0" | ||
268 | return nil | ||
269 | } | ||
270 | |||
271 | // If it is a slice, then we have to turn it into a *Set so that | ||
272 | // we get the proper order back based on the hash code. | ||
273 | if v := reflect.ValueOf(value); v.Kind() == reflect.Slice { | ||
274 | // Build a temp *ResourceData to use for the conversion | ||
275 | tempSchema := *schema | ||
276 | tempSchema.Type = TypeList | ||
277 | tempSchemaMap := map[string]*Schema{addr[0]: &tempSchema} | ||
278 | tempW := &MapFieldWriter{Schema: tempSchemaMap} | ||
279 | |||
280 | // Set the entire list, this lets us get sane values out of it | ||
281 | if err := tempW.WriteField(addr, value); err != nil { | ||
282 | return err | ||
283 | } | ||
284 | |||
285 | // Build the set by going over the list items in order and | ||
286 | // hashing them into the set. The reason we go over the list and | ||
287 | // not the `value` directly is because this forces all types | ||
288 | // to become []interface{} (generic) instead of []string, which | ||
289 | // most hash functions are expecting. | ||
290 | s := schema.ZeroValue().(*Set) | ||
291 | tempR := &MapFieldReader{ | ||
292 | Map: BasicMapReader(tempW.Map()), | ||
293 | Schema: tempSchemaMap, | ||
294 | } | ||
295 | for i := 0; i < v.Len(); i++ { | ||
296 | is := strconv.FormatInt(int64(i), 10) | ||
297 | result, err := tempR.ReadField(append(addrCopy, is)) | ||
298 | if err != nil { | ||
299 | return err | ||
300 | } | ||
301 | if !result.Exists { | ||
302 | panic("set item just set doesn't exist") | ||
303 | } | ||
304 | |||
305 | s.Add(result.Value) | ||
306 | } | ||
307 | |||
308 | value = s | ||
309 | } | ||
310 | |||
311 | for code, elem := range value.(*Set).m { | ||
312 | if err := w.set(append(addrCopy, code), elem); err != nil { | ||
313 | return err | ||
314 | } | ||
315 | } | ||
316 | |||
317 | w.result[k+".#"] = strconv.Itoa(value.(*Set).Len()) | ||
318 | return nil | ||
319 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go b/vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go new file mode 100644 index 0000000..3a97629 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go | |||
@@ -0,0 +1,36 @@ | |||
1 | // Code generated by "stringer -type=getSource resource_data_get_source.go"; DO NOT EDIT. | ||
2 | |||
3 | package schema | ||
4 | |||
5 | import "fmt" | ||
6 | |||
7 | const ( | ||
8 | _getSource_name_0 = "getSourceStategetSourceConfig" | ||
9 | _getSource_name_1 = "getSourceDiff" | ||
10 | _getSource_name_2 = "getSourceSet" | ||
11 | _getSource_name_3 = "getSourceLevelMaskgetSourceExact" | ||
12 | ) | ||
13 | |||
14 | var ( | ||
15 | _getSource_index_0 = [...]uint8{0, 14, 29} | ||
16 | _getSource_index_1 = [...]uint8{0, 13} | ||
17 | _getSource_index_2 = [...]uint8{0, 12} | ||
18 | _getSource_index_3 = [...]uint8{0, 18, 32} | ||
19 | ) | ||
20 | |||
21 | func (i getSource) String() string { | ||
22 | switch { | ||
23 | case 1 <= i && i <= 2: | ||
24 | i -= 1 | ||
25 | return _getSource_name_0[_getSource_index_0[i]:_getSource_index_0[i+1]] | ||
26 | case i == 4: | ||
27 | return _getSource_name_1 | ||
28 | case i == 8: | ||
29 | return _getSource_name_2 | ||
30 | case 15 <= i && i <= 16: | ||
31 | i -= 15 | ||
32 | return _getSource_name_3[_getSource_index_3[i]:_getSource_index_3[i+1]] | ||
33 | default: | ||
34 | return fmt.Sprintf("getSource(%d)", i) | ||
35 | } | ||
36 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/provider.go b/vendor/github.com/hashicorp/terraform/helper/schema/provider.go new file mode 100644 index 0000000..d52d2f5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/provider.go | |||
@@ -0,0 +1,400 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "errors" | ||
6 | "fmt" | ||
7 | "sort" | ||
8 | "sync" | ||
9 | |||
10 | "github.com/hashicorp/go-multierror" | ||
11 | "github.com/hashicorp/terraform/terraform" | ||
12 | ) | ||
13 | |||
14 | // Provider represents a resource provider in Terraform, and properly | ||
15 | // implements all of the ResourceProvider API. | ||
16 | // | ||
17 | // By defining a schema for the configuration of the provider, the | ||
18 | // map of supporting resources, and a configuration function, the schema | ||
19 | // framework takes over and handles all the provider operations for you. | ||
20 | // | ||
21 | // After defining the provider structure, it is unlikely that you'll require any | ||
22 | // of the methods on Provider itself. | ||
23 | type Provider struct { | ||
24 | // Schema is the schema for the configuration of this provider. If this | ||
25 | // provider has no configuration, this can be omitted. | ||
26 | // | ||
27 | // The keys of this map are the configuration keys, and the value is | ||
28 | // the schema describing the value of the configuration. | ||
29 | Schema map[string]*Schema | ||
30 | |||
31 | // ResourcesMap is the list of available resources that this provider | ||
32 | // can manage, along with their Resource structure defining their | ||
33 | // own schemas and CRUD operations. | ||
34 | // | ||
35 | // Provider automatically handles routing operations such as Apply, | ||
36 | // Diff, etc. to the proper resource. | ||
37 | ResourcesMap map[string]*Resource | ||
38 | |||
39 | // DataSourcesMap is the collection of available data sources that | ||
40 | // this provider implements, with a Resource instance defining | ||
41 | // the schema and Read operation of each. | ||
42 | // | ||
43 | // Resource instances for data sources must have a Read function | ||
44 | // and must *not* implement Create, Update or Delete. | ||
45 | DataSourcesMap map[string]*Resource | ||
46 | |||
47 | // ConfigureFunc is a function for configuring the provider. If the | ||
48 | // provider doesn't need to be configured, this can be omitted. | ||
49 | // | ||
50 | // See the ConfigureFunc documentation for more information. | ||
51 | ConfigureFunc ConfigureFunc | ||
52 | |||
53 | // MetaReset is called by TestReset to reset any state stored in the meta | ||
54 | // interface. This is especially important if the StopContext is stored by | ||
55 | // the provider. | ||
56 | MetaReset func() error | ||
57 | |||
58 | meta interface{} | ||
59 | |||
60 | // a mutex is required because TestReset can directly repalce the stopCtx | ||
61 | stopMu sync.Mutex | ||
62 | stopCtx context.Context | ||
63 | stopCtxCancel context.CancelFunc | ||
64 | stopOnce sync.Once | ||
65 | } | ||
66 | |||
67 | // ConfigureFunc is the function used to configure a Provider. | ||
68 | // | ||
69 | // The interface{} value returned by this function is stored and passed into | ||
70 | // the subsequent resources as the meta parameter. This return value is | ||
71 | // usually used to pass along a configured API client, a configuration | ||
72 | // structure, etc. | ||
73 | type ConfigureFunc func(*ResourceData) (interface{}, error) | ||
74 | |||
75 | // InternalValidate should be called to validate the structure | ||
76 | // of the provider. | ||
77 | // | ||
78 | // This should be called in a unit test for any provider to verify | ||
79 | // before release that a provider is properly configured for use with | ||
80 | // this library. | ||
81 | func (p *Provider) InternalValidate() error { | ||
82 | if p == nil { | ||
83 | return errors.New("provider is nil") | ||
84 | } | ||
85 | |||
86 | var validationErrors error | ||
87 | sm := schemaMap(p.Schema) | ||
88 | if err := sm.InternalValidate(sm); err != nil { | ||
89 | validationErrors = multierror.Append(validationErrors, err) | ||
90 | } | ||
91 | |||
92 | for k, r := range p.ResourcesMap { | ||
93 | if err := r.InternalValidate(nil, true); err != nil { | ||
94 | validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err)) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | for k, r := range p.DataSourcesMap { | ||
99 | if err := r.InternalValidate(nil, false); err != nil { | ||
100 | validationErrors = multierror.Append(validationErrors, fmt.Errorf("data source %s: %s", k, err)) | ||
101 | } | ||
102 | } | ||
103 | |||
104 | return validationErrors | ||
105 | } | ||
106 | |||
107 | // Meta returns the metadata associated with this provider that was | ||
108 | // returned by the Configure call. It will be nil until Configure is called. | ||
109 | func (p *Provider) Meta() interface{} { | ||
110 | return p.meta | ||
111 | } | ||
112 | |||
113 | // SetMeta can be used to forcefully set the Meta object of the provider. | ||
114 | // Note that if Configure is called the return value will override anything | ||
115 | // set here. | ||
116 | func (p *Provider) SetMeta(v interface{}) { | ||
117 | p.meta = v | ||
118 | } | ||
119 | |||
120 | // Stopped reports whether the provider has been stopped or not. | ||
121 | func (p *Provider) Stopped() bool { | ||
122 | ctx := p.StopContext() | ||
123 | select { | ||
124 | case <-ctx.Done(): | ||
125 | return true | ||
126 | default: | ||
127 | return false | ||
128 | } | ||
129 | } | ||
130 | |||
131 | // StopCh returns a channel that is closed once the provider is stopped. | ||
132 | func (p *Provider) StopContext() context.Context { | ||
133 | p.stopOnce.Do(p.stopInit) | ||
134 | |||
135 | p.stopMu.Lock() | ||
136 | defer p.stopMu.Unlock() | ||
137 | |||
138 | return p.stopCtx | ||
139 | } | ||
140 | |||
141 | func (p *Provider) stopInit() { | ||
142 | p.stopMu.Lock() | ||
143 | defer p.stopMu.Unlock() | ||
144 | |||
145 | p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) | ||
146 | } | ||
147 | |||
148 | // Stop implementation of terraform.ResourceProvider interface. | ||
149 | func (p *Provider) Stop() error { | ||
150 | p.stopOnce.Do(p.stopInit) | ||
151 | |||
152 | p.stopMu.Lock() | ||
153 | defer p.stopMu.Unlock() | ||
154 | |||
155 | p.stopCtxCancel() | ||
156 | return nil | ||
157 | } | ||
158 | |||
159 | // TestReset resets any state stored in the Provider, and will call TestReset | ||
160 | // on Meta if it implements the TestProvider interface. | ||
161 | // This may be used to reset the schema.Provider at the start of a test, and is | ||
162 | // automatically called by resource.Test. | ||
163 | func (p *Provider) TestReset() error { | ||
164 | p.stopInit() | ||
165 | if p.MetaReset != nil { | ||
166 | return p.MetaReset() | ||
167 | } | ||
168 | return nil | ||
169 | } | ||
170 | |||
171 | // Input implementation of terraform.ResourceProvider interface. | ||
172 | func (p *Provider) Input( | ||
173 | input terraform.UIInput, | ||
174 | c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { | ||
175 | return schemaMap(p.Schema).Input(input, c) | ||
176 | } | ||
177 | |||
178 | // Validate implementation of terraform.ResourceProvider interface. | ||
179 | func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) { | ||
180 | if err := p.InternalValidate(); err != nil { | ||
181 | return nil, []error{fmt.Errorf( | ||
182 | "Internal validation of the provider failed! This is always a bug\n"+ | ||
183 | "with the provider itself, and not a user issue. Please report\n"+ | ||
184 | "this bug:\n\n%s", err)} | ||
185 | } | ||
186 | |||
187 | return schemaMap(p.Schema).Validate(c) | ||
188 | } | ||
189 | |||
190 | // ValidateResource implementation of terraform.ResourceProvider interface. | ||
191 | func (p *Provider) ValidateResource( | ||
192 | t string, c *terraform.ResourceConfig) ([]string, []error) { | ||
193 | r, ok := p.ResourcesMap[t] | ||
194 | if !ok { | ||
195 | return nil, []error{fmt.Errorf( | ||
196 | "Provider doesn't support resource: %s", t)} | ||
197 | } | ||
198 | |||
199 | return r.Validate(c) | ||
200 | } | ||
201 | |||
202 | // Configure implementation of terraform.ResourceProvider interface. | ||
203 | func (p *Provider) Configure(c *terraform.ResourceConfig) error { | ||
204 | // No configuration | ||
205 | if p.ConfigureFunc == nil { | ||
206 | return nil | ||
207 | } | ||
208 | |||
209 | sm := schemaMap(p.Schema) | ||
210 | |||
211 | // Get a ResourceData for this configuration. To do this, we actually | ||
212 | // generate an intermediary "diff" although that is never exposed. | ||
213 | diff, err := sm.Diff(nil, c) | ||
214 | if err != nil { | ||
215 | return err | ||
216 | } | ||
217 | |||
218 | data, err := sm.Data(nil, diff) | ||
219 | if err != nil { | ||
220 | return err | ||
221 | } | ||
222 | |||
223 | meta, err := p.ConfigureFunc(data) | ||
224 | if err != nil { | ||
225 | return err | ||
226 | } | ||
227 | |||
228 | p.meta = meta | ||
229 | return nil | ||
230 | } | ||
231 | |||
232 | // Apply implementation of terraform.ResourceProvider interface. | ||
233 | func (p *Provider) Apply( | ||
234 | info *terraform.InstanceInfo, | ||
235 | s *terraform.InstanceState, | ||
236 | d *terraform.InstanceDiff) (*terraform.InstanceState, error) { | ||
237 | r, ok := p.ResourcesMap[info.Type] | ||
238 | if !ok { | ||
239 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | ||
240 | } | ||
241 | |||
242 | return r.Apply(s, d, p.meta) | ||
243 | } | ||
244 | |||
245 | // Diff implementation of terraform.ResourceProvider interface. | ||
246 | func (p *Provider) Diff( | ||
247 | info *terraform.InstanceInfo, | ||
248 | s *terraform.InstanceState, | ||
249 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | ||
250 | r, ok := p.ResourcesMap[info.Type] | ||
251 | if !ok { | ||
252 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | ||
253 | } | ||
254 | |||
255 | return r.Diff(s, c) | ||
256 | } | ||
257 | |||
258 | // Refresh implementation of terraform.ResourceProvider interface. | ||
259 | func (p *Provider) Refresh( | ||
260 | info *terraform.InstanceInfo, | ||
261 | s *terraform.InstanceState) (*terraform.InstanceState, error) { | ||
262 | r, ok := p.ResourcesMap[info.Type] | ||
263 | if !ok { | ||
264 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | ||
265 | } | ||
266 | |||
267 | return r.Refresh(s, p.meta) | ||
268 | } | ||
269 | |||
270 | // Resources implementation of terraform.ResourceProvider interface. | ||
271 | func (p *Provider) Resources() []terraform.ResourceType { | ||
272 | keys := make([]string, 0, len(p.ResourcesMap)) | ||
273 | for k, _ := range p.ResourcesMap { | ||
274 | keys = append(keys, k) | ||
275 | } | ||
276 | sort.Strings(keys) | ||
277 | |||
278 | result := make([]terraform.ResourceType, 0, len(keys)) | ||
279 | for _, k := range keys { | ||
280 | resource := p.ResourcesMap[k] | ||
281 | |||
282 | // This isn't really possible (it'd fail InternalValidate), but | ||
283 | // we do it anyways to avoid a panic. | ||
284 | if resource == nil { | ||
285 | resource = &Resource{} | ||
286 | } | ||
287 | |||
288 | result = append(result, terraform.ResourceType{ | ||
289 | Name: k, | ||
290 | Importable: resource.Importer != nil, | ||
291 | }) | ||
292 | } | ||
293 | |||
294 | return result | ||
295 | } | ||
296 | |||
297 | func (p *Provider) ImportState( | ||
298 | info *terraform.InstanceInfo, | ||
299 | id string) ([]*terraform.InstanceState, error) { | ||
300 | // Find the resource | ||
301 | r, ok := p.ResourcesMap[info.Type] | ||
302 | if !ok { | ||
303 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | ||
304 | } | ||
305 | |||
306 | // If it doesn't support import, error | ||
307 | if r.Importer == nil { | ||
308 | return nil, fmt.Errorf("resource %s doesn't support import", info.Type) | ||
309 | } | ||
310 | |||
311 | // Create the data | ||
312 | data := r.Data(nil) | ||
313 | data.SetId(id) | ||
314 | data.SetType(info.Type) | ||
315 | |||
316 | // Call the import function | ||
317 | results := []*ResourceData{data} | ||
318 | if r.Importer.State != nil { | ||
319 | var err error | ||
320 | results, err = r.Importer.State(data, p.meta) | ||
321 | if err != nil { | ||
322 | return nil, err | ||
323 | } | ||
324 | } | ||
325 | |||
326 | // Convert the results to InstanceState values and return it | ||
327 | states := make([]*terraform.InstanceState, len(results)) | ||
328 | for i, r := range results { | ||
329 | states[i] = r.State() | ||
330 | } | ||
331 | |||
332 | // Verify that all are non-nil. If there are any nil the error | ||
333 | // isn't obvious so we circumvent that with a friendlier error. | ||
334 | for _, s := range states { | ||
335 | if s == nil { | ||
336 | return nil, fmt.Errorf( | ||
337 | "nil entry in ImportState results. This is always a bug with\n" + | ||
338 | "the resource that is being imported. Please report this as\n" + | ||
339 | "a bug to Terraform.") | ||
340 | } | ||
341 | } | ||
342 | |||
343 | return states, nil | ||
344 | } | ||
345 | |||
346 | // ValidateDataSource implementation of terraform.ResourceProvider interface. | ||
347 | func (p *Provider) ValidateDataSource( | ||
348 | t string, c *terraform.ResourceConfig) ([]string, []error) { | ||
349 | r, ok := p.DataSourcesMap[t] | ||
350 | if !ok { | ||
351 | return nil, []error{fmt.Errorf( | ||
352 | "Provider doesn't support data source: %s", t)} | ||
353 | } | ||
354 | |||
355 | return r.Validate(c) | ||
356 | } | ||
357 | |||
358 | // ReadDataDiff implementation of terraform.ResourceProvider interface. | ||
359 | func (p *Provider) ReadDataDiff( | ||
360 | info *terraform.InstanceInfo, | ||
361 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | ||
362 | |||
363 | r, ok := p.DataSourcesMap[info.Type] | ||
364 | if !ok { | ||
365 | return nil, fmt.Errorf("unknown data source: %s", info.Type) | ||
366 | } | ||
367 | |||
368 | return r.Diff(nil, c) | ||
369 | } | ||
370 | |||
371 | // RefreshData implementation of terraform.ResourceProvider interface. | ||
372 | func (p *Provider) ReadDataApply( | ||
373 | info *terraform.InstanceInfo, | ||
374 | d *terraform.InstanceDiff) (*terraform.InstanceState, error) { | ||
375 | |||
376 | r, ok := p.DataSourcesMap[info.Type] | ||
377 | if !ok { | ||
378 | return nil, fmt.Errorf("unknown data source: %s", info.Type) | ||
379 | } | ||
380 | |||
381 | return r.ReadDataApply(d, p.meta) | ||
382 | } | ||
383 | |||
384 | // DataSources implementation of terraform.ResourceProvider interface. | ||
385 | func (p *Provider) DataSources() []terraform.DataSource { | ||
386 | keys := make([]string, 0, len(p.DataSourcesMap)) | ||
387 | for k, _ := range p.DataSourcesMap { | ||
388 | keys = append(keys, k) | ||
389 | } | ||
390 | sort.Strings(keys) | ||
391 | |||
392 | result := make([]terraform.DataSource, 0, len(keys)) | ||
393 | for _, k := range keys { | ||
394 | result = append(result, terraform.DataSource{ | ||
395 | Name: k, | ||
396 | }) | ||
397 | } | ||
398 | |||
399 | return result | ||
400 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go b/vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go new file mode 100644 index 0000000..c1564a2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go | |||
@@ -0,0 +1,180 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "errors" | ||
6 | "fmt" | ||
7 | "sync" | ||
8 | |||
9 | "github.com/hashicorp/go-multierror" | ||
10 | "github.com/hashicorp/terraform/config" | ||
11 | "github.com/hashicorp/terraform/terraform" | ||
12 | ) | ||
13 | |||
14 | // Provisioner represents a resource provisioner in Terraform and properly | ||
15 | // implements all of the ResourceProvisioner API. | ||
16 | // | ||
17 | // This higher level structure makes it much easier to implement a new or | ||
18 | // custom provisioner for Terraform. | ||
19 | // | ||
20 | // The function callbacks for this structure are all passed a context object. | ||
21 | // This context object has a number of pre-defined values that can be accessed | ||
22 | // via the global functions defined in context.go. | ||
23 | type Provisioner struct { | ||
24 | // ConnSchema is the schema for the connection settings for this | ||
25 | // provisioner. | ||
26 | // | ||
27 | // The keys of this map are the configuration keys, and the value is | ||
28 | // the schema describing the value of the configuration. | ||
29 | // | ||
30 | // NOTE: The value of connection keys can only be strings for now. | ||
31 | ConnSchema map[string]*Schema | ||
32 | |||
33 | // Schema is the schema for the usage of this provisioner. | ||
34 | // | ||
35 | // The keys of this map are the configuration keys, and the value is | ||
36 | // the schema describing the value of the configuration. | ||
37 | Schema map[string]*Schema | ||
38 | |||
39 | // ApplyFunc is the function for executing the provisioner. This is required. | ||
40 | // It is given a context. See the Provisioner struct docs for more | ||
41 | // information. | ||
42 | ApplyFunc func(ctx context.Context) error | ||
43 | |||
44 | stopCtx context.Context | ||
45 | stopCtxCancel context.CancelFunc | ||
46 | stopOnce sync.Once | ||
47 | } | ||
48 | |||
49 | // Keys that can be used to access data in the context parameters for | ||
50 | // Provisioners. | ||
51 | var ( | ||
52 | connDataInvalid = contextKey("data invalid") | ||
53 | |||
54 | // This returns a *ResourceData for the connection information. | ||
55 | // Guaranteed to never be nil. | ||
56 | ProvConnDataKey = contextKey("provider conn data") | ||
57 | |||
58 | // This returns a *ResourceData for the config information. | ||
59 | // Guaranteed to never be nil. | ||
60 | ProvConfigDataKey = contextKey("provider config data") | ||
61 | |||
62 | // This returns a terraform.UIOutput. Guaranteed to never be nil. | ||
63 | ProvOutputKey = contextKey("provider output") | ||
64 | |||
65 | // This returns the raw InstanceState passed to Apply. Guaranteed to | ||
66 | // be set, but may be nil. | ||
67 | ProvRawStateKey = contextKey("provider raw state") | ||
68 | ) | ||
69 | |||
70 | // InternalValidate should be called to validate the structure | ||
71 | // of the provisioner. | ||
72 | // | ||
73 | // This should be called in a unit test to verify before release that this | ||
74 | // structure is properly configured for use. | ||
75 | func (p *Provisioner) InternalValidate() error { | ||
76 | if p == nil { | ||
77 | return errors.New("provisioner is nil") | ||
78 | } | ||
79 | |||
80 | var validationErrors error | ||
81 | { | ||
82 | sm := schemaMap(p.ConnSchema) | ||
83 | if err := sm.InternalValidate(sm); err != nil { | ||
84 | validationErrors = multierror.Append(validationErrors, err) | ||
85 | } | ||
86 | } | ||
87 | |||
88 | { | ||
89 | sm := schemaMap(p.Schema) | ||
90 | if err := sm.InternalValidate(sm); err != nil { | ||
91 | validationErrors = multierror.Append(validationErrors, err) | ||
92 | } | ||
93 | } | ||
94 | |||
95 | if p.ApplyFunc == nil { | ||
96 | validationErrors = multierror.Append(validationErrors, fmt.Errorf( | ||
97 | "ApplyFunc must not be nil")) | ||
98 | } | ||
99 | |||
100 | return validationErrors | ||
101 | } | ||
102 | |||
103 | // StopContext returns a context that checks whether a provisioner is stopped. | ||
104 | func (p *Provisioner) StopContext() context.Context { | ||
105 | p.stopOnce.Do(p.stopInit) | ||
106 | return p.stopCtx | ||
107 | } | ||
108 | |||
109 | func (p *Provisioner) stopInit() { | ||
110 | p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) | ||
111 | } | ||
112 | |||
113 | // Stop implementation of terraform.ResourceProvisioner interface. | ||
114 | func (p *Provisioner) Stop() error { | ||
115 | p.stopOnce.Do(p.stopInit) | ||
116 | p.stopCtxCancel() | ||
117 | return nil | ||
118 | } | ||
119 | |||
120 | func (p *Provisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) { | ||
121 | return schemaMap(p.Schema).Validate(c) | ||
122 | } | ||
123 | |||
124 | // Apply implementation of terraform.ResourceProvisioner interface. | ||
125 | func (p *Provisioner) Apply( | ||
126 | o terraform.UIOutput, | ||
127 | s *terraform.InstanceState, | ||
128 | c *terraform.ResourceConfig) error { | ||
129 | var connData, configData *ResourceData | ||
130 | |||
131 | { | ||
132 | // We first need to turn the connection information into a | ||
133 | // terraform.ResourceConfig so that we can use that type to more | ||
134 | // easily build a ResourceData structure. We do this by simply treating | ||
135 | // the conn info as configuration input. | ||
136 | raw := make(map[string]interface{}) | ||
137 | if s != nil { | ||
138 | for k, v := range s.Ephemeral.ConnInfo { | ||
139 | raw[k] = v | ||
140 | } | ||
141 | } | ||
142 | |||
143 | c, err := config.NewRawConfig(raw) | ||
144 | if err != nil { | ||
145 | return err | ||
146 | } | ||
147 | |||
148 | sm := schemaMap(p.ConnSchema) | ||
149 | diff, err := sm.Diff(nil, terraform.NewResourceConfig(c)) | ||
150 | if err != nil { | ||
151 | return err | ||
152 | } | ||
153 | connData, err = sm.Data(nil, diff) | ||
154 | if err != nil { | ||
155 | return err | ||
156 | } | ||
157 | } | ||
158 | |||
159 | { | ||
160 | // Build the configuration data. Doing this requires making a "diff" | ||
161 | // even though that's never used. We use that just to get the correct types. | ||
162 | configMap := schemaMap(p.Schema) | ||
163 | diff, err := configMap.Diff(nil, c) | ||
164 | if err != nil { | ||
165 | return err | ||
166 | } | ||
167 | configData, err = configMap.Data(nil, diff) | ||
168 | if err != nil { | ||
169 | return err | ||
170 | } | ||
171 | } | ||
172 | |||
173 | // Build the context and call the function | ||
174 | ctx := p.StopContext() | ||
175 | ctx = context.WithValue(ctx, ProvConnDataKey, connData) | ||
176 | ctx = context.WithValue(ctx, ProvConfigDataKey, configData) | ||
177 | ctx = context.WithValue(ctx, ProvOutputKey, o) | ||
178 | ctx = context.WithValue(ctx, ProvRawStateKey, s) | ||
179 | return p.ApplyFunc(ctx) | ||
180 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go new file mode 100644 index 0000000..c810558 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go | |||
@@ -0,0 +1,478 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "fmt" | ||
6 | "log" | ||
7 | "strconv" | ||
8 | |||
9 | "github.com/hashicorp/terraform/terraform" | ||
10 | ) | ||
11 | |||
12 | // Resource represents a thing in Terraform that has a set of configurable | ||
13 | // attributes and a lifecycle (create, read, update, delete). | ||
14 | // | ||
15 | // The Resource schema is an abstraction that allows provider writers to | ||
16 | // worry only about CRUD operations while off-loading validation, diff | ||
17 | // generation, etc. to this higher level library. | ||
18 | // | ||
19 | // In spite of the name, this struct is not used only for terraform resources, | ||
20 | // but also for data sources. In the case of data sources, the Create, | ||
21 | // Update and Delete functions must not be provided. | ||
22 | type Resource struct { | ||
23 | // Schema is the schema for the configuration of this resource. | ||
24 | // | ||
25 | // The keys of this map are the configuration keys, and the values | ||
26 | // describe the schema of the configuration value. | ||
27 | // | ||
28 | // The schema is used to represent both configurable data as well | ||
29 | // as data that might be computed in the process of creating this | ||
30 | // resource. | ||
31 | Schema map[string]*Schema | ||
32 | |||
33 | // SchemaVersion is the version number for this resource's Schema | ||
34 | // definition. The current SchemaVersion stored in the state for each | ||
35 | // resource. Provider authors can increment this version number | ||
36 | // when Schema semantics change. If the State's SchemaVersion is less than | ||
37 | // the current SchemaVersion, the InstanceState is yielded to the | ||
38 | // MigrateState callback, where the provider can make whatever changes it | ||
39 | // needs to update the state to be compatible to the latest version of the | ||
40 | // Schema. | ||
41 | // | ||
42 | // When unset, SchemaVersion defaults to 0, so provider authors can start | ||
43 | // their Versioning at any integer >= 1 | ||
44 | SchemaVersion int | ||
45 | |||
46 | // MigrateState is responsible for updating an InstanceState with an old | ||
47 | // version to the format expected by the current version of the Schema. | ||
48 | // | ||
49 | // It is called during Refresh if the State's stored SchemaVersion is less | ||
50 | // than the current SchemaVersion of the Resource. | ||
51 | // | ||
52 | // The function is yielded the state's stored SchemaVersion and a pointer to | ||
53 | // the InstanceState that needs updating, as well as the configured | ||
54 | // provider's configured meta interface{}, in case the migration process | ||
55 | // needs to make any remote API calls. | ||
56 | MigrateState StateMigrateFunc | ||
57 | |||
58 | // The functions below are the CRUD operations for this resource. | ||
59 | // | ||
60 | // The only optional operation is Update. If Update is not implemented, | ||
61 | // then updates will not be supported for this resource. | ||
62 | // | ||
63 | // The ResourceData parameter in the functions below are used to | ||
64 | // query configuration and changes for the resource as well as to set | ||
65 | // the ID, computed data, etc. | ||
66 | // | ||
67 | // The interface{} parameter is the result of the ConfigureFunc in | ||
68 | // the provider for this resource. If the provider does not define | ||
69 | // a ConfigureFunc, this will be nil. This parameter should be used | ||
70 | // to store API clients, configuration structures, etc. | ||
71 | // | ||
72 | // If any errors occur during each of the operation, an error should be | ||
73 | // returned. If a resource was partially updated, be careful to enable | ||
74 | // partial state mode for ResourceData and use it accordingly. | ||
75 | // | ||
76 | // Exists is a function that is called to check if a resource still | ||
77 | // exists. If this returns false, then this will affect the diff | ||
78 | // accordingly. If this function isn't set, it will not be called. It | ||
79 | // is highly recommended to set it. The *ResourceData passed to Exists | ||
80 | // should _not_ be modified. | ||
81 | Create CreateFunc | ||
82 | Read ReadFunc | ||
83 | Update UpdateFunc | ||
84 | Delete DeleteFunc | ||
85 | Exists ExistsFunc | ||
86 | |||
87 | // Importer is the ResourceImporter implementation for this resource. | ||
88 | // If this is nil, then this resource does not support importing. If | ||
89 | // this is non-nil, then it supports importing and ResourceImporter | ||
90 | // must be validated. The validity of ResourceImporter is verified | ||
91 | // by InternalValidate on Resource. | ||
92 | Importer *ResourceImporter | ||
93 | |||
94 | // If non-empty, this string is emitted as a warning during Validate. | ||
95 | // This is a private interface for now, for use by DataSourceResourceShim, | ||
96 | // and not for general use. (But maybe later...) | ||
97 | deprecationMessage string | ||
98 | |||
99 | // Timeouts allow users to specify specific time durations in which an | ||
100 | // operation should time out, to allow them to extend an action to suit their | ||
101 | // usage. For example, a user may specify a large Creation timeout for their | ||
102 | // AWS RDS Instance due to it's size, or restoring from a snapshot. | ||
103 | // Resource implementors must enable Timeout support by adding the allowed | ||
104 | // actions (Create, Read, Update, Delete, Default) to the Resource struct, and | ||
105 | // accessing them in the matching methods. | ||
106 | Timeouts *ResourceTimeout | ||
107 | } | ||
108 | |||
109 | // See Resource documentation. | ||
110 | type CreateFunc func(*ResourceData, interface{}) error | ||
111 | |||
112 | // See Resource documentation. | ||
113 | type ReadFunc func(*ResourceData, interface{}) error | ||
114 | |||
115 | // See Resource documentation. | ||
116 | type UpdateFunc func(*ResourceData, interface{}) error | ||
117 | |||
118 | // See Resource documentation. | ||
119 | type DeleteFunc func(*ResourceData, interface{}) error | ||
120 | |||
121 | // See Resource documentation. | ||
122 | type ExistsFunc func(*ResourceData, interface{}) (bool, error) | ||
123 | |||
124 | // See Resource documentation. | ||
125 | type StateMigrateFunc func( | ||
126 | int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) | ||
127 | |||
128 | // Apply creates, updates, and/or deletes a resource. | ||
129 | func (r *Resource) Apply( | ||
130 | s *terraform.InstanceState, | ||
131 | d *terraform.InstanceDiff, | ||
132 | meta interface{}) (*terraform.InstanceState, error) { | ||
133 | data, err := schemaMap(r.Schema).Data(s, d) | ||
134 | if err != nil { | ||
135 | return s, err | ||
136 | } | ||
137 | |||
138 | // Instance Diff shoould have the timeout info, need to copy it over to the | ||
139 | // ResourceData meta | ||
140 | rt := ResourceTimeout{} | ||
141 | if _, ok := d.Meta[TimeoutKey]; ok { | ||
142 | if err := rt.DiffDecode(d); err != nil { | ||
143 | log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) | ||
144 | } | ||
145 | } else { | ||
146 | log.Printf("[DEBUG] No meta timeoutkey found in Apply()") | ||
147 | } | ||
148 | data.timeouts = &rt | ||
149 | |||
150 | if s == nil { | ||
151 | // The Terraform API dictates that this should never happen, but | ||
152 | // it doesn't hurt to be safe in this case. | ||
153 | s = new(terraform.InstanceState) | ||
154 | } | ||
155 | |||
156 | if d.Destroy || d.RequiresNew() { | ||
157 | if s.ID != "" { | ||
158 | // Destroy the resource since it is created | ||
159 | if err := r.Delete(data, meta); err != nil { | ||
160 | return r.recordCurrentSchemaVersion(data.State()), err | ||
161 | } | ||
162 | |||
163 | // Make sure the ID is gone. | ||
164 | data.SetId("") | ||
165 | } | ||
166 | |||
167 | // If we're only destroying, and not creating, then return | ||
168 | // now since we're done! | ||
169 | if !d.RequiresNew() { | ||
170 | return nil, nil | ||
171 | } | ||
172 | |||
173 | // Reset the data to be stateless since we just destroyed | ||
174 | data, err = schemaMap(r.Schema).Data(nil, d) | ||
175 | // data was reset, need to re-apply the parsed timeouts | ||
176 | data.timeouts = &rt | ||
177 | if err != nil { | ||
178 | return nil, err | ||
179 | } | ||
180 | } | ||
181 | |||
182 | err = nil | ||
183 | if data.Id() == "" { | ||
184 | // We're creating, it is a new resource. | ||
185 | data.MarkNewResource() | ||
186 | err = r.Create(data, meta) | ||
187 | } else { | ||
188 | if r.Update == nil { | ||
189 | return s, fmt.Errorf("doesn't support update") | ||
190 | } | ||
191 | |||
192 | err = r.Update(data, meta) | ||
193 | } | ||
194 | |||
195 | return r.recordCurrentSchemaVersion(data.State()), err | ||
196 | } | ||
197 | |||
198 | // Diff returns a diff of this resource and is API compatible with the | ||
199 | // ResourceProvider interface. | ||
200 | func (r *Resource) Diff( | ||
201 | s *terraform.InstanceState, | ||
202 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | ||
203 | |||
204 | t := &ResourceTimeout{} | ||
205 | err := t.ConfigDecode(r, c) | ||
206 | |||
207 | if err != nil { | ||
208 | return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err) | ||
209 | } | ||
210 | |||
211 | instanceDiff, err := schemaMap(r.Schema).Diff(s, c) | ||
212 | if err != nil { | ||
213 | return instanceDiff, err | ||
214 | } | ||
215 | |||
216 | if instanceDiff != nil { | ||
217 | if err := t.DiffEncode(instanceDiff); err != nil { | ||
218 | log.Printf("[ERR] Error encoding timeout to instance diff: %s", err) | ||
219 | } | ||
220 | } else { | ||
221 | log.Printf("[DEBUG] Instance Diff is nil in Diff()") | ||
222 | } | ||
223 | |||
224 | return instanceDiff, err | ||
225 | } | ||
226 | |||
227 | // Validate validates the resource configuration against the schema. | ||
228 | func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { | ||
229 | warns, errs := schemaMap(r.Schema).Validate(c) | ||
230 | |||
231 | if r.deprecationMessage != "" { | ||
232 | warns = append(warns, r.deprecationMessage) | ||
233 | } | ||
234 | |||
235 | return warns, errs | ||
236 | } | ||
237 | |||
238 | // ReadDataApply loads the data for a data source, given a diff that | ||
239 | // describes the configuration arguments and desired computed attributes. | ||
240 | func (r *Resource) ReadDataApply( | ||
241 | d *terraform.InstanceDiff, | ||
242 | meta interface{}, | ||
243 | ) (*terraform.InstanceState, error) { | ||
244 | |||
245 | // Data sources are always built completely from scratch | ||
246 | // on each read, so the source state is always nil. | ||
247 | data, err := schemaMap(r.Schema).Data(nil, d) | ||
248 | if err != nil { | ||
249 | return nil, err | ||
250 | } | ||
251 | |||
252 | err = r.Read(data, meta) | ||
253 | state := data.State() | ||
254 | if state != nil && state.ID == "" { | ||
255 | // Data sources can set an ID if they want, but they aren't | ||
256 | // required to; we'll provide a placeholder if they don't, | ||
257 | // to preserve the invariant that all resources have non-empty | ||
258 | // ids. | ||
259 | state.ID = "-" | ||
260 | } | ||
261 | |||
262 | return r.recordCurrentSchemaVersion(state), err | ||
263 | } | ||
264 | |||
265 | // Refresh refreshes the state of the resource. | ||
266 | func (r *Resource) Refresh( | ||
267 | s *terraform.InstanceState, | ||
268 | meta interface{}) (*terraform.InstanceState, error) { | ||
269 | // If the ID is already somehow blank, it doesn't exist | ||
270 | if s.ID == "" { | ||
271 | return nil, nil | ||
272 | } | ||
273 | |||
274 | rt := ResourceTimeout{} | ||
275 | if _, ok := s.Meta[TimeoutKey]; ok { | ||
276 | if err := rt.StateDecode(s); err != nil { | ||
277 | log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) | ||
278 | } | ||
279 | } | ||
280 | |||
281 | if r.Exists != nil { | ||
282 | // Make a copy of data so that if it is modified it doesn't | ||
283 | // affect our Read later. | ||
284 | data, err := schemaMap(r.Schema).Data(s, nil) | ||
285 | data.timeouts = &rt | ||
286 | |||
287 | if err != nil { | ||
288 | return s, err | ||
289 | } | ||
290 | |||
291 | exists, err := r.Exists(data, meta) | ||
292 | if err != nil { | ||
293 | return s, err | ||
294 | } | ||
295 | if !exists { | ||
296 | return nil, nil | ||
297 | } | ||
298 | } | ||
299 | |||
300 | needsMigration, stateSchemaVersion := r.checkSchemaVersion(s) | ||
301 | if needsMigration && r.MigrateState != nil { | ||
302 | s, err := r.MigrateState(stateSchemaVersion, s, meta) | ||
303 | if err != nil { | ||
304 | return s, err | ||
305 | } | ||
306 | } | ||
307 | |||
308 | data, err := schemaMap(r.Schema).Data(s, nil) | ||
309 | data.timeouts = &rt | ||
310 | if err != nil { | ||
311 | return s, err | ||
312 | } | ||
313 | |||
314 | err = r.Read(data, meta) | ||
315 | state := data.State() | ||
316 | if state != nil && state.ID == "" { | ||
317 | state = nil | ||
318 | } | ||
319 | |||
320 | return r.recordCurrentSchemaVersion(state), err | ||
321 | } | ||
322 | |||
323 | // InternalValidate should be called to validate the structure | ||
324 | // of the resource. | ||
325 | // | ||
326 | // This should be called in a unit test for any resource to verify | ||
327 | // before release that a resource is properly configured for use with | ||
328 | // this library. | ||
329 | // | ||
330 | // Provider.InternalValidate() will automatically call this for all of | ||
331 | // the resources it manages, so you don't need to call this manually if it | ||
332 | // is part of a Provider. | ||
333 | func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error { | ||
334 | if r == nil { | ||
335 | return errors.New("resource is nil") | ||
336 | } | ||
337 | |||
338 | if !writable { | ||
339 | if r.Create != nil || r.Update != nil || r.Delete != nil { | ||
340 | return fmt.Errorf("must not implement Create, Update or Delete") | ||
341 | } | ||
342 | } | ||
343 | |||
344 | tsm := topSchemaMap | ||
345 | |||
346 | if r.isTopLevel() && writable { | ||
347 | // All non-Computed attributes must be ForceNew if Update is not defined | ||
348 | if r.Update == nil { | ||
349 | nonForceNewAttrs := make([]string, 0) | ||
350 | for k, v := range r.Schema { | ||
351 | if !v.ForceNew && !v.Computed { | ||
352 | nonForceNewAttrs = append(nonForceNewAttrs, k) | ||
353 | } | ||
354 | } | ||
355 | if len(nonForceNewAttrs) > 0 { | ||
356 | return fmt.Errorf( | ||
357 | "No Update defined, must set ForceNew on: %#v", nonForceNewAttrs) | ||
358 | } | ||
359 | } else { | ||
360 | nonUpdateableAttrs := make([]string, 0) | ||
361 | for k, v := range r.Schema { | ||
362 | if v.ForceNew || v.Computed && !v.Optional { | ||
363 | nonUpdateableAttrs = append(nonUpdateableAttrs, k) | ||
364 | } | ||
365 | } | ||
366 | updateableAttrs := len(r.Schema) - len(nonUpdateableAttrs) | ||
367 | if updateableAttrs == 0 { | ||
368 | return fmt.Errorf( | ||
369 | "All fields are ForceNew or Computed w/out Optional, Update is superfluous") | ||
370 | } | ||
371 | } | ||
372 | |||
373 | tsm = schemaMap(r.Schema) | ||
374 | |||
375 | // Destroy, and Read are required | ||
376 | if r.Read == nil { | ||
377 | return fmt.Errorf("Read must be implemented") | ||
378 | } | ||
379 | if r.Delete == nil { | ||
380 | return fmt.Errorf("Delete must be implemented") | ||
381 | } | ||
382 | |||
383 | // If we have an importer, we need to verify the importer. | ||
384 | if r.Importer != nil { | ||
385 | if err := r.Importer.InternalValidate(); err != nil { | ||
386 | return err | ||
387 | } | ||
388 | } | ||
389 | } | ||
390 | |||
391 | return schemaMap(r.Schema).InternalValidate(tsm) | ||
392 | } | ||
393 | |||
394 | // Data returns a ResourceData struct for this Resource. Each return value | ||
395 | // is a separate copy and can be safely modified differently. | ||
396 | // | ||
397 | // The data returned from this function has no actual affect on the Resource | ||
398 | // itself (including the state given to this function). | ||
399 | // | ||
400 | // This function is useful for unit tests and ResourceImporter functions. | ||
401 | func (r *Resource) Data(s *terraform.InstanceState) *ResourceData { | ||
402 | result, err := schemaMap(r.Schema).Data(s, nil) | ||
403 | if err != nil { | ||
404 | // At the time of writing, this isn't possible (Data never returns | ||
405 | // non-nil errors). We panic to find this in the future if we have to. | ||
406 | // I don't see a reason for Data to ever return an error. | ||
407 | panic(err) | ||
408 | } | ||
409 | |||
410 | // Set the schema version to latest by default | ||
411 | result.meta = map[string]interface{}{ | ||
412 | "schema_version": strconv.Itoa(r.SchemaVersion), | ||
413 | } | ||
414 | |||
415 | return result | ||
416 | } | ||
417 | |||
418 | // TestResourceData Yields a ResourceData filled with this resource's schema for use in unit testing | ||
419 | // | ||
420 | // TODO: May be able to be removed with the above ResourceData function. | ||
421 | func (r *Resource) TestResourceData() *ResourceData { | ||
422 | return &ResourceData{ | ||
423 | schema: r.Schema, | ||
424 | } | ||
425 | } | ||
426 | |||
427 | // Returns true if the resource is "top level" i.e. not a sub-resource. | ||
428 | func (r *Resource) isTopLevel() bool { | ||
429 | // TODO: This is a heuristic; replace with a definitive attribute? | ||
430 | return r.Create != nil | ||
431 | } | ||
432 | |||
433 | // Determines if a given InstanceState needs to be migrated by checking the | ||
434 | // stored version number with the current SchemaVersion | ||
435 | func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) { | ||
436 | // Get the raw interface{} value for the schema version. If it doesn't | ||
437 | // exist or is nil then set it to zero. | ||
438 | raw := is.Meta["schema_version"] | ||
439 | if raw == nil { | ||
440 | raw = "0" | ||
441 | } | ||
442 | |||
443 | // Try to convert it to a string. If it isn't a string then we pretend | ||
444 | // that it isn't set at all. It should never not be a string unless it | ||
445 | // was manually tampered with. | ||
446 | rawString, ok := raw.(string) | ||
447 | if !ok { | ||
448 | rawString = "0" | ||
449 | } | ||
450 | |||
451 | stateSchemaVersion, _ := strconv.Atoi(rawString) | ||
452 | return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion | ||
453 | } | ||
454 | |||
455 | func (r *Resource) recordCurrentSchemaVersion( | ||
456 | state *terraform.InstanceState) *terraform.InstanceState { | ||
457 | if state != nil && r.SchemaVersion > 0 { | ||
458 | if state.Meta == nil { | ||
459 | state.Meta = make(map[string]interface{}) | ||
460 | } | ||
461 | state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) | ||
462 | } | ||
463 | return state | ||
464 | } | ||
465 | |||
466 | // Noop is a convenience implementation of resource function which takes | ||
467 | // no action and returns no error. | ||
468 | func Noop(*ResourceData, interface{}) error { | ||
469 | return nil | ||
470 | } | ||
471 | |||
472 | // RemoveFromState is a convenience implementation of a resource function | ||
473 | // which sets the resource ID to empty string (to remove it from state) | ||
474 | // and returns no error. | ||
475 | func RemoveFromState(d *ResourceData, _ interface{}) error { | ||
476 | d.SetId("") | ||
477 | return nil | ||
478 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go new file mode 100644 index 0000000..b2bc8f6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go | |||
@@ -0,0 +1,502 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "log" | ||
5 | "reflect" | ||
6 | "strings" | ||
7 | "sync" | ||
8 | "time" | ||
9 | |||
10 | "github.com/hashicorp/terraform/terraform" | ||
11 | ) | ||
12 | |||
13 | // ResourceData is used to query and set the attributes of a resource. | ||
14 | // | ||
15 | // ResourceData is the primary argument received for CRUD operations on | ||
16 | // a resource as well as configuration of a provider. It is a powerful | ||
17 | // structure that can be used to not only query data, but check for changes, | ||
18 | // define partial state updates, etc. | ||
19 | // | ||
20 | // The most relevant methods to take a look at are Get, Set, and Partial. | ||
21 | type ResourceData struct { | ||
22 | // Settable (internally) | ||
23 | schema map[string]*Schema | ||
24 | config *terraform.ResourceConfig | ||
25 | state *terraform.InstanceState | ||
26 | diff *terraform.InstanceDiff | ||
27 | meta map[string]interface{} | ||
28 | timeouts *ResourceTimeout | ||
29 | |||
30 | // Don't set | ||
31 | multiReader *MultiLevelFieldReader | ||
32 | setWriter *MapFieldWriter | ||
33 | newState *terraform.InstanceState | ||
34 | partial bool | ||
35 | partialMap map[string]struct{} | ||
36 | once sync.Once | ||
37 | isNew bool | ||
38 | } | ||
39 | |||
40 | // getResult is the internal structure that is generated when a Get | ||
41 | // is called that contains some extra data that might be used. | ||
42 | type getResult struct { | ||
43 | Value interface{} | ||
44 | ValueProcessed interface{} | ||
45 | Computed bool | ||
46 | Exists bool | ||
47 | Schema *Schema | ||
48 | } | ||
49 | |||
50 | // UnsafeSetFieldRaw allows setting arbitrary values in state to arbitrary | ||
51 | // values, bypassing schema. This MUST NOT be used in normal circumstances - | ||
52 | // it exists only to support the remote_state data source. | ||
53 | func (d *ResourceData) UnsafeSetFieldRaw(key string, value string) { | ||
54 | d.once.Do(d.init) | ||
55 | |||
56 | d.setWriter.unsafeWriteField(key, value) | ||
57 | } | ||
58 | |||
59 | // Get returns the data for the given key, or nil if the key doesn't exist | ||
60 | // in the schema. | ||
61 | // | ||
62 | // If the key does exist in the schema but doesn't exist in the configuration, | ||
63 | // then the default value for that type will be returned. For strings, this is | ||
64 | // "", for numbers it is 0, etc. | ||
65 | // | ||
66 | // If you want to test if something is set at all in the configuration, | ||
67 | // use GetOk. | ||
68 | func (d *ResourceData) Get(key string) interface{} { | ||
69 | v, _ := d.GetOk(key) | ||
70 | return v | ||
71 | } | ||
72 | |||
73 | // GetChange returns the old and new value for a given key. | ||
74 | // | ||
75 | // HasChange should be used to check if a change exists. It is possible | ||
76 | // that both the old and new value are the same if the old value was not | ||
77 | // set and the new value is. This is common, for example, for boolean | ||
78 | // fields which have a zero value of false. | ||
79 | func (d *ResourceData) GetChange(key string) (interface{}, interface{}) { | ||
80 | o, n := d.getChange(key, getSourceState, getSourceDiff) | ||
81 | return o.Value, n.Value | ||
82 | } | ||
83 | |||
84 | // GetOk returns the data for the given key and whether or not the key | ||
85 | // has been set to a non-zero value at some point. | ||
86 | // | ||
87 | // The first result will not necessarilly be nil if the value doesn't exist. | ||
88 | // The second result should be checked to determine this information. | ||
89 | func (d *ResourceData) GetOk(key string) (interface{}, bool) { | ||
90 | r := d.getRaw(key, getSourceSet) | ||
91 | exists := r.Exists && !r.Computed | ||
92 | if exists { | ||
93 | // If it exists, we also want to verify it is not the zero-value. | ||
94 | value := r.Value | ||
95 | zero := r.Schema.Type.Zero() | ||
96 | |||
97 | if eq, ok := value.(Equal); ok { | ||
98 | exists = !eq.Equal(zero) | ||
99 | } else { | ||
100 | exists = !reflect.DeepEqual(value, zero) | ||
101 | } | ||
102 | } | ||
103 | |||
104 | return r.Value, exists | ||
105 | } | ||
106 | |||
107 | func (d *ResourceData) getRaw(key string, level getSource) getResult { | ||
108 | var parts []string | ||
109 | if key != "" { | ||
110 | parts = strings.Split(key, ".") | ||
111 | } | ||
112 | |||
113 | return d.get(parts, level) | ||
114 | } | ||
115 | |||
116 | // HasChange returns whether or not the given key has been changed. | ||
117 | func (d *ResourceData) HasChange(key string) bool { | ||
118 | o, n := d.GetChange(key) | ||
119 | |||
120 | // If the type implements the Equal interface, then call that | ||
121 | // instead of just doing a reflect.DeepEqual. An example where this is | ||
122 | // needed is *Set | ||
123 | if eq, ok := o.(Equal); ok { | ||
124 | return !eq.Equal(n) | ||
125 | } | ||
126 | |||
127 | return !reflect.DeepEqual(o, n) | ||
128 | } | ||
129 | |||
130 | // Partial turns partial state mode on/off. | ||
131 | // | ||
132 | // When partial state mode is enabled, then only key prefixes specified | ||
133 | // by SetPartial will be in the final state. This allows providers to return | ||
134 | // partial states for partially applied resources (when errors occur). | ||
135 | func (d *ResourceData) Partial(on bool) { | ||
136 | d.partial = on | ||
137 | if on { | ||
138 | if d.partialMap == nil { | ||
139 | d.partialMap = make(map[string]struct{}) | ||
140 | } | ||
141 | } else { | ||
142 | d.partialMap = nil | ||
143 | } | ||
144 | } | ||
145 | |||
146 | // Set sets the value for the given key. | ||
147 | // | ||
148 | // If the key is invalid or the value is not a correct type, an error | ||
149 | // will be returned. | ||
150 | func (d *ResourceData) Set(key string, value interface{}) error { | ||
151 | d.once.Do(d.init) | ||
152 | |||
153 | // If the value is a pointer to a non-struct, get its value and | ||
154 | // use that. This allows Set to take a pointer to primitives to | ||
155 | // simplify the interface. | ||
156 | reflectVal := reflect.ValueOf(value) | ||
157 | if reflectVal.Kind() == reflect.Ptr { | ||
158 | if reflectVal.IsNil() { | ||
159 | // If the pointer is nil, then the value is just nil | ||
160 | value = nil | ||
161 | } else { | ||
162 | // Otherwise, we dereference the pointer as long as its not | ||
163 | // a pointer to a struct, since struct pointers are allowed. | ||
164 | reflectVal = reflect.Indirect(reflectVal) | ||
165 | if reflectVal.Kind() != reflect.Struct { | ||
166 | value = reflectVal.Interface() | ||
167 | } | ||
168 | } | ||
169 | } | ||
170 | |||
171 | return d.setWriter.WriteField(strings.Split(key, "."), value) | ||
172 | } | ||
173 | |||
174 | // SetPartial adds the key to the final state output while | ||
175 | // in partial state mode. The key must be a root key in the schema (i.e. | ||
176 | // it cannot be "list.0"). | ||
177 | // | ||
178 | // If partial state mode is disabled, then this has no effect. Additionally, | ||
179 | // whenever partial state mode is toggled, the partial data is cleared. | ||
180 | func (d *ResourceData) SetPartial(k string) { | ||
181 | if d.partial { | ||
182 | d.partialMap[k] = struct{}{} | ||
183 | } | ||
184 | } | ||
185 | |||
186 | func (d *ResourceData) MarkNewResource() { | ||
187 | d.isNew = true | ||
188 | } | ||
189 | |||
190 | func (d *ResourceData) IsNewResource() bool { | ||
191 | return d.isNew | ||
192 | } | ||
193 | |||
194 | // Id returns the ID of the resource. | ||
195 | func (d *ResourceData) Id() string { | ||
196 | var result string | ||
197 | |||
198 | if d.state != nil { | ||
199 | result = d.state.ID | ||
200 | } | ||
201 | |||
202 | if d.newState != nil { | ||
203 | result = d.newState.ID | ||
204 | } | ||
205 | |||
206 | return result | ||
207 | } | ||
208 | |||
209 | // ConnInfo returns the connection info for this resource. | ||
210 | func (d *ResourceData) ConnInfo() map[string]string { | ||
211 | if d.newState != nil { | ||
212 | return d.newState.Ephemeral.ConnInfo | ||
213 | } | ||
214 | |||
215 | if d.state != nil { | ||
216 | return d.state.Ephemeral.ConnInfo | ||
217 | } | ||
218 | |||
219 | return nil | ||
220 | } | ||
221 | |||
222 | // SetId sets the ID of the resource. If the value is blank, then the | ||
223 | // resource is destroyed. | ||
224 | func (d *ResourceData) SetId(v string) { | ||
225 | d.once.Do(d.init) | ||
226 | d.newState.ID = v | ||
227 | } | ||
228 | |||
229 | // SetConnInfo sets the connection info for a resource. | ||
230 | func (d *ResourceData) SetConnInfo(v map[string]string) { | ||
231 | d.once.Do(d.init) | ||
232 | d.newState.Ephemeral.ConnInfo = v | ||
233 | } | ||
234 | |||
235 | // SetType sets the ephemeral type for the data. This is only required | ||
236 | // for importing. | ||
237 | func (d *ResourceData) SetType(t string) { | ||
238 | d.once.Do(d.init) | ||
239 | d.newState.Ephemeral.Type = t | ||
240 | } | ||
241 | |||
242 | // State returns the new InstanceState after the diff and any Set | ||
243 | // calls. | ||
244 | func (d *ResourceData) State() *terraform.InstanceState { | ||
245 | var result terraform.InstanceState | ||
246 | result.ID = d.Id() | ||
247 | result.Meta = d.meta | ||
248 | |||
249 | // If we have no ID, then this resource doesn't exist and we just | ||
250 | // return nil. | ||
251 | if result.ID == "" { | ||
252 | return nil | ||
253 | } | ||
254 | |||
255 | if d.timeouts != nil { | ||
256 | if err := d.timeouts.StateEncode(&result); err != nil { | ||
257 | log.Printf("[ERR] Error encoding Timeout meta to Instance State: %s", err) | ||
258 | } | ||
259 | } | ||
260 | |||
261 | // Look for a magic key in the schema that determines we skip the | ||
262 | // integrity check of fields existing in the schema, allowing dynamic | ||
263 | // keys to be created. | ||
264 | hasDynamicAttributes := false | ||
265 | for k, _ := range d.schema { | ||
266 | if k == "__has_dynamic_attributes" { | ||
267 | hasDynamicAttributes = true | ||
268 | log.Printf("[INFO] Resource %s has dynamic attributes", result.ID) | ||
269 | } | ||
270 | } | ||
271 | |||
272 | // In order to build the final state attributes, we read the full | ||
273 | // attribute set as a map[string]interface{}, write it to a MapFieldWriter, | ||
274 | // and then use that map. | ||
275 | rawMap := make(map[string]interface{}) | ||
276 | for k := range d.schema { | ||
277 | source := getSourceSet | ||
278 | if d.partial { | ||
279 | source = getSourceState | ||
280 | if _, ok := d.partialMap[k]; ok { | ||
281 | source = getSourceSet | ||
282 | } | ||
283 | } | ||
284 | |||
285 | raw := d.get([]string{k}, source) | ||
286 | if raw.Exists && !raw.Computed { | ||
287 | rawMap[k] = raw.Value | ||
288 | if raw.ValueProcessed != nil { | ||
289 | rawMap[k] = raw.ValueProcessed | ||
290 | } | ||
291 | } | ||
292 | } | ||
293 | |||
294 | mapW := &MapFieldWriter{Schema: d.schema} | ||
295 | if err := mapW.WriteField(nil, rawMap); err != nil { | ||
296 | return nil | ||
297 | } | ||
298 | |||
299 | result.Attributes = mapW.Map() | ||
300 | |||
301 | if hasDynamicAttributes { | ||
302 | // If we have dynamic attributes, just copy the attributes map | ||
303 | // one for one into the result attributes. | ||
304 | for k, v := range d.setWriter.Map() { | ||
305 | // Don't clobber schema values. This limits usage of dynamic | ||
306 | // attributes to names which _do not_ conflict with schema | ||
307 | // keys! | ||
308 | if _, ok := result.Attributes[k]; !ok { | ||
309 | result.Attributes[k] = v | ||
310 | } | ||
311 | } | ||
312 | } | ||
313 | |||
314 | if d.newState != nil { | ||
315 | result.Ephemeral = d.newState.Ephemeral | ||
316 | } | ||
317 | |||
318 | // TODO: This is hacky and we can remove this when we have a proper | ||
319 | // state writer. We should instead have a proper StateFieldWriter | ||
320 | // and use that. | ||
321 | for k, schema := range d.schema { | ||
322 | if schema.Type != TypeMap { | ||
323 | continue | ||
324 | } | ||
325 | |||
326 | if result.Attributes[k] == "" { | ||
327 | delete(result.Attributes, k) | ||
328 | } | ||
329 | } | ||
330 | |||
331 | if v := d.Id(); v != "" { | ||
332 | result.Attributes["id"] = d.Id() | ||
333 | } | ||
334 | |||
335 | if d.state != nil { | ||
336 | result.Tainted = d.state.Tainted | ||
337 | } | ||
338 | |||
339 | return &result | ||
340 | } | ||
341 | |||
342 | // Timeout returns the data for the given timeout key | ||
343 | // Returns a duration of 20 minutes for any key not found, or not found and no default. | ||
344 | func (d *ResourceData) Timeout(key string) time.Duration { | ||
345 | key = strings.ToLower(key) | ||
346 | |||
347 | var timeout *time.Duration | ||
348 | switch key { | ||
349 | case TimeoutCreate: | ||
350 | timeout = d.timeouts.Create | ||
351 | case TimeoutRead: | ||
352 | timeout = d.timeouts.Read | ||
353 | case TimeoutUpdate: | ||
354 | timeout = d.timeouts.Update | ||
355 | case TimeoutDelete: | ||
356 | timeout = d.timeouts.Delete | ||
357 | } | ||
358 | |||
359 | if timeout != nil { | ||
360 | return *timeout | ||
361 | } | ||
362 | |||
363 | if d.timeouts.Default != nil { | ||
364 | return *d.timeouts.Default | ||
365 | } | ||
366 | |||
367 | // Return system default of 20 minutes | ||
368 | return 20 * time.Minute | ||
369 | } | ||
370 | |||
371 | func (d *ResourceData) init() { | ||
372 | // Initialize the field that will store our new state | ||
373 | var copyState terraform.InstanceState | ||
374 | if d.state != nil { | ||
375 | copyState = *d.state.DeepCopy() | ||
376 | } | ||
377 | d.newState = ©State | ||
378 | |||
379 | // Initialize the map for storing set data | ||
380 | d.setWriter = &MapFieldWriter{Schema: d.schema} | ||
381 | |||
382 | // Initialize the reader for getting data from the | ||
383 | // underlying sources (config, diff, etc.) | ||
384 | readers := make(map[string]FieldReader) | ||
385 | var stateAttributes map[string]string | ||
386 | if d.state != nil { | ||
387 | stateAttributes = d.state.Attributes | ||
388 | readers["state"] = &MapFieldReader{ | ||
389 | Schema: d.schema, | ||
390 | Map: BasicMapReader(stateAttributes), | ||
391 | } | ||
392 | } | ||
393 | if d.config != nil { | ||
394 | readers["config"] = &ConfigFieldReader{ | ||
395 | Schema: d.schema, | ||
396 | Config: d.config, | ||
397 | } | ||
398 | } | ||
399 | if d.diff != nil { | ||
400 | readers["diff"] = &DiffFieldReader{ | ||
401 | Schema: d.schema, | ||
402 | Diff: d.diff, | ||
403 | Source: &MultiLevelFieldReader{ | ||
404 | Levels: []string{"state", "config"}, | ||
405 | Readers: readers, | ||
406 | }, | ||
407 | } | ||
408 | } | ||
409 | readers["set"] = &MapFieldReader{ | ||
410 | Schema: d.schema, | ||
411 | Map: BasicMapReader(d.setWriter.Map()), | ||
412 | } | ||
413 | d.multiReader = &MultiLevelFieldReader{ | ||
414 | Levels: []string{ | ||
415 | "state", | ||
416 | "config", | ||
417 | "diff", | ||
418 | "set", | ||
419 | }, | ||
420 | |||
421 | Readers: readers, | ||
422 | } | ||
423 | } | ||
424 | |||
425 | func (d *ResourceData) diffChange( | ||
426 | k string) (interface{}, interface{}, bool, bool) { | ||
427 | // Get the change between the state and the config. | ||
428 | o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact) | ||
429 | if !o.Exists { | ||
430 | o.Value = nil | ||
431 | } | ||
432 | if !n.Exists { | ||
433 | n.Value = nil | ||
434 | } | ||
435 | |||
436 | // Return the old, new, and whether there is a change | ||
437 | return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed | ||
438 | } | ||
439 | |||
440 | func (d *ResourceData) getChange( | ||
441 | k string, | ||
442 | oldLevel getSource, | ||
443 | newLevel getSource) (getResult, getResult) { | ||
444 | var parts, parts2 []string | ||
445 | if k != "" { | ||
446 | parts = strings.Split(k, ".") | ||
447 | parts2 = strings.Split(k, ".") | ||
448 | } | ||
449 | |||
450 | o := d.get(parts, oldLevel) | ||
451 | n := d.get(parts2, newLevel) | ||
452 | return o, n | ||
453 | } | ||
454 | |||
455 | func (d *ResourceData) get(addr []string, source getSource) getResult { | ||
456 | d.once.Do(d.init) | ||
457 | |||
458 | level := "set" | ||
459 | flags := source & ^getSourceLevelMask | ||
460 | exact := flags&getSourceExact != 0 | ||
461 | source = source & getSourceLevelMask | ||
462 | if source >= getSourceSet { | ||
463 | level = "set" | ||
464 | } else if source >= getSourceDiff { | ||
465 | level = "diff" | ||
466 | } else if source >= getSourceConfig { | ||
467 | level = "config" | ||
468 | } else { | ||
469 | level = "state" | ||
470 | } | ||
471 | |||
472 | var result FieldReadResult | ||
473 | var err error | ||
474 | if exact { | ||
475 | result, err = d.multiReader.ReadFieldExact(addr, level) | ||
476 | } else { | ||
477 | result, err = d.multiReader.ReadFieldMerge(addr, level) | ||
478 | } | ||
479 | if err != nil { | ||
480 | panic(err) | ||
481 | } | ||
482 | |||
483 | // If the result doesn't exist, then we set the value to the zero value | ||
484 | var schema *Schema | ||
485 | if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 { | ||
486 | schema = schemaL[len(schemaL)-1] | ||
487 | } | ||
488 | |||
489 | if result.Value == nil && schema != nil { | ||
490 | result.Value = result.ValueOrZero(schema) | ||
491 | } | ||
492 | |||
493 | // Transform the FieldReadResult into a getResult. It might be worth | ||
494 | // merging these two structures one day. | ||
495 | return getResult{ | ||
496 | Value: result.Value, | ||
497 | ValueProcessed: result.ValueProcessed, | ||
498 | Computed: result.Computed, | ||
499 | Exists: result.Exists, | ||
500 | Schema: schema, | ||
501 | } | ||
502 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data_get_source.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data_get_source.go new file mode 100644 index 0000000..7dd655d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data_get_source.go | |||
@@ -0,0 +1,17 @@ | |||
1 | package schema | ||
2 | |||
3 | //go:generate stringer -type=getSource resource_data_get_source.go | ||
4 | |||
5 | // getSource represents the level we want to get for a value (internally). | ||
6 | // Any source less than or equal to the level will be loaded (whichever | ||
7 | // has a value first). | ||
8 | type getSource byte | ||
9 | |||
10 | const ( | ||
11 | getSourceState getSource = 1 << iota | ||
12 | getSourceConfig | ||
13 | getSourceDiff | ||
14 | getSourceSet | ||
15 | getSourceExact // Only get from the _exact_ level | ||
16 | getSourceLevelMask getSource = getSourceState | getSourceConfig | getSourceDiff | getSourceSet | ||
17 | ) | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_importer.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_importer.go new file mode 100644 index 0000000..5dada3c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_importer.go | |||
@@ -0,0 +1,52 @@ | |||
1 | package schema | ||
2 | |||
3 | // ResourceImporter defines how a resource is imported in Terraform. This | ||
4 | // can be set onto a Resource struct to make it Importable. Not all resources | ||
5 | // have to be importable; if a Resource doesn't have a ResourceImporter then | ||
6 | // it won't be importable. | ||
7 | // | ||
8 | // "Importing" in Terraform is the process of taking an already-created | ||
9 | // resource and bringing it under Terraform management. This can include | ||
10 | // updating Terraform state, generating Terraform configuration, etc. | ||
11 | type ResourceImporter struct { | ||
12 | // The functions below must all be implemented for importing to work. | ||
13 | |||
14 | // State is called to convert an ID to one or more InstanceState to | ||
15 | // insert into the Terraform state. If this isn't specified, then | ||
16 | // the ID is passed straight through. | ||
17 | State StateFunc | ||
18 | } | ||
19 | |||
20 | // StateFunc is the function called to import a resource into the | ||
21 | // Terraform state. It is given a ResourceData with only ID set. This | ||
22 | // ID is going to be an arbitrary value given by the user and may not map | ||
23 | // directly to the ID format that the resource expects, so that should | ||
24 | // be validated. | ||
25 | // | ||
26 | // This should return a slice of ResourceData that turn into the state | ||
27 | // that was imported. This might be as simple as returning only the argument | ||
28 | // that was given to the function. In other cases (such as AWS security groups), | ||
29 | // an import may fan out to multiple resources and this will have to return | ||
30 | // multiple. | ||
31 | // | ||
32 | // To create the ResourceData structures for other resource types (if | ||
33 | // you have to), instantiate your resource and call the Data function. | ||
34 | type StateFunc func(*ResourceData, interface{}) ([]*ResourceData, error) | ||
35 | |||
36 | // InternalValidate should be called to validate the structure of this | ||
37 | // importer. This should be called in a unit test. | ||
38 | // | ||
39 | // Resource.InternalValidate() will automatically call this, so this doesn't | ||
40 | // need to be called manually. Further, Resource.InternalValidate() is | ||
41 | // automatically called by Provider.InternalValidate(), so you only need | ||
42 | // to internal validate the provider. | ||
43 | func (r *ResourceImporter) InternalValidate() error { | ||
44 | return nil | ||
45 | } | ||
46 | |||
47 | // ImportStatePassthrough is an implementation of StateFunc that can be | ||
48 | // used to simply pass the ID directly through. This should be used only | ||
49 | // in the case that an ID-only refresh is possible. | ||
50 | func ImportStatePassthrough(d *ResourceData, m interface{}) ([]*ResourceData, error) { | ||
51 | return []*ResourceData{d}, nil | ||
52 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go new file mode 100644 index 0000000..445819f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go | |||
@@ -0,0 +1,237 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "log" | ||
6 | "time" | ||
7 | |||
8 | "github.com/hashicorp/terraform/terraform" | ||
9 | "github.com/mitchellh/copystructure" | ||
10 | ) | ||
11 | |||
12 | const TimeoutKey = "e2bfb730-ecaa-11e6-8f88-34363bc7c4c0" | ||
13 | const TimeoutsConfigKey = "timeouts" | ||
14 | |||
15 | const ( | ||
16 | TimeoutCreate = "create" | ||
17 | TimeoutRead = "read" | ||
18 | TimeoutUpdate = "update" | ||
19 | TimeoutDelete = "delete" | ||
20 | TimeoutDefault = "default" | ||
21 | ) | ||
22 | |||
23 | func timeoutKeys() []string { | ||
24 | return []string{ | ||
25 | TimeoutCreate, | ||
26 | TimeoutRead, | ||
27 | TimeoutUpdate, | ||
28 | TimeoutDelete, | ||
29 | TimeoutDefault, | ||
30 | } | ||
31 | } | ||
32 | |||
33 | // could be time.Duration, int64 or float64 | ||
34 | func DefaultTimeout(tx interface{}) *time.Duration { | ||
35 | var td time.Duration | ||
36 | switch raw := tx.(type) { | ||
37 | case time.Duration: | ||
38 | return &raw | ||
39 | case int64: | ||
40 | td = time.Duration(raw) | ||
41 | case float64: | ||
42 | td = time.Duration(int64(raw)) | ||
43 | default: | ||
44 | log.Printf("[WARN] Unknown type in DefaultTimeout: %#v", tx) | ||
45 | } | ||
46 | return &td | ||
47 | } | ||
48 | |||
49 | type ResourceTimeout struct { | ||
50 | Create, Read, Update, Delete, Default *time.Duration | ||
51 | } | ||
52 | |||
53 | // ConfigDecode takes a schema and the configuration (available in Diff) and | ||
54 | // validates, parses the timeouts into `t` | ||
55 | func (t *ResourceTimeout) ConfigDecode(s *Resource, c *terraform.ResourceConfig) error { | ||
56 | if s.Timeouts != nil { | ||
57 | raw, err := copystructure.Copy(s.Timeouts) | ||
58 | if err != nil { | ||
59 | log.Printf("[DEBUG] Error with deep copy: %s", err) | ||
60 | } | ||
61 | *t = *raw.(*ResourceTimeout) | ||
62 | } | ||
63 | |||
64 | if raw, ok := c.Config[TimeoutsConfigKey]; ok { | ||
65 | if configTimeouts, ok := raw.([]map[string]interface{}); ok { | ||
66 | for _, timeoutValues := range configTimeouts { | ||
67 | // loop through each Timeout given in the configuration and validate they | ||
68 | // the Timeout defined in the resource | ||
69 | for timeKey, timeValue := range timeoutValues { | ||
70 | // validate that we're dealing with the normal CRUD actions | ||
71 | var found bool | ||
72 | for _, key := range timeoutKeys() { | ||
73 | if timeKey == key { | ||
74 | found = true | ||
75 | break | ||
76 | } | ||
77 | } | ||
78 | |||
79 | if !found { | ||
80 | return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey) | ||
81 | } | ||
82 | |||
83 | // Get timeout | ||
84 | rt, err := time.ParseDuration(timeValue.(string)) | ||
85 | if err != nil { | ||
86 | return fmt.Errorf("Error parsing Timeout for (%s): %s", timeKey, err) | ||
87 | } | ||
88 | |||
89 | var timeout *time.Duration | ||
90 | switch timeKey { | ||
91 | case TimeoutCreate: | ||
92 | timeout = t.Create | ||
93 | case TimeoutUpdate: | ||
94 | timeout = t.Update | ||
95 | case TimeoutRead: | ||
96 | timeout = t.Read | ||
97 | case TimeoutDelete: | ||
98 | timeout = t.Delete | ||
99 | case TimeoutDefault: | ||
100 | timeout = t.Default | ||
101 | } | ||
102 | |||
103 | // If the resource has not delcared this in the definition, then error | ||
104 | // with an unsupported message | ||
105 | if timeout == nil { | ||
106 | return unsupportedTimeoutKeyError(timeKey) | ||
107 | } | ||
108 | |||
109 | *timeout = rt | ||
110 | } | ||
111 | } | ||
112 | } else { | ||
113 | log.Printf("[WARN] Invalid Timeout structure found, skipping timeouts") | ||
114 | } | ||
115 | } | ||
116 | |||
117 | return nil | ||
118 | } | ||
119 | |||
120 | func unsupportedTimeoutKeyError(key string) error { | ||
121 | return fmt.Errorf("Timeout Key (%s) is not supported", key) | ||
122 | } | ||
123 | |||
124 | // DiffEncode, StateEncode, and MetaDecode are analogous to the Go stdlib JSONEncoder | ||
125 | // interface: they encode/decode a timeouts struct from an instance diff, which is | ||
126 | // where the timeout data is stored after a diff to pass into Apply. | ||
127 | // | ||
128 | // StateEncode encodes the timeout into the ResourceData's InstanceState for | ||
129 | // saving to state | ||
130 | // | ||
131 | func (t *ResourceTimeout) DiffEncode(id *terraform.InstanceDiff) error { | ||
132 | return t.metaEncode(id) | ||
133 | } | ||
134 | |||
135 | func (t *ResourceTimeout) StateEncode(is *terraform.InstanceState) error { | ||
136 | return t.metaEncode(is) | ||
137 | } | ||
138 | |||
139 | // metaEncode encodes the ResourceTimeout into a map[string]interface{} format | ||
140 | // and stores it in the Meta field of the interface it's given. | ||
141 | // Assumes the interface is either *terraform.InstanceState or | ||
142 | // *terraform.InstanceDiff, returns an error otherwise | ||
143 | func (t *ResourceTimeout) metaEncode(ids interface{}) error { | ||
144 | m := make(map[string]interface{}) | ||
145 | |||
146 | if t.Create != nil { | ||
147 | m[TimeoutCreate] = t.Create.Nanoseconds() | ||
148 | } | ||
149 | if t.Read != nil { | ||
150 | m[TimeoutRead] = t.Read.Nanoseconds() | ||
151 | } | ||
152 | if t.Update != nil { | ||
153 | m[TimeoutUpdate] = t.Update.Nanoseconds() | ||
154 | } | ||
155 | if t.Delete != nil { | ||
156 | m[TimeoutDelete] = t.Delete.Nanoseconds() | ||
157 | } | ||
158 | if t.Default != nil { | ||
159 | m[TimeoutDefault] = t.Default.Nanoseconds() | ||
160 | // for any key above that is nil, if default is specified, we need to | ||
161 | // populate it with the default | ||
162 | for _, k := range timeoutKeys() { | ||
163 | if _, ok := m[k]; !ok { | ||
164 | m[k] = t.Default.Nanoseconds() | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | |||
169 | // only add the Timeout to the Meta if we have values | ||
170 | if len(m) > 0 { | ||
171 | switch instance := ids.(type) { | ||
172 | case *terraform.InstanceDiff: | ||
173 | if instance.Meta == nil { | ||
174 | instance.Meta = make(map[string]interface{}) | ||
175 | } | ||
176 | instance.Meta[TimeoutKey] = m | ||
177 | case *terraform.InstanceState: | ||
178 | if instance.Meta == nil { | ||
179 | instance.Meta = make(map[string]interface{}) | ||
180 | } | ||
181 | instance.Meta[TimeoutKey] = m | ||
182 | default: | ||
183 | return fmt.Errorf("Error matching type for Diff Encode") | ||
184 | } | ||
185 | } | ||
186 | |||
187 | return nil | ||
188 | } | ||
189 | |||
190 | func (t *ResourceTimeout) StateDecode(id *terraform.InstanceState) error { | ||
191 | return t.metaDecode(id) | ||
192 | } | ||
193 | func (t *ResourceTimeout) DiffDecode(is *terraform.InstanceDiff) error { | ||
194 | return t.metaDecode(is) | ||
195 | } | ||
196 | |||
197 | func (t *ResourceTimeout) metaDecode(ids interface{}) error { | ||
198 | var rawMeta interface{} | ||
199 | var ok bool | ||
200 | switch rawInstance := ids.(type) { | ||
201 | case *terraform.InstanceDiff: | ||
202 | rawMeta, ok = rawInstance.Meta[TimeoutKey] | ||
203 | if !ok { | ||
204 | return nil | ||
205 | } | ||
206 | case *terraform.InstanceState: | ||
207 | rawMeta, ok = rawInstance.Meta[TimeoutKey] | ||
208 | if !ok { | ||
209 | return nil | ||
210 | } | ||
211 | default: | ||
212 | return fmt.Errorf("Unknown or unsupported type in metaDecode: %#v", ids) | ||
213 | } | ||
214 | |||
215 | times := rawMeta.(map[string]interface{}) | ||
216 | if len(times) == 0 { | ||
217 | return nil | ||
218 | } | ||
219 | |||
220 | if v, ok := times[TimeoutCreate]; ok { | ||
221 | t.Create = DefaultTimeout(v) | ||
222 | } | ||
223 | if v, ok := times[TimeoutRead]; ok { | ||
224 | t.Read = DefaultTimeout(v) | ||
225 | } | ||
226 | if v, ok := times[TimeoutUpdate]; ok { | ||
227 | t.Update = DefaultTimeout(v) | ||
228 | } | ||
229 | if v, ok := times[TimeoutDelete]; ok { | ||
230 | t.Delete = DefaultTimeout(v) | ||
231 | } | ||
232 | if v, ok := times[TimeoutDefault]; ok { | ||
233 | t.Default = DefaultTimeout(v) | ||
234 | } | ||
235 | |||
236 | return nil | ||
237 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go new file mode 100644 index 0000000..32d1721 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go | |||
@@ -0,0 +1,1537 @@ | |||
1 | // schema is a high-level framework for easily writing new providers | ||
2 | // for Terraform. Usage of schema is recommended over attempting to write | ||
3 | // to the low-level plugin interfaces manually. | ||
4 | // | ||
5 | // schema breaks down provider creation into simple CRUD operations for | ||
6 | // resources. The logic of diffing, destroying before creating, updating | ||
7 | // or creating, etc. is all handled by the framework. The plugin author | ||
8 | // only needs to implement a configuration schema and the CRUD operations and | ||
9 | // everything else is meant to just work. | ||
10 | // | ||
11 | // A good starting point is to view the Provider structure. | ||
12 | package schema | ||
13 | |||
14 | import ( | ||
15 | "fmt" | ||
16 | "os" | ||
17 | "reflect" | ||
18 | "sort" | ||
19 | "strconv" | ||
20 | "strings" | ||
21 | |||
22 | "github.com/hashicorp/terraform/terraform" | ||
23 | "github.com/mitchellh/mapstructure" | ||
24 | ) | ||
25 | |||
26 | // type used for schema package context keys | ||
27 | type contextKey string | ||
28 | |||
29 | // Schema is used to describe the structure of a value. | ||
30 | // | ||
31 | // Read the documentation of the struct elements for important details. | ||
32 | type Schema struct { | ||
33 | // Type is the type of the value and must be one of the ValueType values. | ||
34 | // | ||
35 | // This type not only determines what type is expected/valid in configuring | ||
36 | // this value, but also what type is returned when ResourceData.Get is | ||
37 | // called. The types returned by Get are: | ||
38 | // | ||
39 | // TypeBool - bool | ||
40 | // TypeInt - int | ||
41 | // TypeFloat - float64 | ||
42 | // TypeString - string | ||
43 | // TypeList - []interface{} | ||
44 | // TypeMap - map[string]interface{} | ||
45 | // TypeSet - *schema.Set | ||
46 | // | ||
47 | Type ValueType | ||
48 | |||
49 | // If one of these is set, then this item can come from the configuration. | ||
50 | // Both cannot be set. If Optional is set, the value is optional. If | ||
51 | // Required is set, the value is required. | ||
52 | // | ||
53 | // One of these must be set if the value is not computed. That is: | ||
54 | // value either comes from the config, is computed, or is both. | ||
55 | Optional bool | ||
56 | Required bool | ||
57 | |||
58 | // If this is non-nil, the provided function will be used during diff | ||
59 | // of this field. If this is nil, a default diff for the type of the | ||
60 | // schema will be used. | ||
61 | // | ||
62 | // This allows comparison based on something other than primitive, list | ||
63 | // or map equality - for example SSH public keys may be considered | ||
64 | // equivalent regardless of trailing whitespace. | ||
65 | DiffSuppressFunc SchemaDiffSuppressFunc | ||
66 | |||
67 | // If this is non-nil, then this will be a default value that is used | ||
68 | // when this item is not set in the configuration. | ||
69 | // | ||
70 | // DefaultFunc can be specified to compute a dynamic default. | ||
71 | // Only one of Default or DefaultFunc can be set. If DefaultFunc is | ||
72 | // used then its return value should be stable to avoid generating | ||
73 | // confusing/perpetual diffs. | ||
74 | // | ||
75 | // Changing either Default or the return value of DefaultFunc can be | ||
76 | // a breaking change, especially if the attribute in question has | ||
77 | // ForceNew set. If a default needs to change to align with changing | ||
78 | // assumptions in an upstream API then it may be necessary to also use | ||
79 | // the MigrateState function on the resource to change the state to match, | ||
80 | // or have the Read function adjust the state value to align with the | ||
81 | // new default. | ||
82 | // | ||
83 | // If Required is true above, then Default cannot be set. DefaultFunc | ||
84 | // can be set with Required. If the DefaultFunc returns nil, then there | ||
85 | // will be no default and the user will be asked to fill it in. | ||
86 | // | ||
87 | // If either of these is set, then the user won't be asked for input | ||
88 | // for this key if the default is not nil. | ||
89 | Default interface{} | ||
90 | DefaultFunc SchemaDefaultFunc | ||
91 | |||
92 | // Description is used as the description for docs or asking for user | ||
93 | // input. It should be relatively short (a few sentences max) and should | ||
94 | // be formatted to fit a CLI. | ||
95 | Description string | ||
96 | |||
97 | // InputDefault is the default value to use for when inputs are requested. | ||
98 | // This differs from Default in that if Default is set, no input is | ||
99 | // asked for. If Input is asked, this will be the default value offered. | ||
100 | InputDefault string | ||
101 | |||
102 | // The fields below relate to diffs. | ||
103 | // | ||
104 | // If Computed is true, then the result of this value is computed | ||
105 | // (unless specified by config) on creation. | ||
106 | // | ||
107 | // If ForceNew is true, then a change in this resource necessitates | ||
108 | // the creation of a new resource. | ||
109 | // | ||
110 | // StateFunc is a function called to change the value of this before | ||
111 | // storing it in the state (and likewise before comparing for diffs). | ||
112 | // The use for this is for example with large strings, you may want | ||
113 | // to simply store the hash of it. | ||
114 | Computed bool | ||
115 | ForceNew bool | ||
116 | StateFunc SchemaStateFunc | ||
117 | |||
118 | // The following fields are only set for a TypeList or TypeSet Type. | ||
119 | // | ||
120 | // Elem must be either a *Schema or a *Resource only if the Type is | ||
121 | // TypeList, and represents what the element type is. If it is *Schema, | ||
122 | // the element type is just a simple value. If it is *Resource, the | ||
123 | // element type is a complex structure, potentially with its own lifecycle. | ||
124 | // | ||
125 | // MaxItems defines a maximum amount of items that can exist within a | ||
126 | // TypeSet or TypeList. Specific use cases would be if a TypeSet is being | ||
127 | // used to wrap a complex structure, however more than one instance would | ||
128 | // cause instability. | ||
129 | // | ||
130 | // MinItems defines a minimum amount of items that can exist within a | ||
131 | // TypeSet or TypeList. Specific use cases would be if a TypeSet is being | ||
132 | // used to wrap a complex structure, however less than one instance would | ||
133 | // cause instability. | ||
134 | // | ||
135 | // PromoteSingle, if true, will allow single elements to be standalone | ||
136 | // and promote them to a list. For example "foo" would be promoted to | ||
137 | // ["foo"] automatically. This is primarily for legacy reasons and the | ||
138 | // ambiguity is not recommended for new usage. Promotion is only allowed | ||
139 | // for primitive element types. | ||
140 | Elem interface{} | ||
141 | MaxItems int | ||
142 | MinItems int | ||
143 | PromoteSingle bool | ||
144 | |||
145 | // The following fields are only valid for a TypeSet type. | ||
146 | // | ||
147 | // Set defines a function to determine the unique ID of an item so that | ||
148 | // a proper set can be built. | ||
149 | Set SchemaSetFunc | ||
150 | |||
151 | // ComputedWhen is a set of queries on the configuration. Whenever any | ||
152 | // of these things is changed, it will require a recompute (this requires | ||
153 | // that Computed is set to true). | ||
154 | // | ||
155 | // NOTE: This currently does not work. | ||
156 | ComputedWhen []string | ||
157 | |||
158 | // ConflictsWith is a set of schema keys that conflict with this schema. | ||
159 | // This will only check that they're set in the _config_. This will not | ||
160 | // raise an error for a malfunctioning resource that sets a conflicting | ||
161 | // key. | ||
162 | ConflictsWith []string | ||
163 | |||
164 | // When Deprecated is set, this attribute is deprecated. | ||
165 | // | ||
166 | // A deprecated field still works, but will probably stop working in near | ||
167 | // future. This string is the message shown to the user with instructions on | ||
168 | // how to address the deprecation. | ||
169 | Deprecated string | ||
170 | |||
171 | // When Removed is set, this attribute has been removed from the schema | ||
172 | // | ||
173 | // Removed attributes can be left in the Schema to generate informative error | ||
174 | // messages for the user when they show up in resource configurations. | ||
175 | // This string is the message shown to the user with instructions on | ||
176 | // what do to about the removed attribute. | ||
177 | Removed string | ||
178 | |||
179 | // ValidateFunc allows individual fields to define arbitrary validation | ||
180 | // logic. It is yielded the provided config value as an interface{} that is | ||
181 | // guaranteed to be of the proper Schema type, and it can yield warnings or | ||
182 | // errors based on inspection of that value. | ||
183 | // | ||
184 | // ValidateFunc currently only works for primitive types. | ||
185 | ValidateFunc SchemaValidateFunc | ||
186 | |||
187 | // Sensitive ensures that the attribute's value does not get displayed in | ||
188 | // logs or regular output. It should be used for passwords or other | ||
189 | // secret fields. Future versions of Terraform may encrypt these | ||
190 | // values. | ||
191 | Sensitive bool | ||
192 | } | ||
193 | |||
194 | // SchemaDiffSuppresFunc is a function which can be used to determine | ||
195 | // whether a detected diff on a schema element is "valid" or not, and | ||
196 | // suppress it from the plan if necessary. | ||
197 | // | ||
198 | // Return true if the diff should be suppressed, false to retain it. | ||
199 | type SchemaDiffSuppressFunc func(k, old, new string, d *ResourceData) bool | ||
200 | |||
201 | // SchemaDefaultFunc is a function called to return a default value for | ||
202 | // a field. | ||
203 | type SchemaDefaultFunc func() (interface{}, error) | ||
204 | |||
205 | // EnvDefaultFunc is a helper function that returns the value of the | ||
206 | // given environment variable, if one exists, or the default value | ||
207 | // otherwise. | ||
208 | func EnvDefaultFunc(k string, dv interface{}) SchemaDefaultFunc { | ||
209 | return func() (interface{}, error) { | ||
210 | if v := os.Getenv(k); v != "" { | ||
211 | return v, nil | ||
212 | } | ||
213 | |||
214 | return dv, nil | ||
215 | } | ||
216 | } | ||
217 | |||
218 | // MultiEnvDefaultFunc is a helper function that returns the value of the first | ||
219 | // environment variable in the given list that returns a non-empty value. If | ||
220 | // none of the environment variables return a value, the default value is | ||
221 | // returned. | ||
222 | func MultiEnvDefaultFunc(ks []string, dv interface{}) SchemaDefaultFunc { | ||
223 | return func() (interface{}, error) { | ||
224 | for _, k := range ks { | ||
225 | if v := os.Getenv(k); v != "" { | ||
226 | return v, nil | ||
227 | } | ||
228 | } | ||
229 | return dv, nil | ||
230 | } | ||
231 | } | ||
232 | |||
233 | // SchemaSetFunc is a function that must return a unique ID for the given | ||
234 | // element. This unique ID is used to store the element in a hash. | ||
235 | type SchemaSetFunc func(interface{}) int | ||
236 | |||
237 | // SchemaStateFunc is a function used to convert some type to a string | ||
238 | // to be stored in the state. | ||
239 | type SchemaStateFunc func(interface{}) string | ||
240 | |||
241 | // SchemaValidateFunc is a function used to validate a single field in the | ||
242 | // schema. | ||
243 | type SchemaValidateFunc func(interface{}, string) ([]string, []error) | ||
244 | |||
245 | func (s *Schema) GoString() string { | ||
246 | return fmt.Sprintf("*%#v", *s) | ||
247 | } | ||
248 | |||
249 | // Returns a default value for this schema by either reading Default or | ||
250 | // evaluating DefaultFunc. If neither of these are defined, returns nil. | ||
251 | func (s *Schema) DefaultValue() (interface{}, error) { | ||
252 | if s.Default != nil { | ||
253 | return s.Default, nil | ||
254 | } | ||
255 | |||
256 | if s.DefaultFunc != nil { | ||
257 | defaultValue, err := s.DefaultFunc() | ||
258 | if err != nil { | ||
259 | return nil, fmt.Errorf("error loading default: %s", err) | ||
260 | } | ||
261 | return defaultValue, nil | ||
262 | } | ||
263 | |||
264 | return nil, nil | ||
265 | } | ||
266 | |||
267 | // Returns a zero value for the schema. | ||
268 | func (s *Schema) ZeroValue() interface{} { | ||
269 | // If it's a set then we'll do a bit of extra work to provide the | ||
270 | // right hashing function in our empty value. | ||
271 | if s.Type == TypeSet { | ||
272 | setFunc := s.Set | ||
273 | if setFunc == nil { | ||
274 | // Default set function uses the schema to hash the whole value | ||
275 | elem := s.Elem | ||
276 | switch t := elem.(type) { | ||
277 | case *Schema: | ||
278 | setFunc = HashSchema(t) | ||
279 | case *Resource: | ||
280 | setFunc = HashResource(t) | ||
281 | default: | ||
282 | panic("invalid set element type") | ||
283 | } | ||
284 | } | ||
285 | return &Set{F: setFunc} | ||
286 | } else { | ||
287 | return s.Type.Zero() | ||
288 | } | ||
289 | } | ||
290 | |||
291 | func (s *Schema) finalizeDiff( | ||
292 | d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff { | ||
293 | if d == nil { | ||
294 | return d | ||
295 | } | ||
296 | |||
297 | if s.Type == TypeBool { | ||
298 | normalizeBoolString := func(s string) string { | ||
299 | switch s { | ||
300 | case "0": | ||
301 | return "false" | ||
302 | case "1": | ||
303 | return "true" | ||
304 | } | ||
305 | return s | ||
306 | } | ||
307 | d.Old = normalizeBoolString(d.Old) | ||
308 | d.New = normalizeBoolString(d.New) | ||
309 | } | ||
310 | |||
311 | if s.Computed && !d.NewRemoved && d.New == "" { | ||
312 | // Computed attribute without a new value set | ||
313 | d.NewComputed = true | ||
314 | } | ||
315 | |||
316 | if s.ForceNew { | ||
317 | // ForceNew, mark that this field is requiring new under the | ||
318 | // following conditions, explained below: | ||
319 | // | ||
320 | // * Old != New - There is a change in value. This field | ||
321 | // is therefore causing a new resource. | ||
322 | // | ||
323 | // * NewComputed - This field is being computed, hence a | ||
324 | // potential change in value, mark as causing a new resource. | ||
325 | d.RequiresNew = d.Old != d.New || d.NewComputed | ||
326 | } | ||
327 | |||
328 | if d.NewRemoved { | ||
329 | return d | ||
330 | } | ||
331 | |||
332 | if s.Computed { | ||
333 | if d.Old != "" && d.New == "" { | ||
334 | // This is a computed value with an old value set already, | ||
335 | // just let it go. | ||
336 | return nil | ||
337 | } | ||
338 | |||
339 | if d.New == "" { | ||
340 | // Computed attribute without a new value set | ||
341 | d.NewComputed = true | ||
342 | } | ||
343 | } | ||
344 | |||
345 | if s.Sensitive { | ||
346 | // Set the Sensitive flag so output is hidden in the UI | ||
347 | d.Sensitive = true | ||
348 | } | ||
349 | |||
350 | return d | ||
351 | } | ||
352 | |||
353 | // schemaMap is a wrapper that adds nice functions on top of schemas. | ||
354 | type schemaMap map[string]*Schema | ||
355 | |||
356 | // Data returns a ResourceData for the given schema, state, and diff. | ||
357 | // | ||
358 | // The diff is optional. | ||
359 | func (m schemaMap) Data( | ||
360 | s *terraform.InstanceState, | ||
361 | d *terraform.InstanceDiff) (*ResourceData, error) { | ||
362 | return &ResourceData{ | ||
363 | schema: m, | ||
364 | state: s, | ||
365 | diff: d, | ||
366 | }, nil | ||
367 | } | ||
368 | |||
369 | // Diff returns the diff for a resource given the schema map, | ||
370 | // state, and configuration. | ||
371 | func (m schemaMap) Diff( | ||
372 | s *terraform.InstanceState, | ||
373 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | ||
374 | result := new(terraform.InstanceDiff) | ||
375 | result.Attributes = make(map[string]*terraform.ResourceAttrDiff) | ||
376 | |||
377 | // Make sure to mark if the resource is tainted | ||
378 | if s != nil { | ||
379 | result.DestroyTainted = s.Tainted | ||
380 | } | ||
381 | |||
382 | d := &ResourceData{ | ||
383 | schema: m, | ||
384 | state: s, | ||
385 | config: c, | ||
386 | } | ||
387 | |||
388 | for k, schema := range m { | ||
389 | err := m.diff(k, schema, result, d, false) | ||
390 | if err != nil { | ||
391 | return nil, err | ||
392 | } | ||
393 | } | ||
394 | |||
395 | // If the diff requires a new resource, then we recompute the diff | ||
396 | // so we have the complete new resource diff, and preserve the | ||
397 | // RequiresNew fields where necessary so the user knows exactly what | ||
398 | // caused that. | ||
399 | if result.RequiresNew() { | ||
400 | // Create the new diff | ||
401 | result2 := new(terraform.InstanceDiff) | ||
402 | result2.Attributes = make(map[string]*terraform.ResourceAttrDiff) | ||
403 | |||
404 | // Preserve the DestroyTainted flag | ||
405 | result2.DestroyTainted = result.DestroyTainted | ||
406 | |||
407 | // Reset the data to not contain state. We have to call init() | ||
408 | // again in order to reset the FieldReaders. | ||
409 | d.state = nil | ||
410 | d.init() | ||
411 | |||
412 | // Perform the diff again | ||
413 | for k, schema := range m { | ||
414 | err := m.diff(k, schema, result2, d, false) | ||
415 | if err != nil { | ||
416 | return nil, err | ||
417 | } | ||
418 | } | ||
419 | |||
420 | // Force all the fields to not force a new since we know what we | ||
421 | // want to force new. | ||
422 | for k, attr := range result2.Attributes { | ||
423 | if attr == nil { | ||
424 | continue | ||
425 | } | ||
426 | |||
427 | if attr.RequiresNew { | ||
428 | attr.RequiresNew = false | ||
429 | } | ||
430 | |||
431 | if s != nil { | ||
432 | attr.Old = s.Attributes[k] | ||
433 | } | ||
434 | } | ||
435 | |||
436 | // Now copy in all the requires new diffs... | ||
437 | for k, attr := range result.Attributes { | ||
438 | if attr == nil { | ||
439 | continue | ||
440 | } | ||
441 | |||
442 | newAttr, ok := result2.Attributes[k] | ||
443 | if !ok { | ||
444 | newAttr = attr | ||
445 | } | ||
446 | |||
447 | if attr.RequiresNew { | ||
448 | newAttr.RequiresNew = true | ||
449 | } | ||
450 | |||
451 | result2.Attributes[k] = newAttr | ||
452 | } | ||
453 | |||
454 | // And set the diff! | ||
455 | result = result2 | ||
456 | } | ||
457 | |||
458 | // Remove any nil diffs just to keep things clean | ||
459 | for k, v := range result.Attributes { | ||
460 | if v == nil { | ||
461 | delete(result.Attributes, k) | ||
462 | } | ||
463 | } | ||
464 | |||
465 | // Go through and detect all of the ComputedWhens now that we've | ||
466 | // finished the diff. | ||
467 | // TODO | ||
468 | |||
469 | if result.Empty() { | ||
470 | // If we don't have any diff elements, just return nil | ||
471 | return nil, nil | ||
472 | } | ||
473 | |||
474 | return result, nil | ||
475 | } | ||
476 | |||
477 | // Input implements the terraform.ResourceProvider method by asking | ||
478 | // for input for required configuration keys that don't have a value. | ||
479 | func (m schemaMap) Input( | ||
480 | input terraform.UIInput, | ||
481 | c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { | ||
482 | keys := make([]string, 0, len(m)) | ||
483 | for k, _ := range m { | ||
484 | keys = append(keys, k) | ||
485 | } | ||
486 | sort.Strings(keys) | ||
487 | |||
488 | for _, k := range keys { | ||
489 | v := m[k] | ||
490 | |||
491 | // Skip things that don't require config, if that is even valid | ||
492 | // for a provider schema. | ||
493 | // Required XOR Optional must always be true to validate, so we only | ||
494 | // need to check one. | ||
495 | if v.Optional { | ||
496 | continue | ||
497 | } | ||
498 | |||
499 | // Deprecated fields should never prompt | ||
500 | if v.Deprecated != "" { | ||
501 | continue | ||
502 | } | ||
503 | |||
504 | // Skip things that have a value of some sort already | ||
505 | if _, ok := c.Raw[k]; ok { | ||
506 | continue | ||
507 | } | ||
508 | |||
509 | // Skip if it has a default value | ||
510 | defaultValue, err := v.DefaultValue() | ||
511 | if err != nil { | ||
512 | return nil, fmt.Errorf("%s: error loading default: %s", k, err) | ||
513 | } | ||
514 | if defaultValue != nil { | ||
515 | continue | ||
516 | } | ||
517 | |||
518 | var value interface{} | ||
519 | switch v.Type { | ||
520 | case TypeBool, TypeInt, TypeFloat, TypeSet, TypeList: | ||
521 | continue | ||
522 | case TypeString: | ||
523 | value, err = m.inputString(input, k, v) | ||
524 | default: | ||
525 | panic(fmt.Sprintf("Unknown type for input: %#v", v.Type)) | ||
526 | } | ||
527 | |||
528 | if err != nil { | ||
529 | return nil, fmt.Errorf( | ||
530 | "%s: %s", k, err) | ||
531 | } | ||
532 | |||
533 | c.Config[k] = value | ||
534 | } | ||
535 | |||
536 | return c, nil | ||
537 | } | ||
538 | |||
539 | // Validate validates the configuration against this schema mapping. | ||
540 | func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) { | ||
541 | return m.validateObject("", m, c) | ||
542 | } | ||
543 | |||
544 | // InternalValidate validates the format of this schema. This should be called | ||
545 | // from a unit test (and not in user-path code) to verify that a schema | ||
546 | // is properly built. | ||
547 | func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error { | ||
548 | if topSchemaMap == nil { | ||
549 | topSchemaMap = m | ||
550 | } | ||
551 | for k, v := range m { | ||
552 | if v.Type == TypeInvalid { | ||
553 | return fmt.Errorf("%s: Type must be specified", k) | ||
554 | } | ||
555 | |||
556 | if v.Optional && v.Required { | ||
557 | return fmt.Errorf("%s: Optional or Required must be set, not both", k) | ||
558 | } | ||
559 | |||
560 | if v.Required && v.Computed { | ||
561 | return fmt.Errorf("%s: Cannot be both Required and Computed", k) | ||
562 | } | ||
563 | |||
564 | if !v.Required && !v.Optional && !v.Computed { | ||
565 | return fmt.Errorf("%s: One of optional, required, or computed must be set", k) | ||
566 | } | ||
567 | |||
568 | if v.Computed && v.Default != nil { | ||
569 | return fmt.Errorf("%s: Default must be nil if computed", k) | ||
570 | } | ||
571 | |||
572 | if v.Required && v.Default != nil { | ||
573 | return fmt.Errorf("%s: Default cannot be set with Required", k) | ||
574 | } | ||
575 | |||
576 | if len(v.ComputedWhen) > 0 && !v.Computed { | ||
577 | return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k) | ||
578 | } | ||
579 | |||
580 | if len(v.ConflictsWith) > 0 && v.Required { | ||
581 | return fmt.Errorf("%s: ConflictsWith cannot be set with Required", k) | ||
582 | } | ||
583 | |||
584 | if len(v.ConflictsWith) > 0 { | ||
585 | for _, key := range v.ConflictsWith { | ||
586 | parts := strings.Split(key, ".") | ||
587 | sm := topSchemaMap | ||
588 | var target *Schema | ||
589 | for _, part := range parts { | ||
590 | // Skip index fields | ||
591 | if _, err := strconv.Atoi(part); err == nil { | ||
592 | continue | ||
593 | } | ||
594 | |||
595 | var ok bool | ||
596 | if target, ok = sm[part]; !ok { | ||
597 | return fmt.Errorf("%s: ConflictsWith references unknown attribute (%s)", k, key) | ||
598 | } | ||
599 | |||
600 | if subResource, ok := target.Elem.(*Resource); ok { | ||
601 | sm = schemaMap(subResource.Schema) | ||
602 | } | ||
603 | } | ||
604 | if target == nil { | ||
605 | return fmt.Errorf("%s: ConflictsWith cannot find target attribute (%s), sm: %#v", k, key, sm) | ||
606 | } | ||
607 | if target.Required { | ||
608 | return fmt.Errorf("%s: ConflictsWith cannot contain Required attribute (%s)", k, key) | ||
609 | } | ||
610 | |||
611 | if len(target.ComputedWhen) > 0 { | ||
612 | return fmt.Errorf("%s: ConflictsWith cannot contain Computed(When) attribute (%s)", k, key) | ||
613 | } | ||
614 | } | ||
615 | } | ||
616 | |||
617 | if v.Type == TypeList || v.Type == TypeSet { | ||
618 | if v.Elem == nil { | ||
619 | return fmt.Errorf("%s: Elem must be set for lists", k) | ||
620 | } | ||
621 | |||
622 | if v.Default != nil { | ||
623 | return fmt.Errorf("%s: Default is not valid for lists or sets", k) | ||
624 | } | ||
625 | |||
626 | if v.Type != TypeSet && v.Set != nil { | ||
627 | return fmt.Errorf("%s: Set can only be set for TypeSet", k) | ||
628 | } | ||
629 | |||
630 | switch t := v.Elem.(type) { | ||
631 | case *Resource: | ||
632 | if err := t.InternalValidate(topSchemaMap, true); err != nil { | ||
633 | return err | ||
634 | } | ||
635 | case *Schema: | ||
636 | bad := t.Computed || t.Optional || t.Required | ||
637 | if bad { | ||
638 | return fmt.Errorf( | ||
639 | "%s: Elem must have only Type set", k) | ||
640 | } | ||
641 | } | ||
642 | } else { | ||
643 | if v.MaxItems > 0 || v.MinItems > 0 { | ||
644 | return fmt.Errorf("%s: MaxItems and MinItems are only supported on lists or sets", k) | ||
645 | } | ||
646 | } | ||
647 | |||
648 | // Computed-only field | ||
649 | if v.Computed && !v.Optional { | ||
650 | if v.ValidateFunc != nil { | ||
651 | return fmt.Errorf("%s: ValidateFunc is for validating user input, "+ | ||
652 | "there's nothing to validate on computed-only field", k) | ||
653 | } | ||
654 | if v.DiffSuppressFunc != nil { | ||
655 | return fmt.Errorf("%s: DiffSuppressFunc is for suppressing differences"+ | ||
656 | " between config and state representation. "+ | ||
657 | "There is no config for computed-only field, nothing to compare.", k) | ||
658 | } | ||
659 | } | ||
660 | |||
661 | if v.ValidateFunc != nil { | ||
662 | switch v.Type { | ||
663 | case TypeList, TypeSet: | ||
664 | return fmt.Errorf("ValidateFunc is not yet supported on lists or sets.") | ||
665 | } | ||
666 | } | ||
667 | } | ||
668 | |||
669 | return nil | ||
670 | } | ||
671 | |||
672 | func (m schemaMap) diff( | ||
673 | k string, | ||
674 | schema *Schema, | ||
675 | diff *terraform.InstanceDiff, | ||
676 | d *ResourceData, | ||
677 | all bool) error { | ||
678 | |||
679 | unsupressedDiff := new(terraform.InstanceDiff) | ||
680 | unsupressedDiff.Attributes = make(map[string]*terraform.ResourceAttrDiff) | ||
681 | |||
682 | var err error | ||
683 | switch schema.Type { | ||
684 | case TypeBool, TypeInt, TypeFloat, TypeString: | ||
685 | err = m.diffString(k, schema, unsupressedDiff, d, all) | ||
686 | case TypeList: | ||
687 | err = m.diffList(k, schema, unsupressedDiff, d, all) | ||
688 | case TypeMap: | ||
689 | err = m.diffMap(k, schema, unsupressedDiff, d, all) | ||
690 | case TypeSet: | ||
691 | err = m.diffSet(k, schema, unsupressedDiff, d, all) | ||
692 | default: | ||
693 | err = fmt.Errorf("%s: unknown type %#v", k, schema.Type) | ||
694 | } | ||
695 | |||
696 | for attrK, attrV := range unsupressedDiff.Attributes { | ||
697 | if schema.DiffSuppressFunc != nil && | ||
698 | attrV != nil && | ||
699 | schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, d) { | ||
700 | continue | ||
701 | } | ||
702 | |||
703 | diff.Attributes[attrK] = attrV | ||
704 | } | ||
705 | |||
706 | return err | ||
707 | } | ||
708 | |||
709 | func (m schemaMap) diffList( | ||
710 | k string, | ||
711 | schema *Schema, | ||
712 | diff *terraform.InstanceDiff, | ||
713 | d *ResourceData, | ||
714 | all bool) error { | ||
715 | o, n, _, computedList := d.diffChange(k) | ||
716 | if computedList { | ||
717 | n = nil | ||
718 | } | ||
719 | nSet := n != nil | ||
720 | |||
721 | // If we have an old value and no new value is set or will be | ||
722 | // computed once all variables can be interpolated and we're | ||
723 | // computed, then nothing has changed. | ||
724 | if o != nil && n == nil && !computedList && schema.Computed { | ||
725 | return nil | ||
726 | } | ||
727 | |||
728 | if o == nil { | ||
729 | o = []interface{}{} | ||
730 | } | ||
731 | if n == nil { | ||
732 | n = []interface{}{} | ||
733 | } | ||
734 | if s, ok := o.(*Set); ok { | ||
735 | o = s.List() | ||
736 | } | ||
737 | if s, ok := n.(*Set); ok { | ||
738 | n = s.List() | ||
739 | } | ||
740 | os := o.([]interface{}) | ||
741 | vs := n.([]interface{}) | ||
742 | |||
743 | // If the new value was set, and the two are equal, then we're done. | ||
744 | // We have to do this check here because sets might be NOT | ||
745 | // reflect.DeepEqual so we need to wait until we get the []interface{} | ||
746 | if !all && nSet && reflect.DeepEqual(os, vs) { | ||
747 | return nil | ||
748 | } | ||
749 | |||
750 | // Get the counts | ||
751 | oldLen := len(os) | ||
752 | newLen := len(vs) | ||
753 | oldStr := strconv.FormatInt(int64(oldLen), 10) | ||
754 | |||
755 | // If the whole list is computed, then say that the # is computed | ||
756 | if computedList { | ||
757 | diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{ | ||
758 | Old: oldStr, | ||
759 | NewComputed: true, | ||
760 | RequiresNew: schema.ForceNew, | ||
761 | } | ||
762 | return nil | ||
763 | } | ||
764 | |||
765 | // If the counts are not the same, then record that diff | ||
766 | changed := oldLen != newLen | ||
767 | computed := oldLen == 0 && newLen == 0 && schema.Computed | ||
768 | if changed || computed || all { | ||
769 | countSchema := &Schema{ | ||
770 | Type: TypeInt, | ||
771 | Computed: schema.Computed, | ||
772 | ForceNew: schema.ForceNew, | ||
773 | } | ||
774 | |||
775 | newStr := "" | ||
776 | if !computed { | ||
777 | newStr = strconv.FormatInt(int64(newLen), 10) | ||
778 | } else { | ||
779 | oldStr = "" | ||
780 | } | ||
781 | |||
782 | diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ | ||
783 | Old: oldStr, | ||
784 | New: newStr, | ||
785 | }) | ||
786 | } | ||
787 | |||
788 | // Figure out the maximum | ||
789 | maxLen := oldLen | ||
790 | if newLen > maxLen { | ||
791 | maxLen = newLen | ||
792 | } | ||
793 | |||
794 | switch t := schema.Elem.(type) { | ||
795 | case *Resource: | ||
796 | // This is a complex resource | ||
797 | for i := 0; i < maxLen; i++ { | ||
798 | for k2, schema := range t.Schema { | ||
799 | subK := fmt.Sprintf("%s.%d.%s", k, i, k2) | ||
800 | err := m.diff(subK, schema, diff, d, all) | ||
801 | if err != nil { | ||
802 | return err | ||
803 | } | ||
804 | } | ||
805 | } | ||
806 | case *Schema: | ||
807 | // Copy the schema so that we can set Computed/ForceNew from | ||
808 | // the parent schema (the TypeList). | ||
809 | t2 := *t | ||
810 | t2.ForceNew = schema.ForceNew | ||
811 | |||
812 | // This is just a primitive element, so go through each and | ||
813 | // just diff each. | ||
814 | for i := 0; i < maxLen; i++ { | ||
815 | subK := fmt.Sprintf("%s.%d", k, i) | ||
816 | err := m.diff(subK, &t2, diff, d, all) | ||
817 | if err != nil { | ||
818 | return err | ||
819 | } | ||
820 | } | ||
821 | default: | ||
822 | return fmt.Errorf("%s: unknown element type (internal)", k) | ||
823 | } | ||
824 | |||
825 | return nil | ||
826 | } | ||
827 | |||
828 | func (m schemaMap) diffMap( | ||
829 | k string, | ||
830 | schema *Schema, | ||
831 | diff *terraform.InstanceDiff, | ||
832 | d *ResourceData, | ||
833 | all bool) error { | ||
834 | prefix := k + "." | ||
835 | |||
836 | // First get all the values from the state | ||
837 | var stateMap, configMap map[string]string | ||
838 | o, n, _, nComputed := d.diffChange(k) | ||
839 | if err := mapstructure.WeakDecode(o, &stateMap); err != nil { | ||
840 | return fmt.Errorf("%s: %s", k, err) | ||
841 | } | ||
842 | if err := mapstructure.WeakDecode(n, &configMap); err != nil { | ||
843 | return fmt.Errorf("%s: %s", k, err) | ||
844 | } | ||
845 | |||
846 | // Keep track of whether the state _exists_ at all prior to clearing it | ||
847 | stateExists := o != nil | ||
848 | |||
849 | // Delete any count values, since we don't use those | ||
850 | delete(configMap, "%") | ||
851 | delete(stateMap, "%") | ||
852 | |||
853 | // Check if the number of elements has changed. | ||
854 | oldLen, newLen := len(stateMap), len(configMap) | ||
855 | changed := oldLen != newLen | ||
856 | if oldLen != 0 && newLen == 0 && schema.Computed { | ||
857 | changed = false | ||
858 | } | ||
859 | |||
860 | // It is computed if we have no old value, no new value, the schema | ||
861 | // says it is computed, and it didn't exist in the state before. The | ||
862 | // last point means: if it existed in the state, even empty, then it | ||
863 | // has already been computed. | ||
864 | computed := oldLen == 0 && newLen == 0 && schema.Computed && !stateExists | ||
865 | |||
866 | // If the count has changed or we're computed, then add a diff for the | ||
867 | // count. "nComputed" means that the new value _contains_ a value that | ||
868 | // is computed. We don't do granular diffs for this yet, so we mark the | ||
869 | // whole map as computed. | ||
870 | if changed || computed || nComputed { | ||
871 | countSchema := &Schema{ | ||
872 | Type: TypeInt, | ||
873 | Computed: schema.Computed || nComputed, | ||
874 | ForceNew: schema.ForceNew, | ||
875 | } | ||
876 | |||
877 | oldStr := strconv.FormatInt(int64(oldLen), 10) | ||
878 | newStr := "" | ||
879 | if !computed && !nComputed { | ||
880 | newStr = strconv.FormatInt(int64(newLen), 10) | ||
881 | } else { | ||
882 | oldStr = "" | ||
883 | } | ||
884 | |||
885 | diff.Attributes[k+".%"] = countSchema.finalizeDiff( | ||
886 | &terraform.ResourceAttrDiff{ | ||
887 | Old: oldStr, | ||
888 | New: newStr, | ||
889 | }, | ||
890 | ) | ||
891 | } | ||
892 | |||
893 | // If the new map is nil and we're computed, then ignore it. | ||
894 | if n == nil && schema.Computed { | ||
895 | return nil | ||
896 | } | ||
897 | |||
898 | // Now we compare, preferring values from the config map | ||
899 | for k, v := range configMap { | ||
900 | old, ok := stateMap[k] | ||
901 | delete(stateMap, k) | ||
902 | |||
903 | if old == v && ok && !all { | ||
904 | continue | ||
905 | } | ||
906 | |||
907 | diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ | ||
908 | Old: old, | ||
909 | New: v, | ||
910 | }) | ||
911 | } | ||
912 | for k, v := range stateMap { | ||
913 | diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ | ||
914 | Old: v, | ||
915 | NewRemoved: true, | ||
916 | }) | ||
917 | } | ||
918 | |||
919 | return nil | ||
920 | } | ||
921 | |||
922 | func (m schemaMap) diffSet( | ||
923 | k string, | ||
924 | schema *Schema, | ||
925 | diff *terraform.InstanceDiff, | ||
926 | d *ResourceData, | ||
927 | all bool) error { | ||
928 | |||
929 | o, n, _, computedSet := d.diffChange(k) | ||
930 | if computedSet { | ||
931 | n = nil | ||
932 | } | ||
933 | nSet := n != nil | ||
934 | |||
935 | // If we have an old value and no new value is set or will be | ||
936 | // computed once all variables can be interpolated and we're | ||
937 | // computed, then nothing has changed. | ||
938 | if o != nil && n == nil && !computedSet && schema.Computed { | ||
939 | return nil | ||
940 | } | ||
941 | |||
942 | if o == nil { | ||
943 | o = schema.ZeroValue().(*Set) | ||
944 | } | ||
945 | if n == nil { | ||
946 | n = schema.ZeroValue().(*Set) | ||
947 | } | ||
948 | os := o.(*Set) | ||
949 | ns := n.(*Set) | ||
950 | |||
951 | // If the new value was set, compare the listCode's to determine if | ||
952 | // the two are equal. Comparing listCode's instead of the actual values | ||
953 | // is needed because there could be computed values in the set which | ||
954 | // would result in false positives while comparing. | ||
955 | if !all && nSet && reflect.DeepEqual(os.listCode(), ns.listCode()) { | ||
956 | return nil | ||
957 | } | ||
958 | |||
959 | // Get the counts | ||
960 | oldLen := os.Len() | ||
961 | newLen := ns.Len() | ||
962 | oldStr := strconv.Itoa(oldLen) | ||
963 | newStr := strconv.Itoa(newLen) | ||
964 | |||
965 | // Build a schema for our count | ||
966 | countSchema := &Schema{ | ||
967 | Type: TypeInt, | ||
968 | Computed: schema.Computed, | ||
969 | ForceNew: schema.ForceNew, | ||
970 | } | ||
971 | |||
972 | // If the set computed then say that the # is computed | ||
973 | if computedSet || schema.Computed && !nSet { | ||
974 | // If # already exists, equals 0 and no new set is supplied, there | ||
975 | // is nothing to record in the diff | ||
976 | count, ok := d.GetOk(k + ".#") | ||
977 | if ok && count.(int) == 0 && !nSet && !computedSet { | ||
978 | return nil | ||
979 | } | ||
980 | |||
981 | // Set the count but make sure that if # does not exist, we don't | ||
982 | // use the zeroed value | ||
983 | countStr := strconv.Itoa(count.(int)) | ||
984 | if !ok { | ||
985 | countStr = "" | ||
986 | } | ||
987 | |||
988 | diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ | ||
989 | Old: countStr, | ||
990 | NewComputed: true, | ||
991 | }) | ||
992 | return nil | ||
993 | } | ||
994 | |||
995 | // If the counts are not the same, then record that diff | ||
996 | changed := oldLen != newLen | ||
997 | if changed || all { | ||
998 | diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{ | ||
999 | Old: oldStr, | ||
1000 | New: newStr, | ||
1001 | }) | ||
1002 | } | ||
1003 | |||
1004 | // Build the list of codes that will make up our set. This is the | ||
1005 | // removed codes as well as all the codes in the new codes. | ||
1006 | codes := make([][]string, 2) | ||
1007 | codes[0] = os.Difference(ns).listCode() | ||
1008 | codes[1] = ns.listCode() | ||
1009 | for _, list := range codes { | ||
1010 | for _, code := range list { | ||
1011 | switch t := schema.Elem.(type) { | ||
1012 | case *Resource: | ||
1013 | // This is a complex resource | ||
1014 | for k2, schema := range t.Schema { | ||
1015 | subK := fmt.Sprintf("%s.%s.%s", k, code, k2) | ||
1016 | err := m.diff(subK, schema, diff, d, true) | ||
1017 | if err != nil { | ||
1018 | return err | ||
1019 | } | ||
1020 | } | ||
1021 | case *Schema: | ||
1022 | // Copy the schema so that we can set Computed/ForceNew from | ||
1023 | // the parent schema (the TypeSet). | ||
1024 | t2 := *t | ||
1025 | t2.ForceNew = schema.ForceNew | ||
1026 | |||
1027 | // This is just a primitive element, so go through each and | ||
1028 | // just diff each. | ||
1029 | subK := fmt.Sprintf("%s.%s", k, code) | ||
1030 | err := m.diff(subK, &t2, diff, d, true) | ||
1031 | if err != nil { | ||
1032 | return err | ||
1033 | } | ||
1034 | default: | ||
1035 | return fmt.Errorf("%s: unknown element type (internal)", k) | ||
1036 | } | ||
1037 | } | ||
1038 | } | ||
1039 | |||
1040 | return nil | ||
1041 | } | ||
1042 | |||
1043 | func (m schemaMap) diffString( | ||
1044 | k string, | ||
1045 | schema *Schema, | ||
1046 | diff *terraform.InstanceDiff, | ||
1047 | d *ResourceData, | ||
1048 | all bool) error { | ||
1049 | var originalN interface{} | ||
1050 | var os, ns string | ||
1051 | o, n, _, computed := d.diffChange(k) | ||
1052 | if schema.StateFunc != nil && n != nil { | ||
1053 | originalN = n | ||
1054 | n = schema.StateFunc(n) | ||
1055 | } | ||
1056 | nraw := n | ||
1057 | if nraw == nil && o != nil { | ||
1058 | nraw = schema.Type.Zero() | ||
1059 | } | ||
1060 | if err := mapstructure.WeakDecode(o, &os); err != nil { | ||
1061 | return fmt.Errorf("%s: %s", k, err) | ||
1062 | } | ||
1063 | if err := mapstructure.WeakDecode(nraw, &ns); err != nil { | ||
1064 | return fmt.Errorf("%s: %s", k, err) | ||
1065 | } | ||
1066 | |||
1067 | if os == ns && !all { | ||
1068 | // They're the same value. If there old value is not blank or we | ||
1069 | // have an ID, then return right away since we're already setup. | ||
1070 | if os != "" || d.Id() != "" { | ||
1071 | return nil | ||
1072 | } | ||
1073 | |||
1074 | // Otherwise, only continue if we're computed | ||
1075 | if !schema.Computed && !computed { | ||
1076 | return nil | ||
1077 | } | ||
1078 | } | ||
1079 | |||
1080 | removed := false | ||
1081 | if o != nil && n == nil { | ||
1082 | removed = true | ||
1083 | } | ||
1084 | if removed && schema.Computed { | ||
1085 | return nil | ||
1086 | } | ||
1087 | |||
1088 | diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{ | ||
1089 | Old: os, | ||
1090 | New: ns, | ||
1091 | NewExtra: originalN, | ||
1092 | NewRemoved: removed, | ||
1093 | NewComputed: computed, | ||
1094 | }) | ||
1095 | |||
1096 | return nil | ||
1097 | } | ||
1098 | |||
1099 | func (m schemaMap) inputString( | ||
1100 | input terraform.UIInput, | ||
1101 | k string, | ||
1102 | schema *Schema) (interface{}, error) { | ||
1103 | result, err := input.Input(&terraform.InputOpts{ | ||
1104 | Id: k, | ||
1105 | Query: k, | ||
1106 | Description: schema.Description, | ||
1107 | Default: schema.InputDefault, | ||
1108 | }) | ||
1109 | |||
1110 | return result, err | ||
1111 | } | ||
1112 | |||
1113 | func (m schemaMap) validate( | ||
1114 | k string, | ||
1115 | schema *Schema, | ||
1116 | c *terraform.ResourceConfig) ([]string, []error) { | ||
1117 | raw, ok := c.Get(k) | ||
1118 | if !ok && schema.DefaultFunc != nil { | ||
1119 | // We have a dynamic default. Check if we have a value. | ||
1120 | var err error | ||
1121 | raw, err = schema.DefaultFunc() | ||
1122 | if err != nil { | ||
1123 | return nil, []error{fmt.Errorf( | ||
1124 | "%q, error loading default: %s", k, err)} | ||
1125 | } | ||
1126 | |||
1127 | // We're okay as long as we had a value set | ||
1128 | ok = raw != nil | ||
1129 | } | ||
1130 | if !ok { | ||
1131 | if schema.Required { | ||
1132 | return nil, []error{fmt.Errorf( | ||
1133 | "%q: required field is not set", k)} | ||
1134 | } | ||
1135 | |||
1136 | return nil, nil | ||
1137 | } | ||
1138 | |||
1139 | if !schema.Required && !schema.Optional { | ||
1140 | // This is a computed-only field | ||
1141 | return nil, []error{fmt.Errorf( | ||
1142 | "%q: this field cannot be set", k)} | ||
1143 | } | ||
1144 | |||
1145 | err := m.validateConflictingAttributes(k, schema, c) | ||
1146 | if err != nil { | ||
1147 | return nil, []error{err} | ||
1148 | } | ||
1149 | |||
1150 | return m.validateType(k, raw, schema, c) | ||
1151 | } | ||
1152 | |||
1153 | func (m schemaMap) validateConflictingAttributes( | ||
1154 | k string, | ||
1155 | schema *Schema, | ||
1156 | c *terraform.ResourceConfig) error { | ||
1157 | |||
1158 | if len(schema.ConflictsWith) == 0 { | ||
1159 | return nil | ||
1160 | } | ||
1161 | |||
1162 | for _, conflicting_key := range schema.ConflictsWith { | ||
1163 | if value, ok := c.Get(conflicting_key); ok { | ||
1164 | return fmt.Errorf( | ||
1165 | "%q: conflicts with %s (%#v)", k, conflicting_key, value) | ||
1166 | } | ||
1167 | } | ||
1168 | |||
1169 | return nil | ||
1170 | } | ||
1171 | |||
1172 | func (m schemaMap) validateList( | ||
1173 | k string, | ||
1174 | raw interface{}, | ||
1175 | schema *Schema, | ||
1176 | c *terraform.ResourceConfig) ([]string, []error) { | ||
1177 | // We use reflection to verify the slice because you can't | ||
1178 | // case to []interface{} unless the slice is exactly that type. | ||
1179 | rawV := reflect.ValueOf(raw) | ||
1180 | |||
1181 | // If we support promotion and the raw value isn't a slice, wrap | ||
1182 | // it in []interface{} and check again. | ||
1183 | if schema.PromoteSingle && rawV.Kind() != reflect.Slice { | ||
1184 | raw = []interface{}{raw} | ||
1185 | rawV = reflect.ValueOf(raw) | ||
1186 | } | ||
1187 | |||
1188 | if rawV.Kind() != reflect.Slice { | ||
1189 | return nil, []error{fmt.Errorf( | ||
1190 | "%s: should be a list", k)} | ||
1191 | } | ||
1192 | |||
1193 | // Validate length | ||
1194 | if schema.MaxItems > 0 && rawV.Len() > schema.MaxItems { | ||
1195 | return nil, []error{fmt.Errorf( | ||
1196 | "%s: attribute supports %d item maximum, config has %d declared", k, schema.MaxItems, rawV.Len())} | ||
1197 | } | ||
1198 | |||
1199 | if schema.MinItems > 0 && rawV.Len() < schema.MinItems { | ||
1200 | return nil, []error{fmt.Errorf( | ||
1201 | "%s: attribute supports %d item as a minimum, config has %d declared", k, schema.MinItems, rawV.Len())} | ||
1202 | } | ||
1203 | |||
1204 | // Now build the []interface{} | ||
1205 | raws := make([]interface{}, rawV.Len()) | ||
1206 | for i, _ := range raws { | ||
1207 | raws[i] = rawV.Index(i).Interface() | ||
1208 | } | ||
1209 | |||
1210 | var ws []string | ||
1211 | var es []error | ||
1212 | for i, raw := range raws { | ||
1213 | key := fmt.Sprintf("%s.%d", k, i) | ||
1214 | |||
1215 | // Reify the key value from the ResourceConfig. | ||
1216 | // If the list was computed we have all raw values, but some of these | ||
1217 | // may be known in the config, and aren't individually marked as Computed. | ||
1218 | if r, ok := c.Get(key); ok { | ||
1219 | raw = r | ||
1220 | } | ||
1221 | |||
1222 | var ws2 []string | ||
1223 | var es2 []error | ||
1224 | switch t := schema.Elem.(type) { | ||
1225 | case *Resource: | ||
1226 | // This is a sub-resource | ||
1227 | ws2, es2 = m.validateObject(key, t.Schema, c) | ||
1228 | case *Schema: | ||
1229 | ws2, es2 = m.validateType(key, raw, t, c) | ||
1230 | } | ||
1231 | |||
1232 | if len(ws2) > 0 { | ||
1233 | ws = append(ws, ws2...) | ||
1234 | } | ||
1235 | if len(es2) > 0 { | ||
1236 | es = append(es, es2...) | ||
1237 | } | ||
1238 | } | ||
1239 | |||
1240 | return ws, es | ||
1241 | } | ||
1242 | |||
1243 | func (m schemaMap) validateMap( | ||
1244 | k string, | ||
1245 | raw interface{}, | ||
1246 | schema *Schema, | ||
1247 | c *terraform.ResourceConfig) ([]string, []error) { | ||
1248 | // We use reflection to verify the slice because you can't | ||
1249 | // case to []interface{} unless the slice is exactly that type. | ||
1250 | rawV := reflect.ValueOf(raw) | ||
1251 | switch rawV.Kind() { | ||
1252 | case reflect.String: | ||
1253 | // If raw and reified are equal, this is a string and should | ||
1254 | // be rejected. | ||
1255 | reified, reifiedOk := c.Get(k) | ||
1256 | if reifiedOk && raw == reified && !c.IsComputed(k) { | ||
1257 | return nil, []error{fmt.Errorf("%s: should be a map", k)} | ||
1258 | } | ||
1259 | // Otherwise it's likely raw is an interpolation. | ||
1260 | return nil, nil | ||
1261 | case reflect.Map: | ||
1262 | case reflect.Slice: | ||
1263 | default: | ||
1264 | return nil, []error{fmt.Errorf("%s: should be a map", k)} | ||
1265 | } | ||
1266 | |||
1267 | // If it is not a slice, validate directly | ||
1268 | if rawV.Kind() != reflect.Slice { | ||
1269 | mapIface := rawV.Interface() | ||
1270 | if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 { | ||
1271 | return nil, errs | ||
1272 | } | ||
1273 | if schema.ValidateFunc != nil { | ||
1274 | return schema.ValidateFunc(mapIface, k) | ||
1275 | } | ||
1276 | return nil, nil | ||
1277 | } | ||
1278 | |||
1279 | // It is a slice, verify that all the elements are maps | ||
1280 | raws := make([]interface{}, rawV.Len()) | ||
1281 | for i, _ := range raws { | ||
1282 | raws[i] = rawV.Index(i).Interface() | ||
1283 | } | ||
1284 | |||
1285 | for _, raw := range raws { | ||
1286 | v := reflect.ValueOf(raw) | ||
1287 | if v.Kind() != reflect.Map { | ||
1288 | return nil, []error{fmt.Errorf( | ||
1289 | "%s: should be a map", k)} | ||
1290 | } | ||
1291 | mapIface := v.Interface() | ||
1292 | if _, errs := validateMapValues(k, mapIface.(map[string]interface{}), schema); len(errs) > 0 { | ||
1293 | return nil, errs | ||
1294 | } | ||
1295 | } | ||
1296 | |||
1297 | if schema.ValidateFunc != nil { | ||
1298 | validatableMap := make(map[string]interface{}) | ||
1299 | for _, raw := range raws { | ||
1300 | for k, v := range raw.(map[string]interface{}) { | ||
1301 | validatableMap[k] = v | ||
1302 | } | ||
1303 | } | ||
1304 | |||
1305 | return schema.ValidateFunc(validatableMap, k) | ||
1306 | } | ||
1307 | |||
1308 | return nil, nil | ||
1309 | } | ||
1310 | |||
1311 | func validateMapValues(k string, m map[string]interface{}, schema *Schema) ([]string, []error) { | ||
1312 | for key, raw := range m { | ||
1313 | valueType, err := getValueType(k, schema) | ||
1314 | if err != nil { | ||
1315 | return nil, []error{err} | ||
1316 | } | ||
1317 | |||
1318 | switch valueType { | ||
1319 | case TypeBool: | ||
1320 | var n bool | ||
1321 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1322 | return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} | ||
1323 | } | ||
1324 | case TypeInt: | ||
1325 | var n int | ||
1326 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1327 | return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} | ||
1328 | } | ||
1329 | case TypeFloat: | ||
1330 | var n float64 | ||
1331 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1332 | return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} | ||
1333 | } | ||
1334 | case TypeString: | ||
1335 | var n string | ||
1336 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1337 | return nil, []error{fmt.Errorf("%s (%s): %s", k, key, err)} | ||
1338 | } | ||
1339 | default: | ||
1340 | panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type)) | ||
1341 | } | ||
1342 | } | ||
1343 | return nil, nil | ||
1344 | } | ||
1345 | |||
1346 | func getValueType(k string, schema *Schema) (ValueType, error) { | ||
1347 | if schema.Elem == nil { | ||
1348 | return TypeString, nil | ||
1349 | } | ||
1350 | if vt, ok := schema.Elem.(ValueType); ok { | ||
1351 | return vt, nil | ||
1352 | } | ||
1353 | |||
1354 | if s, ok := schema.Elem.(*Schema); ok { | ||
1355 | if s.Elem == nil { | ||
1356 | return TypeString, nil | ||
1357 | } | ||
1358 | if vt, ok := s.Elem.(ValueType); ok { | ||
1359 | return vt, nil | ||
1360 | } | ||
1361 | } | ||
1362 | |||
1363 | if _, ok := schema.Elem.(*Resource); ok { | ||
1364 | // TODO: We don't actually support this (yet) | ||
1365 | // but silently pass the validation, until we decide | ||
1366 | // how to handle nested structures in maps | ||
1367 | return TypeString, nil | ||
1368 | } | ||
1369 | return 0, fmt.Errorf("%s: unexpected map value type: %#v", k, schema.Elem) | ||
1370 | } | ||
1371 | |||
1372 | func (m schemaMap) validateObject( | ||
1373 | k string, | ||
1374 | schema map[string]*Schema, | ||
1375 | c *terraform.ResourceConfig) ([]string, []error) { | ||
1376 | raw, _ := c.GetRaw(k) | ||
1377 | if _, ok := raw.(map[string]interface{}); !ok { | ||
1378 | return nil, []error{fmt.Errorf( | ||
1379 | "%s: expected object, got %s", | ||
1380 | k, reflect.ValueOf(raw).Kind())} | ||
1381 | } | ||
1382 | |||
1383 | var ws []string | ||
1384 | var es []error | ||
1385 | for subK, s := range schema { | ||
1386 | key := subK | ||
1387 | if k != "" { | ||
1388 | key = fmt.Sprintf("%s.%s", k, subK) | ||
1389 | } | ||
1390 | |||
1391 | ws2, es2 := m.validate(key, s, c) | ||
1392 | if len(ws2) > 0 { | ||
1393 | ws = append(ws, ws2...) | ||
1394 | } | ||
1395 | if len(es2) > 0 { | ||
1396 | es = append(es, es2...) | ||
1397 | } | ||
1398 | } | ||
1399 | |||
1400 | // Detect any extra/unknown keys and report those as errors. | ||
1401 | if m, ok := raw.(map[string]interface{}); ok { | ||
1402 | for subk, _ := range m { | ||
1403 | if _, ok := schema[subk]; !ok { | ||
1404 | if subk == TimeoutsConfigKey { | ||
1405 | continue | ||
1406 | } | ||
1407 | es = append(es, fmt.Errorf( | ||
1408 | "%s: invalid or unknown key: %s", k, subk)) | ||
1409 | } | ||
1410 | } | ||
1411 | } | ||
1412 | |||
1413 | return ws, es | ||
1414 | } | ||
1415 | |||
1416 | func (m schemaMap) validatePrimitive( | ||
1417 | k string, | ||
1418 | raw interface{}, | ||
1419 | schema *Schema, | ||
1420 | c *terraform.ResourceConfig) ([]string, []error) { | ||
1421 | |||
1422 | // Catch if the user gave a complex type where a primitive was | ||
1423 | // expected, so we can return a friendly error message that | ||
1424 | // doesn't contain Go type system terminology. | ||
1425 | switch reflect.ValueOf(raw).Type().Kind() { | ||
1426 | case reflect.Slice: | ||
1427 | return nil, []error{ | ||
1428 | fmt.Errorf("%s must be a single value, not a list", k), | ||
1429 | } | ||
1430 | case reflect.Map: | ||
1431 | return nil, []error{ | ||
1432 | fmt.Errorf("%s must be a single value, not a map", k), | ||
1433 | } | ||
1434 | default: // ok | ||
1435 | } | ||
1436 | |||
1437 | if c.IsComputed(k) { | ||
1438 | // If the key is being computed, then it is not an error as | ||
1439 | // long as it's not a slice or map. | ||
1440 | return nil, nil | ||
1441 | } | ||
1442 | |||
1443 | var decoded interface{} | ||
1444 | switch schema.Type { | ||
1445 | case TypeBool: | ||
1446 | // Verify that we can parse this as the correct type | ||
1447 | var n bool | ||
1448 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1449 | return nil, []error{fmt.Errorf("%s: %s", k, err)} | ||
1450 | } | ||
1451 | decoded = n | ||
1452 | case TypeInt: | ||
1453 | // Verify that we can parse this as an int | ||
1454 | var n int | ||
1455 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1456 | return nil, []error{fmt.Errorf("%s: %s", k, err)} | ||
1457 | } | ||
1458 | decoded = n | ||
1459 | case TypeFloat: | ||
1460 | // Verify that we can parse this as an int | ||
1461 | var n float64 | ||
1462 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1463 | return nil, []error{fmt.Errorf("%s: %s", k, err)} | ||
1464 | } | ||
1465 | decoded = n | ||
1466 | case TypeString: | ||
1467 | // Verify that we can parse this as a string | ||
1468 | var n string | ||
1469 | if err := mapstructure.WeakDecode(raw, &n); err != nil { | ||
1470 | return nil, []error{fmt.Errorf("%s: %s", k, err)} | ||
1471 | } | ||
1472 | decoded = n | ||
1473 | default: | ||
1474 | panic(fmt.Sprintf("Unknown validation type: %#v", schema.Type)) | ||
1475 | } | ||
1476 | |||
1477 | if schema.ValidateFunc != nil { | ||
1478 | return schema.ValidateFunc(decoded, k) | ||
1479 | } | ||
1480 | |||
1481 | return nil, nil | ||
1482 | } | ||
1483 | |||
1484 | func (m schemaMap) validateType( | ||
1485 | k string, | ||
1486 | raw interface{}, | ||
1487 | schema *Schema, | ||
1488 | c *terraform.ResourceConfig) ([]string, []error) { | ||
1489 | var ws []string | ||
1490 | var es []error | ||
1491 | switch schema.Type { | ||
1492 | case TypeSet, TypeList: | ||
1493 | ws, es = m.validateList(k, raw, schema, c) | ||
1494 | case TypeMap: | ||
1495 | ws, es = m.validateMap(k, raw, schema, c) | ||
1496 | default: | ||
1497 | ws, es = m.validatePrimitive(k, raw, schema, c) | ||
1498 | } | ||
1499 | |||
1500 | if schema.Deprecated != "" { | ||
1501 | ws = append(ws, fmt.Sprintf( | ||
1502 | "%q: [DEPRECATED] %s", k, schema.Deprecated)) | ||
1503 | } | ||
1504 | |||
1505 | if schema.Removed != "" { | ||
1506 | es = append(es, fmt.Errorf( | ||
1507 | "%q: [REMOVED] %s", k, schema.Removed)) | ||
1508 | } | ||
1509 | |||
1510 | return ws, es | ||
1511 | } | ||
1512 | |||
1513 | // Zero returns the zero value for a type. | ||
1514 | func (t ValueType) Zero() interface{} { | ||
1515 | switch t { | ||
1516 | case TypeInvalid: | ||
1517 | return nil | ||
1518 | case TypeBool: | ||
1519 | return false | ||
1520 | case TypeInt: | ||
1521 | return 0 | ||
1522 | case TypeFloat: | ||
1523 | return 0.0 | ||
1524 | case TypeString: | ||
1525 | return "" | ||
1526 | case TypeList: | ||
1527 | return []interface{}{} | ||
1528 | case TypeMap: | ||
1529 | return map[string]interface{}{} | ||
1530 | case TypeSet: | ||
1531 | return new(Set) | ||
1532 | case typeObject: | ||
1533 | return map[string]interface{}{} | ||
1534 | default: | ||
1535 | panic(fmt.Sprintf("unknown type %s", t)) | ||
1536 | } | ||
1537 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/serialize.go b/vendor/github.com/hashicorp/terraform/helper/schema/serialize.go new file mode 100644 index 0000000..fe6d750 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/serialize.go | |||
@@ -0,0 +1,125 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "sort" | ||
7 | "strconv" | ||
8 | ) | ||
9 | |||
10 | func SerializeValueForHash(buf *bytes.Buffer, val interface{}, schema *Schema) { | ||
11 | if val == nil { | ||
12 | buf.WriteRune(';') | ||
13 | return | ||
14 | } | ||
15 | |||
16 | switch schema.Type { | ||
17 | case TypeBool: | ||
18 | if val.(bool) { | ||
19 | buf.WriteRune('1') | ||
20 | } else { | ||
21 | buf.WriteRune('0') | ||
22 | } | ||
23 | case TypeInt: | ||
24 | buf.WriteString(strconv.Itoa(val.(int))) | ||
25 | case TypeFloat: | ||
26 | buf.WriteString(strconv.FormatFloat(val.(float64), 'g', -1, 64)) | ||
27 | case TypeString: | ||
28 | buf.WriteString(val.(string)) | ||
29 | case TypeList: | ||
30 | buf.WriteRune('(') | ||
31 | l := val.([]interface{}) | ||
32 | for _, innerVal := range l { | ||
33 | serializeCollectionMemberForHash(buf, innerVal, schema.Elem) | ||
34 | } | ||
35 | buf.WriteRune(')') | ||
36 | case TypeMap: | ||
37 | |||
38 | m := val.(map[string]interface{}) | ||
39 | var keys []string | ||
40 | for k := range m { | ||
41 | keys = append(keys, k) | ||
42 | } | ||
43 | sort.Strings(keys) | ||
44 | buf.WriteRune('[') | ||
45 | for _, k := range keys { | ||
46 | innerVal := m[k] | ||
47 | if innerVal == nil { | ||
48 | continue | ||
49 | } | ||
50 | buf.WriteString(k) | ||
51 | buf.WriteRune(':') | ||
52 | |||
53 | switch innerVal := innerVal.(type) { | ||
54 | case int: | ||
55 | buf.WriteString(strconv.Itoa(innerVal)) | ||
56 | case float64: | ||
57 | buf.WriteString(strconv.FormatFloat(innerVal, 'g', -1, 64)) | ||
58 | case string: | ||
59 | buf.WriteString(innerVal) | ||
60 | default: | ||
61 | panic(fmt.Sprintf("unknown value type in TypeMap %T", innerVal)) | ||
62 | } | ||
63 | |||
64 | buf.WriteRune(';') | ||
65 | } | ||
66 | buf.WriteRune(']') | ||
67 | case TypeSet: | ||
68 | buf.WriteRune('{') | ||
69 | s := val.(*Set) | ||
70 | for _, innerVal := range s.List() { | ||
71 | serializeCollectionMemberForHash(buf, innerVal, schema.Elem) | ||
72 | } | ||
73 | buf.WriteRune('}') | ||
74 | default: | ||
75 | panic("unknown schema type to serialize") | ||
76 | } | ||
77 | buf.WriteRune(';') | ||
78 | } | ||
79 | |||
80 | // SerializeValueForHash appends a serialization of the given resource config | ||
81 | // to the given buffer, guaranteeing deterministic results given the same value | ||
82 | // and schema. | ||
83 | // | ||
84 | // Its primary purpose is as input into a hashing function in order | ||
85 | // to hash complex substructures when used in sets, and so the serialization | ||
86 | // is not reversible. | ||
87 | func SerializeResourceForHash(buf *bytes.Buffer, val interface{}, resource *Resource) { | ||
88 | if val == nil { | ||
89 | return | ||
90 | } | ||
91 | sm := resource.Schema | ||
92 | m := val.(map[string]interface{}) | ||
93 | var keys []string | ||
94 | for k := range sm { | ||
95 | keys = append(keys, k) | ||
96 | } | ||
97 | sort.Strings(keys) | ||
98 | for _, k := range keys { | ||
99 | innerSchema := sm[k] | ||
100 | // Skip attributes that are not user-provided. Computed attributes | ||
101 | // do not contribute to the hash since their ultimate value cannot | ||
102 | // be known at plan/diff time. | ||
103 | if !(innerSchema.Required || innerSchema.Optional) { | ||
104 | continue | ||
105 | } | ||
106 | |||
107 | buf.WriteString(k) | ||
108 | buf.WriteRune(':') | ||
109 | innerVal := m[k] | ||
110 | SerializeValueForHash(buf, innerVal, innerSchema) | ||
111 | } | ||
112 | } | ||
113 | |||
114 | func serializeCollectionMemberForHash(buf *bytes.Buffer, val interface{}, elem interface{}) { | ||
115 | switch tElem := elem.(type) { | ||
116 | case *Schema: | ||
117 | SerializeValueForHash(buf, val, tElem) | ||
118 | case *Resource: | ||
119 | buf.WriteRune('<') | ||
120 | SerializeResourceForHash(buf, val, tElem) | ||
121 | buf.WriteString(">;") | ||
122 | default: | ||
123 | panic(fmt.Sprintf("invalid element type: %T", tElem)) | ||
124 | } | ||
125 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/set.go b/vendor/github.com/hashicorp/terraform/helper/schema/set.go new file mode 100644 index 0000000..de05f40 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/set.go | |||
@@ -0,0 +1,209 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "reflect" | ||
7 | "sort" | ||
8 | "strconv" | ||
9 | "sync" | ||
10 | |||
11 | "github.com/hashicorp/terraform/helper/hashcode" | ||
12 | ) | ||
13 | |||
14 | // HashString hashes strings. If you want a Set of strings, this is the | ||
15 | // SchemaSetFunc you want. | ||
16 | func HashString(v interface{}) int { | ||
17 | return hashcode.String(v.(string)) | ||
18 | } | ||
19 | |||
20 | // HashResource hashes complex structures that are described using | ||
21 | // a *Resource. This is the default set implementation used when a set's | ||
22 | // element type is a full resource. | ||
23 | func HashResource(resource *Resource) SchemaSetFunc { | ||
24 | return func(v interface{}) int { | ||
25 | var buf bytes.Buffer | ||
26 | SerializeResourceForHash(&buf, v, resource) | ||
27 | return hashcode.String(buf.String()) | ||
28 | } | ||
29 | } | ||
30 | |||
31 | // HashSchema hashes values that are described using a *Schema. This is the | ||
32 | // default set implementation used when a set's element type is a single | ||
33 | // schema. | ||
34 | func HashSchema(schema *Schema) SchemaSetFunc { | ||
35 | return func(v interface{}) int { | ||
36 | var buf bytes.Buffer | ||
37 | SerializeValueForHash(&buf, v, schema) | ||
38 | return hashcode.String(buf.String()) | ||
39 | } | ||
40 | } | ||
41 | |||
42 | // Set is a set data structure that is returned for elements of type | ||
43 | // TypeSet. | ||
44 | type Set struct { | ||
45 | F SchemaSetFunc | ||
46 | |||
47 | m map[string]interface{} | ||
48 | once sync.Once | ||
49 | } | ||
50 | |||
51 | // NewSet is a convenience method for creating a new set with the given | ||
52 | // items. | ||
53 | func NewSet(f SchemaSetFunc, items []interface{}) *Set { | ||
54 | s := &Set{F: f} | ||
55 | for _, i := range items { | ||
56 | s.Add(i) | ||
57 | } | ||
58 | |||
59 | return s | ||
60 | } | ||
61 | |||
62 | // CopySet returns a copy of another set. | ||
63 | func CopySet(otherSet *Set) *Set { | ||
64 | return NewSet(otherSet.F, otherSet.List()) | ||
65 | } | ||
66 | |||
67 | // Add adds an item to the set if it isn't already in the set. | ||
68 | func (s *Set) Add(item interface{}) { | ||
69 | s.add(item, false) | ||
70 | } | ||
71 | |||
72 | // Remove removes an item if it's already in the set. Idempotent. | ||
73 | func (s *Set) Remove(item interface{}) { | ||
74 | s.remove(item) | ||
75 | } | ||
76 | |||
77 | // Contains checks if the set has the given item. | ||
78 | func (s *Set) Contains(item interface{}) bool { | ||
79 | _, ok := s.m[s.hash(item)] | ||
80 | return ok | ||
81 | } | ||
82 | |||
83 | // Len returns the amount of items in the set. | ||
84 | func (s *Set) Len() int { | ||
85 | return len(s.m) | ||
86 | } | ||
87 | |||
88 | // List returns the elements of this set in slice format. | ||
89 | // | ||
90 | // The order of the returned elements is deterministic. Given the same | ||
91 | // set, the order of this will always be the same. | ||
92 | func (s *Set) List() []interface{} { | ||
93 | result := make([]interface{}, len(s.m)) | ||
94 | for i, k := range s.listCode() { | ||
95 | result[i] = s.m[k] | ||
96 | } | ||
97 | |||
98 | return result | ||
99 | } | ||
100 | |||
101 | // Difference performs a set difference of the two sets, returning | ||
102 | // a new third set that has only the elements unique to this set. | ||
103 | func (s *Set) Difference(other *Set) *Set { | ||
104 | result := &Set{F: s.F} | ||
105 | result.once.Do(result.init) | ||
106 | |||
107 | for k, v := range s.m { | ||
108 | if _, ok := other.m[k]; !ok { | ||
109 | result.m[k] = v | ||
110 | } | ||
111 | } | ||
112 | |||
113 | return result | ||
114 | } | ||
115 | |||
116 | // Intersection performs the set intersection of the two sets | ||
117 | // and returns a new third set. | ||
118 | func (s *Set) Intersection(other *Set) *Set { | ||
119 | result := &Set{F: s.F} | ||
120 | result.once.Do(result.init) | ||
121 | |||
122 | for k, v := range s.m { | ||
123 | if _, ok := other.m[k]; ok { | ||
124 | result.m[k] = v | ||
125 | } | ||
126 | } | ||
127 | |||
128 | return result | ||
129 | } | ||
130 | |||
131 | // Union performs the set union of the two sets and returns a new third | ||
132 | // set. | ||
133 | func (s *Set) Union(other *Set) *Set { | ||
134 | result := &Set{F: s.F} | ||
135 | result.once.Do(result.init) | ||
136 | |||
137 | for k, v := range s.m { | ||
138 | result.m[k] = v | ||
139 | } | ||
140 | for k, v := range other.m { | ||
141 | result.m[k] = v | ||
142 | } | ||
143 | |||
144 | return result | ||
145 | } | ||
146 | |||
147 | func (s *Set) Equal(raw interface{}) bool { | ||
148 | other, ok := raw.(*Set) | ||
149 | if !ok { | ||
150 | return false | ||
151 | } | ||
152 | |||
153 | return reflect.DeepEqual(s.m, other.m) | ||
154 | } | ||
155 | |||
156 | func (s *Set) GoString() string { | ||
157 | return fmt.Sprintf("*Set(%#v)", s.m) | ||
158 | } | ||
159 | |||
160 | func (s *Set) init() { | ||
161 | s.m = make(map[string]interface{}) | ||
162 | } | ||
163 | |||
164 | func (s *Set) add(item interface{}, computed bool) string { | ||
165 | s.once.Do(s.init) | ||
166 | |||
167 | code := s.hash(item) | ||
168 | if computed { | ||
169 | code = "~" + code | ||
170 | } | ||
171 | |||
172 | if _, ok := s.m[code]; !ok { | ||
173 | s.m[code] = item | ||
174 | } | ||
175 | |||
176 | return code | ||
177 | } | ||
178 | |||
179 | func (s *Set) hash(item interface{}) string { | ||
180 | code := s.F(item) | ||
181 | // Always return a nonnegative hashcode. | ||
182 | if code < 0 { | ||
183 | code = -code | ||
184 | } | ||
185 | return strconv.Itoa(code) | ||
186 | } | ||
187 | |||
188 | func (s *Set) remove(item interface{}) string { | ||
189 | s.once.Do(s.init) | ||
190 | |||
191 | code := s.hash(item) | ||
192 | delete(s.m, code) | ||
193 | |||
194 | return code | ||
195 | } | ||
196 | |||
197 | func (s *Set) index(item interface{}) int { | ||
198 | return sort.SearchStrings(s.listCode(), s.hash(item)) | ||
199 | } | ||
200 | |||
201 | func (s *Set) listCode() []string { | ||
202 | // Sort the hash codes so the order of the list is deterministic | ||
203 | keys := make([]string, 0, len(s.m)) | ||
204 | for k := range s.m { | ||
205 | keys = append(keys, k) | ||
206 | } | ||
207 | sort.Sort(sort.StringSlice(keys)) | ||
208 | return keys | ||
209 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/testing.go b/vendor/github.com/hashicorp/terraform/helper/schema/testing.go new file mode 100644 index 0000000..9765bdb --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/testing.go | |||
@@ -0,0 +1,30 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "testing" | ||
5 | |||
6 | "github.com/hashicorp/terraform/config" | ||
7 | "github.com/hashicorp/terraform/terraform" | ||
8 | ) | ||
9 | |||
10 | // TestResourceDataRaw creates a ResourceData from a raw configuration map. | ||
11 | func TestResourceDataRaw( | ||
12 | t *testing.T, schema map[string]*Schema, raw map[string]interface{}) *ResourceData { | ||
13 | c, err := config.NewRawConfig(raw) | ||
14 | if err != nil { | ||
15 | t.Fatalf("err: %s", err) | ||
16 | } | ||
17 | |||
18 | sm := schemaMap(schema) | ||
19 | diff, err := sm.Diff(nil, terraform.NewResourceConfig(c)) | ||
20 | if err != nil { | ||
21 | t.Fatalf("err: %s", err) | ||
22 | } | ||
23 | |||
24 | result, err := sm.Data(nil, diff) | ||
25 | if err != nil { | ||
26 | t.Fatalf("err: %s", err) | ||
27 | } | ||
28 | |||
29 | return result | ||
30 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/valuetype.go b/vendor/github.com/hashicorp/terraform/helper/schema/valuetype.go new file mode 100644 index 0000000..9286987 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/valuetype.go | |||
@@ -0,0 +1,21 @@ | |||
1 | package schema | ||
2 | |||
3 | //go:generate stringer -type=ValueType valuetype.go | ||
4 | |||
5 | // ValueType is an enum of the type that can be represented by a schema. | ||
6 | type ValueType int | ||
7 | |||
8 | const ( | ||
9 | TypeInvalid ValueType = iota | ||
10 | TypeBool | ||
11 | TypeInt | ||
12 | TypeFloat | ||
13 | TypeString | ||
14 | TypeList | ||
15 | TypeMap | ||
16 | TypeSet | ||
17 | typeObject | ||
18 | ) | ||
19 | |||
20 | // NOTE: ValueType has more functions defined on it in schema.go. We can't | ||
21 | // put them here because we reference other files. | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go b/vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go new file mode 100644 index 0000000..1610cec --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go | |||
@@ -0,0 +1,16 @@ | |||
1 | // Code generated by "stringer -type=ValueType valuetype.go"; DO NOT EDIT. | ||
2 | |||
3 | package schema | ||
4 | |||
5 | import "fmt" | ||
6 | |||
7 | const _ValueType_name = "TypeInvalidTypeBoolTypeIntTypeFloatTypeStringTypeListTypeMapTypeSettypeObject" | ||
8 | |||
9 | var _ValueType_index = [...]uint8{0, 11, 19, 26, 35, 45, 53, 60, 67, 77} | ||
10 | |||
11 | func (i ValueType) String() string { | ||
12 | if i < 0 || i >= ValueType(len(_ValueType_index)-1) { | ||
13 | return fmt.Sprintf("ValueType(%d)", i) | ||
14 | } | ||
15 | return _ValueType_name[_ValueType_index[i]:_ValueType_index[i+1]] | ||
16 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/shadow/closer.go b/vendor/github.com/hashicorp/terraform/helper/shadow/closer.go new file mode 100644 index 0000000..7edd5e7 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/shadow/closer.go | |||
@@ -0,0 +1,80 @@ | |||
1 | package shadow | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "io" | ||
6 | "reflect" | ||
7 | |||
8 | "github.com/hashicorp/go-multierror" | ||
9 | "github.com/mitchellh/reflectwalk" | ||
10 | ) | ||
11 | |||
12 | // Close will close all shadow values within the given structure. | ||
13 | // | ||
14 | // This uses reflection to walk the structure, find all shadow elements, | ||
15 | // and close them. Currently this will only find struct fields that are | ||
16 | // shadow values, and not slice elements, etc. | ||
17 | func Close(v interface{}) error { | ||
18 | // We require a pointer so we can address the internal fields | ||
19 | val := reflect.ValueOf(v) | ||
20 | if val.Kind() != reflect.Ptr { | ||
21 | return fmt.Errorf("value must be a pointer") | ||
22 | } | ||
23 | |||
24 | // Walk and close | ||
25 | var w closeWalker | ||
26 | if err := reflectwalk.Walk(v, &w); err != nil { | ||
27 | return err | ||
28 | } | ||
29 | |||
30 | return w.Err | ||
31 | } | ||
32 | |||
33 | type closeWalker struct { | ||
34 | Err error | ||
35 | } | ||
36 | |||
37 | func (w *closeWalker) Struct(reflect.Value) error { | ||
38 | // Do nothing. We implement this for reflectwalk.StructWalker | ||
39 | return nil | ||
40 | } | ||
41 | |||
42 | func (w *closeWalker) StructField(f reflect.StructField, v reflect.Value) error { | ||
43 | // Not sure why this would be but lets avoid some panics | ||
44 | if !v.IsValid() { | ||
45 | return nil | ||
46 | } | ||
47 | |||
48 | // Empty for exported, so don't check unexported fields | ||
49 | if f.PkgPath != "" { | ||
50 | return nil | ||
51 | } | ||
52 | |||
53 | // Verify the io.Closer is in this package | ||
54 | typ := v.Type() | ||
55 | if typ.PkgPath() != "github.com/hashicorp/terraform/helper/shadow" { | ||
56 | return nil | ||
57 | } | ||
58 | |||
59 | // We're looking for an io.Closer | ||
60 | raw := v.Interface() | ||
61 | if raw == nil { | ||
62 | return nil | ||
63 | } | ||
64 | |||
65 | closer, ok := raw.(io.Closer) | ||
66 | if !ok && v.CanAddr() { | ||
67 | closer, ok = v.Addr().Interface().(io.Closer) | ||
68 | } | ||
69 | if !ok { | ||
70 | return reflectwalk.SkipEntry | ||
71 | } | ||
72 | |||
73 | // Close it | ||
74 | if err := closer.Close(); err != nil { | ||
75 | w.Err = multierror.Append(w.Err, err) | ||
76 | } | ||
77 | |||
78 | // Don't go into the struct field | ||
79 | return reflectwalk.SkipEntry | ||
80 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/shadow/compared_value.go b/vendor/github.com/hashicorp/terraform/helper/shadow/compared_value.go new file mode 100644 index 0000000..4223e92 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/shadow/compared_value.go | |||
@@ -0,0 +1,128 @@ | |||
1 | package shadow | ||
2 | |||
3 | import ( | ||
4 | "sync" | ||
5 | ) | ||
6 | |||
7 | // ComparedValue is a struct that finds a value by comparing some key | ||
8 | // to the list of stored values. This is useful when there is no easy | ||
9 | // uniquely identifying key that works in a map (for that, use KeyedValue). | ||
10 | // | ||
11 | // ComparedValue is very expensive, relative to other Value types. Try to | ||
12 | // limit the number of values stored in a ComparedValue by potentially | ||
13 | // nesting it within a KeyedValue (a keyed value points to a compared value, | ||
14 | // for example). | ||
15 | type ComparedValue struct { | ||
16 | // Func is a function that is given the lookup key and a single | ||
17 | // stored value. If it matches, it returns true. | ||
18 | Func func(k, v interface{}) bool | ||
19 | |||
20 | lock sync.Mutex | ||
21 | once sync.Once | ||
22 | closed bool | ||
23 | values []interface{} | ||
24 | waiters map[interface{}]*Value | ||
25 | } | ||
26 | |||
27 | // Close closes the value. This can never fail. For a definition of | ||
28 | // "close" see the ErrClosed docs. | ||
29 | func (w *ComparedValue) Close() error { | ||
30 | w.lock.Lock() | ||
31 | defer w.lock.Unlock() | ||
32 | |||
33 | // Set closed to true always | ||
34 | w.closed = true | ||
35 | |||
36 | // For all waiters, complete with ErrClosed | ||
37 | for k, val := range w.waiters { | ||
38 | val.SetValue(ErrClosed) | ||
39 | delete(w.waiters, k) | ||
40 | } | ||
41 | |||
42 | return nil | ||
43 | } | ||
44 | |||
45 | // Value returns the value that was set for the given key, or blocks | ||
46 | // until one is available. | ||
47 | func (w *ComparedValue) Value(k interface{}) interface{} { | ||
48 | v, val := w.valueWaiter(k) | ||
49 | if val == nil { | ||
50 | return v | ||
51 | } | ||
52 | |||
53 | return val.Value() | ||
54 | } | ||
55 | |||
56 | // ValueOk gets the value for the given key, returning immediately if the | ||
57 | // value doesn't exist. The second return argument is true if the value exists. | ||
58 | func (w *ComparedValue) ValueOk(k interface{}) (interface{}, bool) { | ||
59 | v, val := w.valueWaiter(k) | ||
60 | return v, val == nil | ||
61 | } | ||
62 | |||
63 | func (w *ComparedValue) SetValue(v interface{}) { | ||
64 | w.lock.Lock() | ||
65 | defer w.lock.Unlock() | ||
66 | w.once.Do(w.init) | ||
67 | |||
68 | // Check if we already have this exact value (by simply comparing | ||
69 | // with == directly). If we do, then we don't insert it again. | ||
70 | found := false | ||
71 | for _, v2 := range w.values { | ||
72 | if v == v2 { | ||
73 | found = true | ||
74 | break | ||
75 | } | ||
76 | } | ||
77 | |||
78 | if !found { | ||
79 | // Set the value, always | ||
80 | w.values = append(w.values, v) | ||
81 | } | ||
82 | |||
83 | // Go through the waiters | ||
84 | for k, val := range w.waiters { | ||
85 | if w.Func(k, v) { | ||
86 | val.SetValue(v) | ||
87 | delete(w.waiters, k) | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | |||
92 | func (w *ComparedValue) valueWaiter(k interface{}) (interface{}, *Value) { | ||
93 | w.lock.Lock() | ||
94 | w.once.Do(w.init) | ||
95 | |||
96 | // Look for a pre-existing value | ||
97 | for _, v := range w.values { | ||
98 | if w.Func(k, v) { | ||
99 | w.lock.Unlock() | ||
100 | return v, nil | ||
101 | } | ||
102 | } | ||
103 | |||
104 | // If we're closed, return that | ||
105 | if w.closed { | ||
106 | w.lock.Unlock() | ||
107 | return ErrClosed, nil | ||
108 | } | ||
109 | |||
110 | // Pre-existing value doesn't exist, create a waiter | ||
111 | val := w.waiters[k] | ||
112 | if val == nil { | ||
113 | val = new(Value) | ||
114 | w.waiters[k] = val | ||
115 | } | ||
116 | w.lock.Unlock() | ||
117 | |||
118 | // Return the waiter | ||
119 | return nil, val | ||
120 | } | ||
121 | |||
122 | // Must be called with w.lock held. | ||
123 | func (w *ComparedValue) init() { | ||
124 | w.waiters = make(map[interface{}]*Value) | ||
125 | if w.Func == nil { | ||
126 | w.Func = func(k, v interface{}) bool { return k == v } | ||
127 | } | ||
128 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/shadow/keyed_value.go b/vendor/github.com/hashicorp/terraform/helper/shadow/keyed_value.go new file mode 100644 index 0000000..432b036 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/shadow/keyed_value.go | |||
@@ -0,0 +1,151 @@ | |||
1 | package shadow | ||
2 | |||
3 | import ( | ||
4 | "sync" | ||
5 | ) | ||
6 | |||
7 | // KeyedValue is a struct that coordinates a value by key. If a value is | ||
8 | // not available for a give key, it'll block until it is available. | ||
9 | type KeyedValue struct { | ||
10 | lock sync.Mutex | ||
11 | once sync.Once | ||
12 | values map[string]interface{} | ||
13 | waiters map[string]*Value | ||
14 | closed bool | ||
15 | } | ||
16 | |||
17 | // Close closes the value. This can never fail. For a definition of | ||
18 | // "close" see the ErrClosed docs. | ||
19 | func (w *KeyedValue) Close() error { | ||
20 | w.lock.Lock() | ||
21 | defer w.lock.Unlock() | ||
22 | |||
23 | // Set closed to true always | ||
24 | w.closed = true | ||
25 | |||
26 | // For all waiters, complete with ErrClosed | ||
27 | for k, val := range w.waiters { | ||
28 | val.SetValue(ErrClosed) | ||
29 | delete(w.waiters, k) | ||
30 | } | ||
31 | |||
32 | return nil | ||
33 | } | ||
34 | |||
35 | // Value returns the value that was set for the given key, or blocks | ||
36 | // until one is available. | ||
37 | func (w *KeyedValue) Value(k string) interface{} { | ||
38 | w.lock.Lock() | ||
39 | v, val := w.valueWaiter(k) | ||
40 | w.lock.Unlock() | ||
41 | |||
42 | // If we have no waiter, then return the value | ||
43 | if val == nil { | ||
44 | return v | ||
45 | } | ||
46 | |||
47 | // We have a waiter, so wait | ||
48 | return val.Value() | ||
49 | } | ||
50 | |||
51 | // WaitForChange waits for the value with the given key to be set again. | ||
52 | // If the key isn't set, it'll wait for an initial value. Note that while | ||
53 | // it is called "WaitForChange", the value isn't guaranteed to _change_; | ||
54 | // this will return when a SetValue is called for the given k. | ||
55 | func (w *KeyedValue) WaitForChange(k string) interface{} { | ||
56 | w.lock.Lock() | ||
57 | w.once.Do(w.init) | ||
58 | |||
59 | // If we're closed, we're closed | ||
60 | if w.closed { | ||
61 | w.lock.Unlock() | ||
62 | return ErrClosed | ||
63 | } | ||
64 | |||
65 | // Check for an active waiter. If there isn't one, make it | ||
66 | val := w.waiters[k] | ||
67 | if val == nil { | ||
68 | val = new(Value) | ||
69 | w.waiters[k] = val | ||
70 | } | ||
71 | w.lock.Unlock() | ||
72 | |||
73 | // And wait | ||
74 | return val.Value() | ||
75 | } | ||
76 | |||
77 | // ValueOk gets the value for the given key, returning immediately if the | ||
78 | // value doesn't exist. The second return argument is true if the value exists. | ||
79 | func (w *KeyedValue) ValueOk(k string) (interface{}, bool) { | ||
80 | w.lock.Lock() | ||
81 | defer w.lock.Unlock() | ||
82 | |||
83 | v, val := w.valueWaiter(k) | ||
84 | return v, val == nil | ||
85 | } | ||
86 | |||
87 | func (w *KeyedValue) SetValue(k string, v interface{}) { | ||
88 | w.lock.Lock() | ||
89 | defer w.lock.Unlock() | ||
90 | w.setValue(k, v) | ||
91 | } | ||
92 | |||
93 | // Init will initialize the key to a given value only if the key has | ||
94 | // not been set before. This is safe to call multiple times and in parallel. | ||
95 | func (w *KeyedValue) Init(k string, v interface{}) { | ||
96 | w.lock.Lock() | ||
97 | defer w.lock.Unlock() | ||
98 | |||
99 | // If we have a waiter, set the value. | ||
100 | _, val := w.valueWaiter(k) | ||
101 | if val != nil { | ||
102 | w.setValue(k, v) | ||
103 | } | ||
104 | } | ||
105 | |||
106 | // Must be called with w.lock held. | ||
107 | func (w *KeyedValue) init() { | ||
108 | w.values = make(map[string]interface{}) | ||
109 | w.waiters = make(map[string]*Value) | ||
110 | } | ||
111 | |||
112 | // setValue is like SetValue but assumes the lock is held. | ||
113 | func (w *KeyedValue) setValue(k string, v interface{}) { | ||
114 | w.once.Do(w.init) | ||
115 | |||
116 | // Set the value, always | ||
117 | w.values[k] = v | ||
118 | |||
119 | // If we have a waiter, set it | ||
120 | if val, ok := w.waiters[k]; ok { | ||
121 | val.SetValue(v) | ||
122 | delete(w.waiters, k) | ||
123 | } | ||
124 | } | ||
125 | |||
126 | // valueWaiter gets the value or the Value waiter for a given key. | ||
127 | // | ||
128 | // This must be called with lock held. | ||
129 | func (w *KeyedValue) valueWaiter(k string) (interface{}, *Value) { | ||
130 | w.once.Do(w.init) | ||
131 | |||
132 | // If we have this value already, return it | ||
133 | if v, ok := w.values[k]; ok { | ||
134 | return v, nil | ||
135 | } | ||
136 | |||
137 | // If we're closed, return that | ||
138 | if w.closed { | ||
139 | return ErrClosed, nil | ||
140 | } | ||
141 | |||
142 | // No pending value, check for a waiter | ||
143 | val := w.waiters[k] | ||
144 | if val == nil { | ||
145 | val = new(Value) | ||
146 | w.waiters[k] = val | ||
147 | } | ||
148 | |||
149 | // Return the waiter | ||
150 | return nil, val | ||
151 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/shadow/ordered_value.go b/vendor/github.com/hashicorp/terraform/helper/shadow/ordered_value.go new file mode 100644 index 0000000..0a43d4d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/shadow/ordered_value.go | |||
@@ -0,0 +1,66 @@ | |||
1 | package shadow | ||
2 | |||
3 | import ( | ||
4 | "container/list" | ||
5 | "sync" | ||
6 | ) | ||
7 | |||
8 | // OrderedValue is a struct that keeps track of a value in the order | ||
9 | // it is set. Each time Value() is called, it will return the most recent | ||
10 | // calls value then discard it. | ||
11 | // | ||
12 | // This is unlike Value that returns the same value once it is set. | ||
13 | type OrderedValue struct { | ||
14 | lock sync.Mutex | ||
15 | values *list.List | ||
16 | waiters *list.List | ||
17 | } | ||
18 | |||
19 | // Value returns the last value that was set, or blocks until one | ||
20 | // is received. | ||
21 | func (w *OrderedValue) Value() interface{} { | ||
22 | w.lock.Lock() | ||
23 | |||
24 | // If we have a pending value already, use it | ||
25 | if w.values != nil && w.values.Len() > 0 { | ||
26 | front := w.values.Front() | ||
27 | w.values.Remove(front) | ||
28 | w.lock.Unlock() | ||
29 | return front.Value | ||
30 | } | ||
31 | |||
32 | // No pending value, create a waiter | ||
33 | if w.waiters == nil { | ||
34 | w.waiters = list.New() | ||
35 | } | ||
36 | |||
37 | var val Value | ||
38 | w.waiters.PushBack(&val) | ||
39 | w.lock.Unlock() | ||
40 | |||
41 | // Return the value once we have it | ||
42 | return val.Value() | ||
43 | } | ||
44 | |||
45 | // SetValue sets the latest value. | ||
46 | func (w *OrderedValue) SetValue(v interface{}) { | ||
47 | w.lock.Lock() | ||
48 | defer w.lock.Unlock() | ||
49 | |||
50 | // If we have a waiter, notify it | ||
51 | if w.waiters != nil && w.waiters.Len() > 0 { | ||
52 | front := w.waiters.Front() | ||
53 | w.waiters.Remove(front) | ||
54 | |||
55 | val := front.Value.(*Value) | ||
56 | val.SetValue(v) | ||
57 | return | ||
58 | } | ||
59 | |||
60 | // Add it to the list of values | ||
61 | if w.values == nil { | ||
62 | w.values = list.New() | ||
63 | } | ||
64 | |||
65 | w.values.PushBack(v) | ||
66 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/shadow/value.go b/vendor/github.com/hashicorp/terraform/helper/shadow/value.go new file mode 100644 index 0000000..2413335 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/shadow/value.go | |||
@@ -0,0 +1,79 @@ | |||
1 | package shadow | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "sync" | ||
6 | ) | ||
7 | |||
8 | // ErrClosed is returned by any closed values. | ||
9 | // | ||
10 | // A "closed value" is when the shadow has been notified that the real | ||
11 | // side is complete and any blocking values will _never_ be satisfied | ||
12 | // in the future. In this case, this error is returned. If a value is already | ||
13 | // available, that is still returned. | ||
14 | var ErrClosed = errors.New("shadow closed") | ||
15 | |||
16 | // Value is a struct that coordinates a value between two | ||
17 | // parallel routines. It is similar to atomic.Value except that when | ||
18 | // Value is called if it isn't set it will wait for it. | ||
19 | // | ||
20 | // The Value can be closed with Close, which will cause any future | ||
21 | // blocking operations to return immediately with ErrClosed. | ||
22 | type Value struct { | ||
23 | lock sync.Mutex | ||
24 | cond *sync.Cond | ||
25 | value interface{} | ||
26 | valueSet bool | ||
27 | } | ||
28 | |||
29 | // Close closes the value. This can never fail. For a definition of | ||
30 | // "close" see the struct docs. | ||
31 | func (w *Value) Close() error { | ||
32 | w.lock.Lock() | ||
33 | set := w.valueSet | ||
34 | w.lock.Unlock() | ||
35 | |||
36 | // If we haven't set the value, set it | ||
37 | if !set { | ||
38 | w.SetValue(ErrClosed) | ||
39 | } | ||
40 | |||
41 | // Done | ||
42 | return nil | ||
43 | } | ||
44 | |||
45 | // Value returns the value that was set. | ||
46 | func (w *Value) Value() interface{} { | ||
47 | w.lock.Lock() | ||
48 | defer w.lock.Unlock() | ||
49 | |||
50 | // If we already have a value just return | ||
51 | for !w.valueSet { | ||
52 | // No value, setup the condition variable if we have to | ||
53 | if w.cond == nil { | ||
54 | w.cond = sync.NewCond(&w.lock) | ||
55 | } | ||
56 | |||
57 | // Wait on it | ||
58 | w.cond.Wait() | ||
59 | } | ||
60 | |||
61 | // Return the value | ||
62 | return w.value | ||
63 | } | ||
64 | |||
65 | // SetValue sets the value. | ||
66 | func (w *Value) SetValue(v interface{}) { | ||
67 | w.lock.Lock() | ||
68 | defer w.lock.Unlock() | ||
69 | |||
70 | // Set the value | ||
71 | w.valueSet = true | ||
72 | w.value = v | ||
73 | |||
74 | // If we have a condition, clear it | ||
75 | if w.cond != nil { | ||
76 | w.cond.Broadcast() | ||
77 | w.cond = nil | ||
78 | } | ||
79 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/structure/expand_json.go b/vendor/github.com/hashicorp/terraform/helper/structure/expand_json.go new file mode 100644 index 0000000..b3eb90f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/structure/expand_json.go | |||
@@ -0,0 +1,11 @@ | |||
1 | package structure | ||
2 | |||
3 | import "encoding/json" | ||
4 | |||
5 | func ExpandJsonFromString(jsonString string) (map[string]interface{}, error) { | ||
6 | var result map[string]interface{} | ||
7 | |||
8 | err := json.Unmarshal([]byte(jsonString), &result) | ||
9 | |||
10 | return result, err | ||
11 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/structure/flatten_json.go b/vendor/github.com/hashicorp/terraform/helper/structure/flatten_json.go new file mode 100644 index 0000000..578ad2e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/structure/flatten_json.go | |||
@@ -0,0 +1,16 @@ | |||
1 | package structure | ||
2 | |||
3 | import "encoding/json" | ||
4 | |||
5 | func FlattenJsonToString(input map[string]interface{}) (string, error) { | ||
6 | if len(input) == 0 { | ||
7 | return "", nil | ||
8 | } | ||
9 | |||
10 | result, err := json.Marshal(input) | ||
11 | if err != nil { | ||
12 | return "", err | ||
13 | } | ||
14 | |||
15 | return string(result), nil | ||
16 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/structure/normalize_json.go b/vendor/github.com/hashicorp/terraform/helper/structure/normalize_json.go new file mode 100644 index 0000000..3256b47 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/structure/normalize_json.go | |||
@@ -0,0 +1,24 @@ | |||
1 | package structure | ||
2 | |||
3 | import "encoding/json" | ||
4 | |||
5 | // Takes a value containing JSON string and passes it through | ||
6 | // the JSON parser to normalize it, returns either a parsing | ||
7 | // error or normalized JSON string. | ||
8 | func NormalizeJsonString(jsonString interface{}) (string, error) { | ||
9 | var j interface{} | ||
10 | |||
11 | if jsonString == nil || jsonString.(string) == "" { | ||
12 | return "", nil | ||
13 | } | ||
14 | |||
15 | s := jsonString.(string) | ||
16 | |||
17 | err := json.Unmarshal([]byte(s), &j) | ||
18 | if err != nil { | ||
19 | return s, err | ||
20 | } | ||
21 | |||
22 | bytes, _ := json.Marshal(j) | ||
23 | return string(bytes[:]), nil | ||
24 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/structure/suppress_json_diff.go b/vendor/github.com/hashicorp/terraform/helper/structure/suppress_json_diff.go new file mode 100644 index 0000000..46f794a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/structure/suppress_json_diff.go | |||
@@ -0,0 +1,21 @@ | |||
1 | package structure | ||
2 | |||
3 | import ( | ||
4 | "reflect" | ||
5 | |||
6 | "github.com/hashicorp/terraform/helper/schema" | ||
7 | ) | ||
8 | |||
9 | func SuppressJsonDiff(k, old, new string, d *schema.ResourceData) bool { | ||
10 | oldMap, err := ExpandJsonFromString(old) | ||
11 | if err != nil { | ||
12 | return false | ||
13 | } | ||
14 | |||
15 | newMap, err := ExpandJsonFromString(new) | ||
16 | if err != nil { | ||
17 | return false | ||
18 | } | ||
19 | |||
20 | return reflect.DeepEqual(oldMap, newMap) | ||
21 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/helper/validation/validation.go b/vendor/github.com/hashicorp/terraform/helper/validation/validation.go new file mode 100644 index 0000000..7b894f5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/validation/validation.go | |||
@@ -0,0 +1,108 @@ | |||
1 | package validation | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "net" | ||
6 | "strings" | ||
7 | |||
8 | "github.com/hashicorp/terraform/helper/schema" | ||
9 | "github.com/hashicorp/terraform/helper/structure" | ||
10 | ) | ||
11 | |||
12 | // IntBetween returns a SchemaValidateFunc which tests if the provided value | ||
13 | // is of type int and is between min and max (inclusive) | ||
14 | func IntBetween(min, max int) schema.SchemaValidateFunc { | ||
15 | return func(i interface{}, k string) (s []string, es []error) { | ||
16 | v, ok := i.(int) | ||
17 | if !ok { | ||
18 | es = append(es, fmt.Errorf("expected type of %s to be int", k)) | ||
19 | return | ||
20 | } | ||
21 | |||
22 | if v < min || v > max { | ||
23 | es = append(es, fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)) | ||
24 | return | ||
25 | } | ||
26 | |||
27 | return | ||
28 | } | ||
29 | } | ||
30 | |||
31 | // StringInSlice returns a SchemaValidateFunc which tests if the provided value | ||
32 | // is of type string and matches the value of an element in the valid slice | ||
33 | // will test with in lower case if ignoreCase is true | ||
34 | func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateFunc { | ||
35 | return func(i interface{}, k string) (s []string, es []error) { | ||
36 | v, ok := i.(string) | ||
37 | if !ok { | ||
38 | es = append(es, fmt.Errorf("expected type of %s to be string", k)) | ||
39 | return | ||
40 | } | ||
41 | |||
42 | for _, str := range valid { | ||
43 | if v == str || (ignoreCase && strings.ToLower(v) == strings.ToLower(str)) { | ||
44 | return | ||
45 | } | ||
46 | } | ||
47 | |||
48 | es = append(es, fmt.Errorf("expected %s to be one of %v, got %s", k, valid, v)) | ||
49 | return | ||
50 | } | ||
51 | } | ||
52 | |||
53 | // StringLenBetween returns a SchemaValidateFunc which tests if the provided value | ||
54 | // is of type string and has length between min and max (inclusive) | ||
55 | func StringLenBetween(min, max int) schema.SchemaValidateFunc { | ||
56 | return func(i interface{}, k string) (s []string, es []error) { | ||
57 | v, ok := i.(string) | ||
58 | if !ok { | ||
59 | es = append(es, fmt.Errorf("expected type of %s to be string", k)) | ||
60 | return | ||
61 | } | ||
62 | if len(v) < min || len(v) > max { | ||
63 | es = append(es, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %s", k, min, max, v)) | ||
64 | } | ||
65 | return | ||
66 | } | ||
67 | } | ||
68 | |||
69 | // CIDRNetwork returns a SchemaValidateFunc which tests if the provided value | ||
70 | // is of type string, is in valid CIDR network notation, and has significant bits between min and max (inclusive) | ||
71 | func CIDRNetwork(min, max int) schema.SchemaValidateFunc { | ||
72 | return func(i interface{}, k string) (s []string, es []error) { | ||
73 | v, ok := i.(string) | ||
74 | if !ok { | ||
75 | es = append(es, fmt.Errorf("expected type of %s to be string", k)) | ||
76 | return | ||
77 | } | ||
78 | |||
79 | _, ipnet, err := net.ParseCIDR(v) | ||
80 | if err != nil { | ||
81 | es = append(es, fmt.Errorf( | ||
82 | "expected %s to contain a valid CIDR, got: %s with err: %s", k, v, err)) | ||
83 | return | ||
84 | } | ||
85 | |||
86 | if ipnet == nil || v != ipnet.String() { | ||
87 | es = append(es, fmt.Errorf( | ||
88 | "expected %s to contain a valid network CIDR, expected %s, got %s", | ||
89 | k, ipnet, v)) | ||
90 | } | ||
91 | |||
92 | sigbits, _ := ipnet.Mask.Size() | ||
93 | if sigbits < min || sigbits > max { | ||
94 | es = append(es, fmt.Errorf( | ||
95 | "expected %q to contain a network CIDR with between %d and %d significant bits, got: %d", | ||
96 | k, min, max, sigbits)) | ||
97 | } | ||
98 | |||
99 | return | ||
100 | } | ||
101 | } | ||
102 | |||
103 | func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) { | ||
104 | if _, err := structure.NormalizeJsonString(v); err != nil { | ||
105 | errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) | ||
106 | } | ||
107 | return | ||
108 | } | ||