github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/terraform/context_plugins.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sync"
     7  
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/configs/configschema"
    10  	"github.com/hashicorp/terraform/internal/providers"
    11  	"github.com/hashicorp/terraform/internal/provisioners"
    12  )
    13  
    14  // contextPlugins represents a library of available plugins (providers and
    15  // provisioners) which we assume will all be used with the same
    16  // terraform.Context, and thus it'll be safe to cache certain information
    17  // about the providers for performance reasons.
    18  type contextPlugins struct {
    19  	providerFactories    map[addrs.Provider]providers.Factory
    20  	provisionerFactories map[string]provisioners.Factory
    21  
    22  	// We memoize the schemas we've previously loaded in here, to avoid
    23  	// repeatedly paying the cost of activating the same plugins to access
    24  	// their schemas in various different spots. We use schemas for many
    25  	// purposes in Terraform, so there isn't a single choke point where
    26  	// it makes sense to preload all of them.
    27  	providerSchemas    map[addrs.Provider]*ProviderSchema
    28  	provisionerSchemas map[string]*configschema.Block
    29  	schemasLock        sync.Mutex
    30  }
    31  
    32  func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) *contextPlugins {
    33  	ret := &contextPlugins{
    34  		providerFactories:    providerFactories,
    35  		provisionerFactories: provisionerFactories,
    36  	}
    37  	ret.init()
    38  	return ret
    39  }
    40  
    41  func (cp *contextPlugins) init() {
    42  	cp.providerSchemas = make(map[addrs.Provider]*ProviderSchema, len(cp.providerFactories))
    43  	cp.provisionerSchemas = make(map[string]*configschema.Block, len(cp.provisionerFactories))
    44  }
    45  
    46  func (cp *contextPlugins) NewProviderInstance(addr addrs.Provider) (providers.Interface, error) {
    47  	f, ok := cp.providerFactories[addr]
    48  	if !ok {
    49  		return nil, fmt.Errorf("unavailable provider %q", addr.String())
    50  	}
    51  
    52  	return f()
    53  
    54  }
    55  
    56  func (cp *contextPlugins) NewProvisionerInstance(typ string) (provisioners.Interface, error) {
    57  	f, ok := cp.provisionerFactories[typ]
    58  	if !ok {
    59  		return nil, fmt.Errorf("unavailable provisioner %q", typ)
    60  	}
    61  
    62  	return f()
    63  }
    64  
    65  // ProviderSchema uses a temporary instance of the provider with the given
    66  // address to obtain the full schema for all aspects of that provider.
    67  //
    68  // ProviderSchema memoizes results by unique provider address, so it's fine
    69  // to repeatedly call this method with the same address if various different
    70  // parts of Terraform all need the same schema information.
    71  func (cp *contextPlugins) ProviderSchema(addr addrs.Provider) (*ProviderSchema, error) {
    72  	cp.schemasLock.Lock()
    73  	defer cp.schemasLock.Unlock()
    74  
    75  	if schema, ok := cp.providerSchemas[addr]; ok {
    76  		return schema, nil
    77  	}
    78  
    79  	log.Printf("[TRACE] terraform.contextPlugins: Initializing provider %q to read its schema", addr)
    80  
    81  	provider, err := cp.NewProviderInstance(addr)
    82  	if err != nil {
    83  		return nil, fmt.Errorf("failed to instantiate provider %q to obtain schema: %s", addr, err)
    84  	}
    85  	defer provider.Close()
    86  
    87  	resp := provider.GetProviderSchema()
    88  	if resp.Diagnostics.HasErrors() {
    89  		return nil, fmt.Errorf("failed to retrieve schema from provider %q: %s", addr, resp.Diagnostics.Err())
    90  	}
    91  
    92  	s := &ProviderSchema{
    93  		Provider:      resp.Provider.Block,
    94  		ResourceTypes: make(map[string]*configschema.Block),
    95  		DataSources:   make(map[string]*configschema.Block),
    96  
    97  		ResourceTypeSchemaVersions: make(map[string]uint64),
    98  	}
    99  
   100  	if resp.Provider.Version < 0 {
   101  		// We're not using the version numbers here yet, but we'll check
   102  		// for validity anyway in case we start using them in future.
   103  		return nil, fmt.Errorf("provider %s has invalid negative schema version for its configuration blocks,which is a bug in the provider ", addr)
   104  	}
   105  
   106  	for t, r := range resp.ResourceTypes {
   107  		if err := r.Block.InternalValidate(); err != nil {
   108  			return nil, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %q", addr, t, err)
   109  		}
   110  		s.ResourceTypes[t] = r.Block
   111  		s.ResourceTypeSchemaVersions[t] = uint64(r.Version)
   112  		if r.Version < 0 {
   113  			return nil, fmt.Errorf("provider %s has invalid negative schema version for managed resource type %q, which is a bug in the provider", addr, t)
   114  		}
   115  	}
   116  
   117  	for t, d := range resp.DataSources {
   118  		if err := d.Block.InternalValidate(); err != nil {
   119  			return nil, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %q", addr, t, err)
   120  		}
   121  		s.DataSources[t] = d.Block
   122  		if d.Version < 0 {
   123  			// We're not using the version numbers here yet, but we'll check
   124  			// for validity anyway in case we start using them in future.
   125  			return nil, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t)
   126  		}
   127  	}
   128  
   129  	if resp.ProviderMeta.Block != nil {
   130  		s.ProviderMeta = resp.ProviderMeta.Block
   131  	}
   132  
   133  	cp.providerSchemas[addr] = s
   134  	return s, nil
   135  }
   136  
   137  // ProviderConfigSchema is a helper wrapper around ProviderSchema which first
   138  // reads the full schema of the given provider and then extracts just the
   139  // provider's configuration schema, which defines what's expected in a
   140  // "provider" block in the configuration when configuring this provider.
   141  func (cp *contextPlugins) ProviderConfigSchema(providerAddr addrs.Provider) (*configschema.Block, error) {
   142  	providerSchema, err := cp.ProviderSchema(providerAddr)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	return providerSchema.Provider, nil
   148  }
   149  
   150  // ResourceTypeSchema is a helper wrapper around ProviderSchema which first
   151  // reads the schema of the given provider and then tries to find the schema
   152  // for the resource type of the given resource mode in that provider.
   153  //
   154  // ResourceTypeSchema will return an error if the provider schema lookup
   155  // fails, but will return nil if the provider schema lookup succeeds but then
   156  // the provider doesn't have a resource of the requested type.
   157  //
   158  // Managed resource types have versioned schemas, so the second return value
   159  // is the current schema version number for the requested resource. The version
   160  // is irrelevant for other resource modes.
   161  func (cp *contextPlugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) {
   162  	providerSchema, err := cp.ProviderSchema(providerAddr)
   163  	if err != nil {
   164  		return nil, 0, err
   165  	}
   166  
   167  	schema, version := providerSchema.SchemaForResourceType(resourceMode, resourceType)
   168  	return schema, version, nil
   169  }
   170  
   171  // ProvisionerSchema uses a temporary instance of the provisioner with the
   172  // given type name to obtain the schema for that provisioner's configuration.
   173  //
   174  // ProvisionerSchema memoizes results by provisioner type name, so it's fine
   175  // to repeatedly call this method with the same name if various different
   176  // parts of Terraform all need the same schema information.
   177  func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, error) {
   178  	cp.schemasLock.Lock()
   179  	defer cp.schemasLock.Unlock()
   180  
   181  	if schema, ok := cp.provisionerSchemas[typ]; ok {
   182  		return schema, nil
   183  	}
   184  
   185  	log.Printf("[TRACE] terraform.contextPlugins: Initializing provisioner %q to read its schema", typ)
   186  	provisioner, err := cp.NewProvisionerInstance(typ)
   187  	if err != nil {
   188  		return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %s", typ, err)
   189  	}
   190  	defer provisioner.Close()
   191  
   192  	resp := provisioner.GetSchema()
   193  	if resp.Diagnostics.HasErrors() {
   194  		return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %s", typ, resp.Diagnostics.Err())
   195  	}
   196  
   197  	cp.provisionerSchemas[typ] = resp.Provisioner
   198  	return resp.Provisioner, nil
   199  }