opentofu/internal/configs/backend.go
Christian Mesh 0d1e6cd5f0
Handle static variable secret flag (#2045)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2024-10-03 10:46:58 -04:00

110 lines
3.6 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package configs
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
// Backend represents a "backend" block inside a "terraform" block in a module
// or file.
type Backend struct {
Type string
Config hcl.Body
Eval *StaticEvaluator
TypeRange hcl.Range
DeclRange hcl.Range
}
func decodeBackendBlock(block *hcl.Block) (*Backend, hcl.Diagnostics) {
return &Backend{
Type: block.Labels[0],
TypeRange: block.LabelRanges[0],
Config: block.Body,
DeclRange: block.DefRange,
}, nil
}
// Hash produces a hash value for the receiver that covers the type and the
// portions of the config that conform to the given schema.
//
// If the config does not conform to the schema then the result is not
// meaningful for comparison since it will be based on an incomplete result.
//
// As an exception, required attributes in the schema are treated as optional
// for the purpose of hashing, so that an incomplete configuration can still
// be hashed. Other errors, such as extraneous attributes, have no such special
// case.
func (b *Backend) Hash(schema *configschema.Block) (int, hcl.Diagnostics) {
// Don't fail if required attributes are not set. Instead, we'll just
// hash them as nulls.
schema = schema.NoneRequired()
// This is a bit of an odd workaround, but the decode below intentionally ignores
// errors. I don't want to try to change that at this point, but it may be worth doing
// at some point. For now, I'm just looking to see if there are any references that are
// not valid that the user should look at, instead of just producing an invalid backend object.
diags := b.referenceDiagnostics(schema)
val, _ := b.Decode(schema)
if val == cty.NilVal {
val = cty.UnknownVal(schema.ImpliedType())
}
if marks.Contains(val, marks.Sensitive) {
return -1, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Backend config contains sensitive values",
Detail: "The backend configuration is stored in .terraform/terraform.tfstate as well as plan files. It is recommended to instead supply sensitive credentials via backend specific environment variables",
Subject: b.DeclRange.Ptr(),
})
}
toHash := cty.TupleVal([]cty.Value{
cty.StringVal(b.Type),
val,
})
return toHash.Hash(), diags
}
func (b *Backend) Decode(schema *configschema.Block) (cty.Value, hcl.Diagnostics) {
return b.Eval.DecodeBlock(b.Config, schema.DecoderSpec(), StaticIdentifier{
Module: addrs.RootModule,
Subject: fmt.Sprintf("backend.%s", b.Type),
DeclRange: b.DeclRange,
})
}
// This is a hack that may not be needed, but preserves the idea that invalid backends will show a cryptic error about running init during plan/apply startup.
func (b *Backend) referenceDiagnostics(schema *configschema.Block) hcl.Diagnostics {
var diags hcl.Diagnostics
refs, refsDiags := lang.References(addrs.ParseRef, hcldec.Variables(b.Config, schema.DecoderSpec()))
diags = append(diags, refsDiags.ToHCL()...)
if diags.HasErrors() {
return diags
}
_, ctxDiags := b.Eval.scope(StaticIdentifier{
Module: addrs.RootModule,
Subject: fmt.Sprintf("backend.%s", b.Type),
DeclRange: b.DeclRange,
}).EvalContext(refs)
diags = append(diags, ctxDiags.ToHCL()...)
return diags
}