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