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