mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-02 04:07:22 -06:00
2adf5801d9
When the user aborts input, it may end up as an unknown value, which needs to be converted to null for PrepareConfig. Allow PrepareConfig to accept null config values in order to fill in missing defaults.
201 lines
5.7 KiB
Go
201 lines
5.7 KiB
Go
package schema
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/config/hcl2shim"
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
ctyconvert "github.com/zclconf/go-cty/cty/convert"
|
|
)
|
|
|
|
// Backend represents a partial backend.Backend implementation and simplifies
|
|
// the creation of configuration loading and validation.
|
|
//
|
|
// Unlike other schema structs such as Provider, this struct is meant to be
|
|
// embedded within your actual implementation. It provides implementations
|
|
// only for Input and Configure and gives you a method for accessing the
|
|
// configuration in the form of a ResourceData that you're expected to call
|
|
// from the other implementation funcs.
|
|
type Backend struct {
|
|
// Schema is the schema for the configuration of this backend. If this
|
|
// Backend has no configuration this can be omitted.
|
|
Schema map[string]*Schema
|
|
|
|
// ConfigureFunc is called to configure the backend. Use the
|
|
// FromContext* methods to extract information from the context.
|
|
// This can be nil, in which case nothing will be called but the
|
|
// config will still be stored.
|
|
ConfigureFunc func(context.Context) error
|
|
|
|
config *ResourceData
|
|
}
|
|
|
|
var (
|
|
backendConfigKey = contextKey("backend config")
|
|
)
|
|
|
|
// FromContextBackendConfig extracts a ResourceData with the configuration
|
|
// from the context. This should only be called by Backend functions.
|
|
func FromContextBackendConfig(ctx context.Context) *ResourceData {
|
|
return ctx.Value(backendConfigKey).(*ResourceData)
|
|
}
|
|
|
|
func (b *Backend) ConfigSchema() *configschema.Block {
|
|
// This is an alias of CoreConfigSchema just to implement the
|
|
// backend.Backend interface.
|
|
return b.CoreConfigSchema()
|
|
}
|
|
|
|
func (b *Backend) PrepareConfig(configVal cty.Value) (cty.Value, tfdiags.Diagnostics) {
|
|
if b == nil {
|
|
return configVal, nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
var err error
|
|
|
|
// In order to use Transform below, this needs to be filled out completely
|
|
// according the schema.
|
|
configVal, err = b.CoreConfigSchema().CoerceValue(configVal)
|
|
if err != nil {
|
|
return configVal, diags.Append(err)
|
|
}
|
|
|
|
// lookup any required, top-level attributes that are Null, and see if we
|
|
// have a Default value available.
|
|
configVal, err = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) {
|
|
// we're only looking for top-level attributes
|
|
if len(path) != 1 {
|
|
return val, nil
|
|
}
|
|
|
|
// nothing to do if we already have a value
|
|
if !val.IsNull() {
|
|
return val, nil
|
|
}
|
|
|
|
// get the Schema definition for this attribute
|
|
getAttr, ok := path[0].(cty.GetAttrStep)
|
|
// these should all exist, but just ignore anything strange
|
|
if !ok {
|
|
return val, nil
|
|
}
|
|
|
|
attrSchema := b.Schema[getAttr.Name]
|
|
// continue to ignore anything that doesn't match
|
|
if attrSchema == nil {
|
|
return val, nil
|
|
}
|
|
|
|
// this is deprecated, so don't set it
|
|
if attrSchema.Deprecated != "" || attrSchema.Removed != "" {
|
|
return val, nil
|
|
}
|
|
|
|
// find a default value if it exists
|
|
def, err := attrSchema.DefaultValue()
|
|
if err != nil {
|
|
diags = diags.Append(fmt.Errorf("error getting default for %q: %s", getAttr.Name, err))
|
|
return val, err
|
|
}
|
|
|
|
// no default
|
|
if def == nil {
|
|
return val, nil
|
|
}
|
|
|
|
// create a cty.Value and make sure it's the correct type
|
|
tmpVal := hcl2shim.HCL2ValueFromConfigValue(def)
|
|
|
|
// helper/schema used to allow setting "" to a bool
|
|
if val.Type() == cty.Bool && tmpVal.RawEquals(cty.StringVal("")) {
|
|
// return a warning about the conversion
|
|
diags = diags.Append("provider set empty string as default value for bool " + getAttr.Name)
|
|
tmpVal = cty.False
|
|
}
|
|
|
|
val, err = ctyconvert.Convert(tmpVal, val.Type())
|
|
if err != nil {
|
|
diags = diags.Append(fmt.Errorf("error setting default for %q: %s", getAttr.Name, err))
|
|
}
|
|
|
|
return val, err
|
|
})
|
|
if err != nil {
|
|
// any error here was already added to the diagnostics
|
|
return configVal, diags
|
|
}
|
|
|
|
shimRC := b.shimConfig(configVal)
|
|
warns, errs := schemaMap(b.Schema).Validate(shimRC)
|
|
for _, warn := range warns {
|
|
diags = diags.Append(tfdiags.SimpleWarning(warn))
|
|
}
|
|
for _, err := range errs {
|
|
diags = diags.Append(err)
|
|
}
|
|
return configVal, diags
|
|
}
|
|
|
|
func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
|
|
var diags tfdiags.Diagnostics
|
|
sm := schemaMap(b.Schema)
|
|
shimRC := b.shimConfig(obj)
|
|
|
|
// Get a ResourceData for this configuration. To do this, we actually
|
|
// generate an intermediary "diff" although that is never exposed.
|
|
diff, err := sm.Diff(nil, shimRC, nil, nil, true)
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return diags
|
|
}
|
|
|
|
data, err := sm.Data(nil, diff)
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return diags
|
|
}
|
|
b.config = data
|
|
|
|
if b.ConfigureFunc != nil {
|
|
err = b.ConfigureFunc(context.WithValue(
|
|
context.Background(), backendConfigKey, data))
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return diags
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
// shimConfig turns a new-style cty.Value configuration (which must be of
|
|
// an object type) into a minimal old-style *terraform.ResourceConfig object
|
|
// that should be populated enough to appease the not-yet-updated functionality
|
|
// in this package. This should be removed once everything is updated.
|
|
func (b *Backend) shimConfig(obj cty.Value) *terraform.ResourceConfig {
|
|
shimMap, ok := hcl2shim.ConfigValueFromHCL2(obj).(map[string]interface{})
|
|
if !ok {
|
|
// If the configVal was nil, we still want a non-nil map here.
|
|
shimMap = map[string]interface{}{}
|
|
}
|
|
return &terraform.ResourceConfig{
|
|
Config: shimMap,
|
|
Raw: shimMap,
|
|
}
|
|
}
|
|
|
|
// Config returns the configuration. This is available after Configure is
|
|
// called.
|
|
func (b *Backend) Config() *ResourceData {
|
|
return b.config
|
|
}
|