1package schema
2
3import (
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.
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
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.
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
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
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
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
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
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.
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.
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
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.
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
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))
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
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
429	return r.Diff(nil, c, p.meta)
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,
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}
466