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