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