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