]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/helper/schema/provider.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[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/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
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
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
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
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 }