github.com/kevinklinger/open_terraform@v1.3.6/noninternal/terraform/schemas.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/kevinklinger/open_terraform/noninternal/addrs"
     8  	"github.com/kevinklinger/open_terraform/noninternal/configs"
     9  	"github.com/kevinklinger/open_terraform/noninternal/configs/configschema"
    10  	"github.com/kevinklinger/open_terraform/noninternal/providers"
    11  	"github.com/kevinklinger/open_terraform/noninternal/states"
    12  	"github.com/kevinklinger/open_terraform/noninternal/tfdiags"
    13  )
    14  
    15  // ProviderSchema is an alias for providers.Schemas, which is the new location
    16  // for what we originally called terraform.ProviderSchema but which has
    17  // moved out as part of ongoing refactoring to shrink down the main "terraform"
    18  // package.
    19  type ProviderSchema = providers.Schemas
    20  
    21  // Schemas is a container for various kinds of schema that Terraform needs
    22  // during processing.
    23  type Schemas struct {
    24  	Providers    map[addrs.Provider]*providers.Schemas
    25  	Provisioners map[string]*configschema.Block
    26  }
    27  
    28  // ProviderSchema returns the entire ProviderSchema object that was produced
    29  // by the plugin for the given provider, or nil if no such schema is available.
    30  //
    31  // It's usually better to go use the more precise methods offered by type
    32  // Schemas to handle this detail automatically.
    33  func (ss *Schemas) ProviderSchema(provider addrs.Provider) *providers.Schemas {
    34  	if ss.Providers == nil {
    35  		return nil
    36  	}
    37  	return ss.Providers[provider]
    38  }
    39  
    40  // ProviderConfig returns the schema for the provider configuration of the
    41  // given provider type, or nil if no such schema is available.
    42  func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block {
    43  	ps := ss.ProviderSchema(provider)
    44  	if ps == nil {
    45  		return nil
    46  	}
    47  	return ps.Provider
    48  }
    49  
    50  // ResourceTypeConfig returns the schema for the configuration of a given
    51  // resource type belonging to a given provider type, or nil of no such
    52  // schema is available.
    53  //
    54  // In many cases the provider type is inferrable from the resource type name,
    55  // but this is not always true because users can override the provider for
    56  // a resource using the "provider" meta-argument. Therefore it's important to
    57  // always pass the correct provider name, even though it many cases it feels
    58  // redundant.
    59  func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) {
    60  	ps := ss.ProviderSchema(provider)
    61  	if ps == nil || ps.ResourceTypes == nil {
    62  		return nil, 0
    63  	}
    64  	return ps.SchemaForResourceType(resourceMode, resourceType)
    65  }
    66  
    67  // ProvisionerConfig returns the schema for the configuration of a given
    68  // provisioner, or nil of no such schema is available.
    69  func (ss *Schemas) ProvisionerConfig(name string) *configschema.Block {
    70  	return ss.Provisioners[name]
    71  }
    72  
    73  // loadSchemas searches the given configuration, state  and plan (any of which
    74  // may be nil) for constructs that have an associated schema, requests the
    75  // necessary schemas from the given component factory (which must _not_ be nil),
    76  // and returns a single object representing all of the necessary schemas.
    77  //
    78  // If an error is returned, it may be a wrapped tfdiags.Diagnostics describing
    79  // errors across multiple separate objects. Errors here will usually indicate
    80  // either misbehavior on the part of one of the providers or of the provider
    81  // protocol itself. When returned with errors, the returned schemas object is
    82  // still valid but may be incomplete.
    83  func loadSchemas(config *configs.Config, state *states.State, plugins *contextPlugins) (*Schemas, error) {
    84  	schemas := &Schemas{
    85  		Providers:    map[addrs.Provider]*providers.Schemas{},
    86  		Provisioners: map[string]*configschema.Block{},
    87  	}
    88  	var diags tfdiags.Diagnostics
    89  
    90  	newDiags := loadProviderSchemas(schemas.Providers, config, state, plugins)
    91  	diags = diags.Append(newDiags)
    92  	newDiags = loadProvisionerSchemas(schemas.Provisioners, config, plugins)
    93  	diags = diags.Append(newDiags)
    94  
    95  	return schemas, diags.Err()
    96  }
    97  
    98  func loadProviderSchemas(schemas map[addrs.Provider]*providers.Schemas, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics {
    99  	var diags tfdiags.Diagnostics
   100  
   101  	ensure := func(fqn addrs.Provider) {
   102  		name := fqn.String()
   103  
   104  		if _, exists := schemas[fqn]; exists {
   105  			return
   106  		}
   107  
   108  		log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", name)
   109  		schema, err := plugins.ProviderSchema(fqn)
   110  		if err != nil {
   111  			// We'll put a stub in the map so we won't re-attempt this on
   112  			// future calls, which would then repeat the same error message
   113  			// multiple times.
   114  			schemas[fqn] = &providers.Schemas{}
   115  			diags = diags.Append(
   116  				tfdiags.Sourceless(
   117  					tfdiags.Error,
   118  					"Failed to obtain provider schema",
   119  					fmt.Sprintf("Could not load the schema for provider %s: %s.", fqn, err),
   120  				),
   121  			)
   122  			return
   123  		}
   124  
   125  		schemas[fqn] = schema
   126  	}
   127  
   128  	if config != nil {
   129  		for _, fqn := range config.ProviderTypes() {
   130  			ensure(fqn)
   131  		}
   132  	}
   133  
   134  	if state != nil {
   135  		needed := providers.AddressedTypesAbs(state.ProviderAddrs())
   136  		for _, typeAddr := range needed {
   137  			ensure(typeAddr)
   138  		}
   139  	}
   140  
   141  	return diags
   142  }
   143  
   144  func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, plugins *contextPlugins) tfdiags.Diagnostics {
   145  	var diags tfdiags.Diagnostics
   146  
   147  	ensure := func(name string) {
   148  		if _, exists := schemas[name]; exists {
   149  			return
   150  		}
   151  
   152  		log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name)
   153  		schema, err := plugins.ProvisionerSchema(name)
   154  		if err != nil {
   155  			// We'll put a stub in the map so we won't re-attempt this on
   156  			// future calls, which would then repeat the same error message
   157  			// multiple times.
   158  			schemas[name] = &configschema.Block{}
   159  			diags = diags.Append(
   160  				tfdiags.Sourceless(
   161  					tfdiags.Error,
   162  					"Failed to obtain provisioner schema",
   163  					fmt.Sprintf("Could not load the schema for provisioner %q: %s.", name, err),
   164  				),
   165  			)
   166  			return
   167  		}
   168  
   169  		schemas[name] = schema
   170  	}
   171  
   172  	if config != nil {
   173  		for _, rc := range config.Module.ManagedResources {
   174  			for _, pc := range rc.Managed.Provisioners {
   175  				ensure(pc.Type)
   176  			}
   177  		}
   178  
   179  		// Must also visit our child modules, recursively.
   180  		for _, cc := range config.Children {
   181  			childDiags := loadProvisionerSchemas(schemas, cc, plugins)
   182  			diags = diags.Append(childDiags)
   183  		}
   184  	}
   185  
   186  	return diags
   187  }