github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/schemas.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs"
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
    12  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    13  )
    14  
    15  // Schemas is a container for various kinds of schema that Terraform needs
    16  // during processing.
    17  type Schemas struct {
    18  	Providers    map[string]*ProviderSchema
    19  	Provisioners map[string]*configschema.Block
    20  }
    21  
    22  // ProviderSchema returns the entire ProviderSchema object that was produced
    23  // by the plugin for the given provider, or nil if no such schema is available.
    24  //
    25  // It's usually better to go use the more precise methods offered by type
    26  // Schemas to handle this detail automatically.
    27  func (ss *Schemas) ProviderSchema(typeName string) *ProviderSchema {
    28  	if ss.Providers == nil {
    29  		return nil
    30  	}
    31  	return ss.Providers[typeName]
    32  }
    33  
    34  // ProviderConfig returns the schema for the provider configuration of the
    35  // given provider type, or nil if no such schema is available.
    36  func (ss *Schemas) ProviderConfig(typeName string) *configschema.Block {
    37  	ps := ss.ProviderSchema(typeName)
    38  	if ps == nil {
    39  		return nil
    40  	}
    41  	return ps.Provider
    42  }
    43  
    44  // ResourceTypeConfig returns the schema for the configuration of a given
    45  // resource type belonging to a given provider type, or nil of no such
    46  // schema is available.
    47  //
    48  // In many cases the provider type is inferrable from the resource type name,
    49  // but this is not always true because users can override the provider for
    50  // a resource using the "provider" meta-argument. Therefore it's important to
    51  // always pass the correct provider name, even though it many cases it feels
    52  // redundant.
    53  func (ss *Schemas) ResourceTypeConfig(providerType string, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) {
    54  	ps := ss.ProviderSchema(providerType)
    55  	if ps == nil || ps.ResourceTypes == nil {
    56  		return nil, 0
    57  	}
    58  	return ps.SchemaForResourceType(resourceMode, resourceType)
    59  }
    60  
    61  // ProvisionerConfig returns the schema for the configuration of a given
    62  // provisioner, or nil of no such schema is available.
    63  func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block {
    64  	return ss.Provisioners[name]
    65  }
    66  
    67  // LoadSchemas searches the given configuration, state  and plan (any of which
    68  // may be nil) for constructs that have an associated schema, requests the
    69  // necessary schemas from the given component factory (which must _not_ be nil),
    70  // and returns a single object representing all of the necessary schemas.
    71  //
    72  // If an error is returned, it may be a wrapped tfdiags.Diagnostics describing
    73  // errors across multiple separate objects. Errors here will usually indicate
    74  // either misbehavior on the part of one of the providers or of the provider
    75  // protocol itself. When returned with errors, the returned schemas object is
    76  // still valid but may be incomplete.
    77  func LoadSchemas(config *configs.Config, state *states.State, components contextComponentFactory) (*Schemas, error) {
    78  	schemas := &Schemas{
    79  		Providers:    map[string]*ProviderSchema{},
    80  		Provisioners: map[string]*configschema.Block{},
    81  	}
    82  	var diags tfdiags.Diagnostics
    83  
    84  	newDiags := loadProviderSchemas(schemas.Providers, config, state, components)
    85  	diags = diags.Append(newDiags)
    86  	newDiags = loadProvisionerSchemas(schemas.Provisioners, config, components)
    87  	diags = diags.Append(newDiags)
    88  
    89  	return schemas, diags.Err()
    90  }
    91  
    92  func loadProviderSchemas(schemas map[string]*ProviderSchema, config *configs.Config, state *states.State, components contextComponentFactory) tfdiags.Diagnostics {
    93  	var diags tfdiags.Diagnostics
    94  
    95  	ensure := func(typeName string) {
    96  		if _, exists := schemas[typeName]; exists {
    97  			return
    98  		}
    99  
   100  		log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", typeName)
   101  		provider, err := components.ResourceProvider(typeName, "early/"+typeName)
   102  		if err != nil {
   103  			// We'll put a stub in the map so we won't re-attempt this on
   104  			// future calls.
   105  			schemas[typeName] = &ProviderSchema{}
   106  			diags = diags.Append(
   107  				fmt.Errorf("Failed to instantiate provider %q to obtain schema: %s", typeName, err),
   108  			)
   109  			return
   110  		}
   111  		defer func() {
   112  			provider.Close()
   113  		}()
   114  
   115  		resp := provider.GetSchema()
   116  		if resp.Diagnostics.HasErrors() {
   117  			// We'll put a stub in the map so we won't re-attempt this on
   118  			// future calls.
   119  			schemas[typeName] = &ProviderSchema{}
   120  			diags = diags.Append(
   121  				fmt.Errorf("Failed to retrieve schema from provider %q: %s", typeName, resp.Diagnostics.Err()),
   122  			)
   123  			return
   124  		}
   125  
   126  		s := &ProviderSchema{
   127  			Provider:      resp.Provider.Block,
   128  			ResourceTypes: make(map[string]*configschema.Block),
   129  			DataSources:   make(map[string]*configschema.Block),
   130  
   131  			ResourceTypeSchemaVersions: make(map[string]uint64),
   132  		}
   133  
   134  		if resp.Provider.Version < 0 {
   135  			// We're not using the version numbers here yet, but we'll check
   136  			// for validity anyway in case we start using them in future.
   137  			diags = diags.Append(
   138  				fmt.Errorf("invalid negative schema version provider configuration for provider %q", typeName),
   139  			)
   140  		}
   141  
   142  		for t, r := range resp.ResourceTypes {
   143  			s.ResourceTypes[t] = r.Block
   144  			s.ResourceTypeSchemaVersions[t] = uint64(r.Version)
   145  			if r.Version < 0 {
   146  				diags = diags.Append(
   147  					fmt.Errorf("invalid negative schema version for resource type %s in provider %q", t, typeName),
   148  				)
   149  			}
   150  		}
   151  
   152  		for t, d := range resp.DataSources {
   153  			s.DataSources[t] = d.Block
   154  			if d.Version < 0 {
   155  				// We're not using the version numbers here yet, but we'll check
   156  				// for validity anyway in case we start using them in future.
   157  				diags = diags.Append(
   158  					fmt.Errorf("invalid negative schema version for data source %s in provider %q", t, typeName),
   159  				)
   160  			}
   161  		}
   162  
   163  		schemas[typeName] = s
   164  	}
   165  
   166  	if config != nil {
   167  		for _, typeName := range config.ProviderTypes() {
   168  			ensure(typeName)
   169  		}
   170  	}
   171  
   172  	if state != nil {
   173  		needed := providers.AddressedTypesAbs(state.ProviderAddrs())
   174  		for _, typeName := range needed {
   175  			ensure(typeName)
   176  		}
   177  	}
   178  
   179  	return diags
   180  }
   181  
   182  func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, components contextComponentFactory) tfdiags.Diagnostics {
   183  	var diags tfdiags.Diagnostics
   184  
   185  	ensure := func(name string) {
   186  		if _, exists := schemas[name]; exists {
   187  			return
   188  		}
   189  
   190  		log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name)
   191  		provisioner, err := components.ResourceProvisioner(name, "early/"+name)
   192  		if err != nil {
   193  			// We'll put a stub in the map so we won't re-attempt this on
   194  			// future calls.
   195  			schemas[name] = &configschema.Block{}
   196  			diags = diags.Append(
   197  				fmt.Errorf("Failed to instantiate provisioner %q to obtain schema: %s", name, err),
   198  			)
   199  			return
   200  		}
   201  		defer func() {
   202  			if closer, ok := provisioner.(ResourceProvisionerCloser); ok {
   203  				closer.Close()
   204  			}
   205  		}()
   206  
   207  		resp := provisioner.GetSchema()
   208  		if resp.Diagnostics.HasErrors() {
   209  			// We'll put a stub in the map so we won't re-attempt this on
   210  			// future calls.
   211  			schemas[name] = &configschema.Block{}
   212  			diags = diags.Append(
   213  				fmt.Errorf("Failed to retrieve schema from provisioner %q: %s", name, resp.Diagnostics.Err()),
   214  			)
   215  			return
   216  		}
   217  
   218  		schemas[name] = resp.Provisioner
   219  	}
   220  
   221  	if config != nil {
   222  		for _, rc := range config.Module.ManagedResources {
   223  			for _, pc := range rc.Managed.Provisioners {
   224  				ensure(pc.Type)
   225  			}
   226  		}
   227  
   228  		// Must also visit our child modules, recursively.
   229  		for _, cc := range config.Children {
   230  			childDiags := loadProvisionerSchemas(schemas, cc, components)
   231  			diags = diags.Append(childDiags)
   232  		}
   233  	}
   234  
   235  	return diags
   236  }
   237  
   238  // ProviderSchema represents the schema for a provider's own configuration
   239  // and the configuration for some or all of its resources and data sources.
   240  //
   241  // The completeness of this structure depends on how it was constructed.
   242  // When constructed for a configuration, it will generally include only
   243  // resource types and data sources used by that configuration.
   244  type ProviderSchema struct {
   245  	Provider      *configschema.Block
   246  	ResourceTypes map[string]*configschema.Block
   247  	DataSources   map[string]*configschema.Block
   248  
   249  	ResourceTypeSchemaVersions map[string]uint64
   250  }
   251  
   252  // SchemaForResourceType attempts to find a schema for the given mode and type.
   253  // Returns nil if no such schema is available.
   254  func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) {
   255  	switch mode {
   256  	case addrs.ManagedResourceMode:
   257  		return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName]
   258  	case addrs.DataResourceMode:
   259  		// Data resources don't have schema versions right now, since state is discarded for each refresh
   260  		return ps.DataSources[typeName], 0
   261  	default:
   262  		// Shouldn't happen, because the above cases are comprehensive.
   263  		return nil, 0
   264  	}
   265  }
   266  
   267  // SchemaForResourceAddr attempts to find a schema for the mode and type from
   268  // the given resource address. Returns nil if no such schema is available.
   269  func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
   270  	return ps.SchemaForResourceType(addr.Mode, addr.Type)
   271  }
   272  
   273  // ProviderSchemaRequest is used to describe to a ResourceProvider which
   274  // aspects of schema are required, when calling the GetSchema method.
   275  type ProviderSchemaRequest struct {
   276  	ResourceTypes []string
   277  	DataSources   []string
   278  }