kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/terraform/schemas.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"kubeform.dev/terraform-backend-sdk/addrs"
     8  	"kubeform.dev/terraform-backend-sdk/configs"
     9  	"kubeform.dev/terraform-backend-sdk/configs/configschema"
    10  	"kubeform.dev/terraform-backend-sdk/providers"
    11  	"kubeform.dev/terraform-backend-sdk/states"
    12  	"kubeform.dev/terraform-backend-sdk/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[addrs.Provider]*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(provider addrs.Provider) *ProviderSchema {
    28  	if ss.Providers == nil {
    29  		return nil
    30  	}
    31  	return ss.Providers[provider]
    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(provider addrs.Provider) *configschema.Block {
    37  	ps := ss.ProviderSchema(provider)
    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(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) {
    54  	ps := ss.ProviderSchema(provider)
    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, plugins *contextPlugins) (*Schemas, error) {
    78  	schemas := &Schemas{
    79  		Providers:    map[addrs.Provider]*ProviderSchema{},
    80  		Provisioners: map[string]*configschema.Block{},
    81  	}
    82  	var diags tfdiags.Diagnostics
    83  
    84  	newDiags := loadProviderSchemas(schemas.Providers, config, state, plugins)
    85  	diags = diags.Append(newDiags)
    86  	newDiags = loadProvisionerSchemas(schemas.Provisioners, config, plugins)
    87  	diags = diags.Append(newDiags)
    88  
    89  	return schemas, diags.Err()
    90  }
    91  
    92  func loadProviderSchemas(schemas map[addrs.Provider]*ProviderSchema, config *configs.Config, state *states.State, plugins *contextPlugins) tfdiags.Diagnostics {
    93  	var diags tfdiags.Diagnostics
    94  
    95  	ensure := func(fqn addrs.Provider) {
    96  		name := fqn.String()
    97  
    98  		if _, exists := schemas[fqn]; exists {
    99  			return
   100  		}
   101  
   102  		log.Printf("[TRACE] LoadSchemas: retrieving schema for provider type %q", name)
   103  		schema, err := plugins.ProviderSchema(fqn)
   104  		if err != nil {
   105  			// We'll put a stub in the map so we won't re-attempt this on
   106  			// future calls, which would then repeat the same error message
   107  			// multiple times.
   108  			schemas[fqn] = &ProviderSchema{}
   109  			diags = diags.Append(
   110  				tfdiags.Sourceless(
   111  					tfdiags.Error,
   112  					"Failed to obtain provider schema",
   113  					fmt.Sprintf("Could not load the schema for provider %s: %s.", fqn, err),
   114  				),
   115  			)
   116  			return
   117  		}
   118  
   119  		schemas[fqn] = schema
   120  	}
   121  
   122  	if config != nil {
   123  		for _, fqn := range config.ProviderTypes() {
   124  			ensure(fqn)
   125  		}
   126  	}
   127  
   128  	if state != nil {
   129  		needed := providers.AddressedTypesAbs(state.ProviderAddrs())
   130  		for _, typeAddr := range needed {
   131  			ensure(typeAddr)
   132  		}
   133  	}
   134  
   135  	return diags
   136  }
   137  
   138  func loadProvisionerSchemas(schemas map[string]*configschema.Block, config *configs.Config, plugins *contextPlugins) tfdiags.Diagnostics {
   139  	var diags tfdiags.Diagnostics
   140  
   141  	ensure := func(name string) {
   142  		if _, exists := schemas[name]; exists {
   143  			return
   144  		}
   145  
   146  		log.Printf("[TRACE] LoadSchemas: retrieving schema for provisioner %q", name)
   147  		schema, err := plugins.ProvisionerSchema(name)
   148  		if err != nil {
   149  			// We'll put a stub in the map so we won't re-attempt this on
   150  			// future calls, which would then repeat the same error message
   151  			// multiple times.
   152  			schemas[name] = &configschema.Block{}
   153  			diags = diags.Append(
   154  				tfdiags.Sourceless(
   155  					tfdiags.Error,
   156  					"Failed to obtain provisioner schema",
   157  					fmt.Sprintf("Could not load the schema for provisioner %q: %s.", name, err),
   158  				),
   159  			)
   160  			return
   161  		}
   162  
   163  		schemas[name] = schema
   164  	}
   165  
   166  	if config != nil {
   167  		for _, rc := range config.Module.ManagedResources {
   168  			for _, pc := range rc.Managed.Provisioners {
   169  				ensure(pc.Type)
   170  			}
   171  		}
   172  
   173  		// Must also visit our child modules, recursively.
   174  		for _, cc := range config.Children {
   175  			childDiags := loadProvisionerSchemas(schemas, cc, plugins)
   176  			diags = diags.Append(childDiags)
   177  		}
   178  	}
   179  
   180  	return diags
   181  }
   182  
   183  // ProviderSchema represents the schema for a provider's own configuration
   184  // and the configuration for some or all of its resources and data sources.
   185  //
   186  // The completeness of this structure depends on how it was constructed.
   187  // When constructed for a configuration, it will generally include only
   188  // resource types and data sources used by that configuration.
   189  type ProviderSchema struct {
   190  	Provider      *configschema.Block
   191  	ProviderMeta  *configschema.Block
   192  	ResourceTypes map[string]*configschema.Block
   193  	DataSources   map[string]*configschema.Block
   194  
   195  	ResourceTypeSchemaVersions map[string]uint64
   196  }
   197  
   198  // SchemaForResourceType attempts to find a schema for the given mode and type.
   199  // Returns nil if no such schema is available.
   200  func (ps *ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) {
   201  	switch mode {
   202  	case addrs.ManagedResourceMode:
   203  		return ps.ResourceTypes[typeName], ps.ResourceTypeSchemaVersions[typeName]
   204  	case addrs.DataResourceMode:
   205  		// Data resources don't have schema versions right now, since state is discarded for each refresh
   206  		return ps.DataSources[typeName], 0
   207  	default:
   208  		// Shouldn't happen, because the above cases are comprehensive.
   209  		return nil, 0
   210  	}
   211  }
   212  
   213  // SchemaForResourceAddr attempts to find a schema for the mode and type from
   214  // the given resource address. Returns nil if no such schema is available.
   215  func (ps *ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
   216  	return ps.SchemaForResourceType(addr.Mode, addr.Type)
   217  }