]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/helper/schema/provider.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / helper / schema / provider.go
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/config"
12 "github.com/hashicorp/terraform/configs/configschema"
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
62 // a mutex is required because TestReset can directly replace the stopCtx
63 stopMu sync.Mutex
64 stopCtx context.Context
65 stopCtxCancel context.CancelFunc
66 stopOnce sync.Once
67
68 TerraformVersion string
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
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
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
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
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
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
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.
256 diff, err := sm.Diff(nil, c, nil, p.meta, true)
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
298 return r.Diff(s, c, p.meta)
299 }
300
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
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))
330 for k := range p.ResourcesMap {
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,
348
349 // Indicates that a provider is compiled against a new enough
350 // version of core to support the GetSchema method.
351 SchemaAvailable: true,
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
429 return r.Diff(nil, c, p.meta)
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,
457
458 // Indicates that a provider is compiled against a new enough
459 // version of core to support the GetSchema method.
460 SchemaAvailable: true,
461 })
462 }
463
464 return result
465 }