]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - 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
CommitLineData
bae9f6d2
JC
1package schema
2
3import (
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.
25type 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.
77type 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.
85func (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
118func 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.
129func (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.
136func (p *Provider) SetMeta(v interface{}) {
137 p.meta = v
138}
139
140// Stopped reports whether the provider has been stopped or not.
141func (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.
152func (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
161func (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.
169func (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.
183func (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
192func (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.
215func (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.
222func (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.
234func (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.
246func (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.
276func (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.
289func (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.
303func (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.
316func (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.
328func (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
358func (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.
408func (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.
420func (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.
433func (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.
446func (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}