github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/legacy/helper/schema/provider.go (about)

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