]>
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" | |
c680a8e1 | 11 | "github.com/hashicorp/terraform/config" |
107c1cdb | 12 | "github.com/hashicorp/terraform/configs/configschema" |
bae9f6d2 JC |
13 | "github.com/hashicorp/terraform/terraform" |
14 | ) | |
15 | ||
16 | // Provider represents a resource provider in Terraform, and properly | |
17 | // implements all of the ResourceProvider API. | |
18 | // | |
19 | // By defining a schema for the configuration of the provider, the | |
20 | // map of supporting resources, and a configuration function, the schema | |
21 | // framework takes over and handles all the provider operations for you. | |
22 | // | |
23 | // After defining the provider structure, it is unlikely that you'll require any | |
24 | // of the methods on Provider itself. | |
25 | type Provider struct { | |
26 | // Schema is the schema for the configuration of this provider. If this | |
27 | // provider has no configuration, this can be omitted. | |
28 | // | |
29 | // The keys of this map are the configuration keys, and the value is | |
30 | // the schema describing the value of the configuration. | |
31 | Schema map[string]*Schema | |
32 | ||
33 | // ResourcesMap is the list of available resources that this provider | |
34 | // can manage, along with their Resource structure defining their | |
35 | // own schemas and CRUD operations. | |
36 | // | |
37 | // Provider automatically handles routing operations such as Apply, | |
38 | // Diff, etc. to the proper resource. | |
39 | ResourcesMap map[string]*Resource | |
40 | ||
41 | // DataSourcesMap is the collection of available data sources that | |
42 | // this provider implements, with a Resource instance defining | |
43 | // the schema and Read operation of each. | |
44 | // | |
45 | // Resource instances for data sources must have a Read function | |
46 | // and must *not* implement Create, Update or Delete. | |
47 | DataSourcesMap map[string]*Resource | |
48 | ||
49 | // ConfigureFunc is a function for configuring the provider. If the | |
50 | // provider doesn't need to be configured, this can be omitted. | |
51 | // | |
52 | // See the ConfigureFunc documentation for more information. | |
53 | ConfigureFunc ConfigureFunc | |
54 | ||
55 | // MetaReset is called by TestReset to reset any state stored in the meta | |
56 | // interface. This is especially important if the StopContext is stored by | |
57 | // the provider. | |
58 | MetaReset func() error | |
59 | ||
60 | meta interface{} | |
61 | ||
15c0b25d | 62 | // a mutex is required because TestReset can directly replace the stopCtx |
bae9f6d2 JC |
63 | stopMu sync.Mutex |
64 | stopCtx context.Context | |
65 | stopCtxCancel context.CancelFunc | |
66 | stopOnce sync.Once | |
107c1cdb ND |
67 | |
68 | TerraformVersion string | |
bae9f6d2 JC |
69 | } |
70 | ||
71 | // ConfigureFunc is the function used to configure a Provider. | |
72 | // | |
73 | // The interface{} value returned by this function is stored and passed into | |
74 | // the subsequent resources as the meta parameter. This return value is | |
75 | // usually used to pass along a configured API client, a configuration | |
76 | // structure, etc. | |
77 | type ConfigureFunc func(*ResourceData) (interface{}, error) | |
78 | ||
79 | // InternalValidate should be called to validate the structure | |
80 | // of the provider. | |
81 | // | |
82 | // This should be called in a unit test for any provider to verify | |
83 | // before release that a provider is properly configured for use with | |
84 | // this library. | |
85 | func (p *Provider) InternalValidate() error { | |
86 | if p == nil { | |
87 | return errors.New("provider is nil") | |
88 | } | |
89 | ||
90 | var validationErrors error | |
91 | sm := schemaMap(p.Schema) | |
92 | if err := sm.InternalValidate(sm); err != nil { | |
93 | validationErrors = multierror.Append(validationErrors, err) | |
94 | } | |
95 | ||
c680a8e1 RS |
96 | // Provider-specific checks |
97 | for k, _ := range sm { | |
98 | if isReservedProviderFieldName(k) { | |
99 | return fmt.Errorf("%s is a reserved field name for a provider", k) | |
100 | } | |
101 | } | |
102 | ||
bae9f6d2 JC |
103 | for k, r := range p.ResourcesMap { |
104 | if err := r.InternalValidate(nil, true); err != nil { | |
105 | validationErrors = multierror.Append(validationErrors, fmt.Errorf("resource %s: %s", k, err)) | |
106 | } | |
107 | } | |
108 | ||
109 | for k, r := range p.DataSourcesMap { | |
110 | if err := r.InternalValidate(nil, false); err != nil { | |
111 | validationErrors = multierror.Append(validationErrors, fmt.Errorf("data source %s: %s", k, err)) | |
112 | } | |
113 | } | |
114 | ||
115 | return validationErrors | |
116 | } | |
117 | ||
c680a8e1 RS |
118 | func isReservedProviderFieldName(name string) bool { |
119 | for _, reservedName := range config.ReservedProviderFields { | |
120 | if name == reservedName { | |
121 | return true | |
122 | } | |
123 | } | |
124 | return false | |
125 | } | |
126 | ||
bae9f6d2 JC |
127 | // Meta returns the metadata associated with this provider that was |
128 | // returned by the Configure call. It will be nil until Configure is called. | |
129 | func (p *Provider) Meta() interface{} { | |
130 | return p.meta | |
131 | } | |
132 | ||
133 | // SetMeta can be used to forcefully set the Meta object of the provider. | |
134 | // Note that if Configure is called the return value will override anything | |
135 | // set here. | |
136 | func (p *Provider) SetMeta(v interface{}) { | |
137 | p.meta = v | |
138 | } | |
139 | ||
140 | // Stopped reports whether the provider has been stopped or not. | |
141 | func (p *Provider) Stopped() bool { | |
142 | ctx := p.StopContext() | |
143 | select { | |
144 | case <-ctx.Done(): | |
145 | return true | |
146 | default: | |
147 | return false | |
148 | } | |
149 | } | |
150 | ||
151 | // StopCh returns a channel that is closed once the provider is stopped. | |
152 | func (p *Provider) StopContext() context.Context { | |
153 | p.stopOnce.Do(p.stopInit) | |
154 | ||
155 | p.stopMu.Lock() | |
156 | defer p.stopMu.Unlock() | |
157 | ||
158 | return p.stopCtx | |
159 | } | |
160 | ||
161 | func (p *Provider) stopInit() { | |
162 | p.stopMu.Lock() | |
163 | defer p.stopMu.Unlock() | |
164 | ||
165 | p.stopCtx, p.stopCtxCancel = context.WithCancel(context.Background()) | |
166 | } | |
167 | ||
168 | // Stop implementation of terraform.ResourceProvider interface. | |
169 | func (p *Provider) Stop() error { | |
170 | p.stopOnce.Do(p.stopInit) | |
171 | ||
172 | p.stopMu.Lock() | |
173 | defer p.stopMu.Unlock() | |
174 | ||
175 | p.stopCtxCancel() | |
176 | return nil | |
177 | } | |
178 | ||
179 | // TestReset resets any state stored in the Provider, and will call TestReset | |
180 | // on Meta if it implements the TestProvider interface. | |
181 | // This may be used to reset the schema.Provider at the start of a test, and is | |
182 | // automatically called by resource.Test. | |
183 | func (p *Provider) TestReset() error { | |
184 | p.stopInit() | |
185 | if p.MetaReset != nil { | |
186 | return p.MetaReset() | |
187 | } | |
188 | return nil | |
189 | } | |
190 | ||
15c0b25d AP |
191 | // GetSchema implementation of terraform.ResourceProvider interface |
192 | func (p *Provider) GetSchema(req *terraform.ProviderSchemaRequest) (*terraform.ProviderSchema, error) { | |
193 | resourceTypes := map[string]*configschema.Block{} | |
194 | dataSources := map[string]*configschema.Block{} | |
195 | ||
196 | for _, name := range req.ResourceTypes { | |
197 | if r, exists := p.ResourcesMap[name]; exists { | |
198 | resourceTypes[name] = r.CoreConfigSchema() | |
199 | } | |
200 | } | |
201 | for _, name := range req.DataSources { | |
202 | if r, exists := p.DataSourcesMap[name]; exists { | |
203 | dataSources[name] = r.CoreConfigSchema() | |
204 | } | |
205 | } | |
206 | ||
207 | return &terraform.ProviderSchema{ | |
208 | Provider: schemaMap(p.Schema).CoreConfigSchema(), | |
209 | ResourceTypes: resourceTypes, | |
210 | DataSources: dataSources, | |
211 | }, nil | |
212 | } | |
213 | ||
bae9f6d2 JC |
214 | // Input implementation of terraform.ResourceProvider interface. |
215 | func (p *Provider) Input( | |
216 | input terraform.UIInput, | |
217 | c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { | |
218 | return schemaMap(p.Schema).Input(input, c) | |
219 | } | |
220 | ||
221 | // Validate implementation of terraform.ResourceProvider interface. | |
222 | func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) { | |
223 | if err := p.InternalValidate(); err != nil { | |
224 | return nil, []error{fmt.Errorf( | |
225 | "Internal validation of the provider failed! This is always a bug\n"+ | |
226 | "with the provider itself, and not a user issue. Please report\n"+ | |
227 | "this bug:\n\n%s", err)} | |
228 | } | |
229 | ||
230 | return schemaMap(p.Schema).Validate(c) | |
231 | } | |
232 | ||
233 | // ValidateResource implementation of terraform.ResourceProvider interface. | |
234 | func (p *Provider) ValidateResource( | |
235 | t string, c *terraform.ResourceConfig) ([]string, []error) { | |
236 | r, ok := p.ResourcesMap[t] | |
237 | if !ok { | |
238 | return nil, []error{fmt.Errorf( | |
239 | "Provider doesn't support resource: %s", t)} | |
240 | } | |
241 | ||
242 | return r.Validate(c) | |
243 | } | |
244 | ||
245 | // Configure implementation of terraform.ResourceProvider interface. | |
246 | func (p *Provider) Configure(c *terraform.ResourceConfig) error { | |
247 | // No configuration | |
248 | if p.ConfigureFunc == nil { | |
249 | return nil | |
250 | } | |
251 | ||
252 | sm := schemaMap(p.Schema) | |
253 | ||
254 | // Get a ResourceData for this configuration. To do this, we actually | |
255 | // generate an intermediary "diff" although that is never exposed. | |
107c1cdb | 256 | diff, err := sm.Diff(nil, c, nil, p.meta, true) |
bae9f6d2 JC |
257 | if err != nil { |
258 | return err | |
259 | } | |
260 | ||
261 | data, err := sm.Data(nil, diff) | |
262 | if err != nil { | |
263 | return err | |
264 | } | |
265 | ||
266 | meta, err := p.ConfigureFunc(data) | |
267 | if err != nil { | |
268 | return err | |
269 | } | |
270 | ||
271 | p.meta = meta | |
272 | return nil | |
273 | } | |
274 | ||
275 | // Apply implementation of terraform.ResourceProvider interface. | |
276 | func (p *Provider) Apply( | |
277 | info *terraform.InstanceInfo, | |
278 | s *terraform.InstanceState, | |
279 | d *terraform.InstanceDiff) (*terraform.InstanceState, error) { | |
280 | r, ok := p.ResourcesMap[info.Type] | |
281 | if !ok { | |
282 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | |
283 | } | |
284 | ||
285 | return r.Apply(s, d, p.meta) | |
286 | } | |
287 | ||
288 | // Diff implementation of terraform.ResourceProvider interface. | |
289 | func (p *Provider) Diff( | |
290 | info *terraform.InstanceInfo, | |
291 | s *terraform.InstanceState, | |
292 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | |
293 | r, ok := p.ResourcesMap[info.Type] | |
294 | if !ok { | |
295 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | |
296 | } | |
297 | ||
15c0b25d | 298 | return r.Diff(s, c, p.meta) |
bae9f6d2 JC |
299 | } |
300 | ||
107c1cdb ND |
301 | // SimpleDiff is used by the new protocol wrappers to get a diff that doesn't |
302 | // attempt to calculate ignore_changes. | |
303 | func (p *Provider) SimpleDiff( | |
304 | info *terraform.InstanceInfo, | |
305 | s *terraform.InstanceState, | |
306 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | |
307 | r, ok := p.ResourcesMap[info.Type] | |
308 | if !ok { | |
309 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | |
310 | } | |
311 | ||
312 | return r.simpleDiff(s, c, p.meta) | |
313 | } | |
314 | ||
bae9f6d2 JC |
315 | // Refresh implementation of terraform.ResourceProvider interface. |
316 | func (p *Provider) Refresh( | |
317 | info *terraform.InstanceInfo, | |
318 | s *terraform.InstanceState) (*terraform.InstanceState, error) { | |
319 | r, ok := p.ResourcesMap[info.Type] | |
320 | if !ok { | |
321 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | |
322 | } | |
323 | ||
324 | return r.Refresh(s, p.meta) | |
325 | } | |
326 | ||
327 | // Resources implementation of terraform.ResourceProvider interface. | |
328 | func (p *Provider) Resources() []terraform.ResourceType { | |
329 | keys := make([]string, 0, len(p.ResourcesMap)) | |
107c1cdb | 330 | for k := range p.ResourcesMap { |
bae9f6d2 JC |
331 | keys = append(keys, k) |
332 | } | |
333 | sort.Strings(keys) | |
334 | ||
335 | result := make([]terraform.ResourceType, 0, len(keys)) | |
336 | for _, k := range keys { | |
337 | resource := p.ResourcesMap[k] | |
338 | ||
339 | // This isn't really possible (it'd fail InternalValidate), but | |
340 | // we do it anyways to avoid a panic. | |
341 | if resource == nil { | |
342 | resource = &Resource{} | |
343 | } | |
344 | ||
345 | result = append(result, terraform.ResourceType{ | |
346 | Name: k, | |
347 | Importable: resource.Importer != nil, | |
15c0b25d AP |
348 | |
349 | // Indicates that a provider is compiled against a new enough | |
350 | // version of core to support the GetSchema method. | |
351 | SchemaAvailable: true, | |
bae9f6d2 JC |
352 | }) |
353 | } | |
354 | ||
355 | return result | |
356 | } | |
357 | ||
358 | func (p *Provider) ImportState( | |
359 | info *terraform.InstanceInfo, | |
360 | id string) ([]*terraform.InstanceState, error) { | |
361 | // Find the resource | |
362 | r, ok := p.ResourcesMap[info.Type] | |
363 | if !ok { | |
364 | return nil, fmt.Errorf("unknown resource type: %s", info.Type) | |
365 | } | |
366 | ||
367 | // If it doesn't support import, error | |
368 | if r.Importer == nil { | |
369 | return nil, fmt.Errorf("resource %s doesn't support import", info.Type) | |
370 | } | |
371 | ||
372 | // Create the data | |
373 | data := r.Data(nil) | |
374 | data.SetId(id) | |
375 | data.SetType(info.Type) | |
376 | ||
377 | // Call the import function | |
378 | results := []*ResourceData{data} | |
379 | if r.Importer.State != nil { | |
380 | var err error | |
381 | results, err = r.Importer.State(data, p.meta) | |
382 | if err != nil { | |
383 | return nil, err | |
384 | } | |
385 | } | |
386 | ||
387 | // Convert the results to InstanceState values and return it | |
388 | states := make([]*terraform.InstanceState, len(results)) | |
389 | for i, r := range results { | |
390 | states[i] = r.State() | |
391 | } | |
392 | ||
393 | // Verify that all are non-nil. If there are any nil the error | |
394 | // isn't obvious so we circumvent that with a friendlier error. | |
395 | for _, s := range states { | |
396 | if s == nil { | |
397 | return nil, fmt.Errorf( | |
398 | "nil entry in ImportState results. This is always a bug with\n" + | |
399 | "the resource that is being imported. Please report this as\n" + | |
400 | "a bug to Terraform.") | |
401 | } | |
402 | } | |
403 | ||
404 | return states, nil | |
405 | } | |
406 | ||
407 | // ValidateDataSource implementation of terraform.ResourceProvider interface. | |
408 | func (p *Provider) ValidateDataSource( | |
409 | t string, c *terraform.ResourceConfig) ([]string, []error) { | |
410 | r, ok := p.DataSourcesMap[t] | |
411 | if !ok { | |
412 | return nil, []error{fmt.Errorf( | |
413 | "Provider doesn't support data source: %s", t)} | |
414 | } | |
415 | ||
416 | return r.Validate(c) | |
417 | } | |
418 | ||
419 | // ReadDataDiff implementation of terraform.ResourceProvider interface. | |
420 | func (p *Provider) ReadDataDiff( | |
421 | info *terraform.InstanceInfo, | |
422 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | |
423 | ||
424 | r, ok := p.DataSourcesMap[info.Type] | |
425 | if !ok { | |
426 | return nil, fmt.Errorf("unknown data source: %s", info.Type) | |
427 | } | |
428 | ||
15c0b25d | 429 | return r.Diff(nil, c, p.meta) |
bae9f6d2 JC |
430 | } |
431 | ||
432 | // RefreshData implementation of terraform.ResourceProvider interface. | |
433 | func (p *Provider) ReadDataApply( | |
434 | info *terraform.InstanceInfo, | |
435 | d *terraform.InstanceDiff) (*terraform.InstanceState, error) { | |
436 | ||
437 | r, ok := p.DataSourcesMap[info.Type] | |
438 | if !ok { | |
439 | return nil, fmt.Errorf("unknown data source: %s", info.Type) | |
440 | } | |
441 | ||
442 | return r.ReadDataApply(d, p.meta) | |
443 | } | |
444 | ||
445 | // DataSources implementation of terraform.ResourceProvider interface. | |
446 | func (p *Provider) DataSources() []terraform.DataSource { | |
447 | keys := make([]string, 0, len(p.DataSourcesMap)) | |
448 | for k, _ := range p.DataSourcesMap { | |
449 | keys = append(keys, k) | |
450 | } | |
451 | sort.Strings(keys) | |
452 | ||
453 | result := make([]terraform.DataSource, 0, len(keys)) | |
454 | for _, k := range keys { | |
455 | result = append(result, terraform.DataSource{ | |
456 | Name: k, | |
15c0b25d AP |
457 | |
458 | // Indicates that a provider is compiled against a new enough | |
459 | // version of core to support the GetSchema method. | |
460 | SchemaAvailable: true, | |
bae9f6d2 JC |
461 | }) |
462 | } | |
463 | ||
464 | return result | |
465 | } |