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