github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/provider.go (about)

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