github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/provider.go (about)

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