7 "github.com/hashicorp/terraform/tfdiags"
8 "github.com/zclconf/go-cty/cty"
10 "github.com/hashicorp/terraform/config/hcl2shim"
11 "github.com/hashicorp/terraform/configs/configschema"
12 "github.com/hashicorp/terraform/terraform"
13 ctyconvert "github.com/zclconf/go-cty/cty/convert"
16 // Backend represents a partial backend.Backend implementation and simplifies
17 // the creation of configuration loading and validation.
19 // Unlike other schema structs such as Provider, this struct is meant to be
20 // embedded within your actual implementation. It provides implementations
21 // only for Input and Configure and gives you a method for accessing the
22 // configuration in the form of a ResourceData that you're expected to call
23 // from the other implementation funcs.
25 // Schema is the schema for the configuration of this backend. If this
26 // Backend has no configuration this can be omitted.
27 Schema map[string]*Schema
29 // ConfigureFunc is called to configure the backend. Use the
30 // FromContext* methods to extract information from the context.
31 // This can be nil, in which case nothing will be called but the
32 // config will still be stored.
33 ConfigureFunc func(context.Context) error
39 backendConfigKey = contextKey("backend config")
42 // FromContextBackendConfig extracts a ResourceData with the configuration
43 // from the context. This should only be called by Backend functions.
44 func FromContextBackendConfig(ctx context.Context) *ResourceData {
45 return ctx.Value(backendConfigKey).(*ResourceData)
48 func (b *Backend) ConfigSchema() *configschema.Block {
49 // This is an alias of CoreConfigSchema just to implement the
50 // backend.Backend interface.
51 return b.CoreConfigSchema()
54 func (b *Backend) PrepareConfig(configVal cty.Value) (cty.Value, tfdiags.Diagnostics) {
58 var diags tfdiags.Diagnostics
61 // In order to use Transform below, this needs to be filled out completely
62 // according the schema.
63 configVal, err = b.CoreConfigSchema().CoerceValue(configVal)
65 return configVal, diags.Append(err)
68 // lookup any required, top-level attributes that are Null, and see if we
69 // have a Default value available.
70 configVal, err = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) {
71 // we're only looking for top-level attributes
76 // nothing to do if we already have a value
81 // get the Schema definition for this attribute
82 getAttr, ok := path[0].(cty.GetAttrStep)
83 // these should all exist, but just ignore anything strange
88 attrSchema := b.Schema[getAttr.Name]
89 // continue to ignore anything that doesn't match
90 if attrSchema == nil {
94 // this is deprecated, so don't set it
95 if attrSchema.Deprecated != "" || attrSchema.Removed != "" {
99 // find a default value if it exists
100 def, err := attrSchema.DefaultValue()
102 diags = diags.Append(fmt.Errorf("error getting default for %q: %s", getAttr.Name, err))
111 // create a cty.Value and make sure it's the correct type
112 tmpVal := hcl2shim.HCL2ValueFromConfigValue(def)
114 // helper/schema used to allow setting "" to a bool
115 if val.Type() == cty.Bool && tmpVal.RawEquals(cty.StringVal("")) {
116 // return a warning about the conversion
117 diags = diags.Append("provider set empty string as default value for bool " + getAttr.Name)
121 val, err = ctyconvert.Convert(tmpVal, val.Type())
123 diags = diags.Append(fmt.Errorf("error setting default for %q: %s", getAttr.Name, err))
129 // any error here was already added to the diagnostics
130 return configVal, diags
133 shimRC := b.shimConfig(configVal)
134 warns, errs := schemaMap(b.Schema).Validate(shimRC)
135 for _, warn := range warns {
136 diags = diags.Append(tfdiags.SimpleWarning(warn))
138 for _, err := range errs {
139 diags = diags.Append(err)
141 return configVal, diags
144 func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
149 var diags tfdiags.Diagnostics
150 sm := schemaMap(b.Schema)
151 shimRC := b.shimConfig(obj)
153 // Get a ResourceData for this configuration. To do this, we actually
154 // generate an intermediary "diff" although that is never exposed.
155 diff, err := sm.Diff(nil, shimRC, nil, nil, true)
157 diags = diags.Append(err)
161 data, err := sm.Data(nil, diff)
163 diags = diags.Append(err)
168 if b.ConfigureFunc != nil {
169 err = b.ConfigureFunc(context.WithValue(
170 context.Background(), backendConfigKey, data))
172 diags = diags.Append(err)
180 // shimConfig turns a new-style cty.Value configuration (which must be of
181 // an object type) into a minimal old-style *terraform.ResourceConfig object
182 // that should be populated enough to appease the not-yet-updated functionality
183 // in this package. This should be removed once everything is updated.
184 func (b *Backend) shimConfig(obj cty.Value) *terraform.ResourceConfig {
185 shimMap, ok := hcl2shim.ConfigValueFromHCL2(obj).(map[string]interface{})
187 // If the configVal was nil, we still want a non-nil map here.
188 shimMap = map[string]interface{}{}
190 return &terraform.ResourceConfig{
196 // Config returns the configuration. This is available after Configure is
198 func (b *Backend) Config() *ResourceData {