diff --git a/internal/configs/module_merge_body.go b/internal/configs/hcl2shim/merge_body.go similarity index 96% rename from internal/configs/module_merge_body.go rename to internal/configs/hcl2shim/merge_body.go index 4e40b66b7a..bda8f5b538 100644 --- a/internal/configs/module_merge_body.go +++ b/internal/configs/hcl2shim/merge_body.go @@ -3,7 +3,7 @@ // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configs +package hcl2shim import ( "github.com/hashicorp/hcl/v2" @@ -46,7 +46,7 @@ var _ hcl.Body = mergeBody{} func (b mergeBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { var diags hcl.Diagnostics baseSchema := schemaWithDynamic(schema) - overrideSchema := schemaWithDynamic(schemaForOverrides(schema)) + overrideSchema := schemaWithDynamic(SchemaForOverrides(schema)) baseContent, _, cDiags := b.Base.PartialContent(baseSchema) diags = append(diags, cDiags...) @@ -61,7 +61,7 @@ func (b mergeBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagno func (b mergeBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { var diags hcl.Diagnostics baseSchema := schemaWithDynamic(schema) - overrideSchema := schemaWithDynamic(schemaForOverrides(schema)) + overrideSchema := schemaWithDynamic(SchemaForOverrides(schema)) baseContent, baseRemain, cDiags := b.Base.PartialContent(baseSchema) diags = append(diags, cDiags...) diff --git a/internal/configs/synth_body.go b/internal/configs/hcl2shim/synth_body.go similarity index 99% rename from internal/configs/synth_body.go rename to internal/configs/hcl2shim/synth_body.go index 2d61501d3b..79924c9cf5 100644 --- a/internal/configs/synth_body.go +++ b/internal/configs/hcl2shim/synth_body.go @@ -3,7 +3,7 @@ // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configs +package hcl2shim import ( "fmt" diff --git a/internal/configs/synth_body_test.go b/internal/configs/hcl2shim/synth_body_test.go similarity index 98% rename from internal/configs/synth_body_test.go rename to internal/configs/hcl2shim/synth_body_test.go index c5dd1e117a..5c9892f800 100644 --- a/internal/configs/synth_body_test.go +++ b/internal/configs/hcl2shim/synth_body_test.go @@ -3,7 +3,7 @@ // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configs +package hcl2shim import ( "testing" diff --git a/internal/configs/util.go b/internal/configs/hcl2shim/util.go similarity index 94% rename from internal/configs/util.go rename to internal/configs/hcl2shim/util.go index 36fbdd9332..94e93c0d1d 100644 --- a/internal/configs/util.go +++ b/internal/configs/hcl2shim/util.go @@ -3,7 +3,7 @@ // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package configs +package hcl2shim import ( "github.com/hashicorp/hcl/v2" @@ -17,7 +17,7 @@ import ( // decoding would've expected a keyword or reference in quotes but our new // decoding expects the keyword or reference to be provided directly as // an identifier-based expression. -func exprIsNativeQuotedString(expr hcl.Expression) bool { +func ExprIsNativeQuotedString(expr hcl.Expression) bool { _, ok := expr.(*hclsyntax.TemplateExpr) return ok } @@ -35,7 +35,7 @@ func exprIsNativeQuotedString(expr hcl.Expression) bool { // Overrides are rarely used, so it's recommended to just create the override // schema on the fly only when it's needed, rather than storing it in a global // variable as we tend to do for a primary schema. -func schemaForOverrides(schema *hcl.BodySchema) *hcl.BodySchema { +func SchemaForOverrides(schema *hcl.BodySchema) *hcl.BodySchema { ret := &hcl.BodySchema{ Attributes: make([]hcl.AttributeSchema, len(schema.Attributes)), Blocks: schema.Blocks, diff --git a/internal/configs/module.go b/internal/configs/module.go index 823e51f155..a53875b40b 100644 --- a/internal/configs/module.go +++ b/internal/configs/module.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/opentofu/opentofu/internal/addrs" + "github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/experiments" tfversion "github.com/opentofu/opentofu/version" @@ -41,6 +42,7 @@ type Module struct { ProviderRequirements *RequiredProviders ProviderLocalNames map[addrs.Provider]string ProviderMetas map[addrs.Provider]*ProviderMeta + Encryption *config.EncryptionConfig Variables map[string]*Variable Locals map[string]*Local @@ -81,6 +83,7 @@ type File struct { ProviderConfigs []*Provider ProviderMetas []*ProviderMeta RequiredProviders []*RequiredProviders + Encryptions []*config.EncryptionConfig Variables []*Variable Locals []*Local @@ -280,6 +283,19 @@ func (m *Module) appendFile(file *File) hcl.Diagnostics { m.ProviderMetas[provider] = pm } + for _, e := range file.Encryptions { + if m.Encryption != nil { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Duplicate encryption configuration", + Detail: fmt.Sprintf("A module may have only one encryption configuration. Encryption was previously configured at %s.", m.Encryption.DeclRange), + Subject: &e.DeclRange, + }) + continue + } + m.Encryption = e + } + for _, v := range file.Variables { if existing, exists := m.Variables[v.Name]; exists { diags = append(diags, &hcl.Diagnostic{ @@ -552,6 +568,22 @@ func (m *Module) mergeFile(file *File) hcl.Diagnostics { } } + if len(file.Encryptions) != 0 { + switch len(file.Encryptions) { + case 1: + m.Encryption = m.Encryption.Merge(file.Encryptions[0]) + default: + // An override file with multiple encryptions is still invalid, even + // though it can override encryptions from _other_ files. + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Duplicate encryption configuration", + Detail: fmt.Sprintf("Each override file may have only one encryption configuration. Encryption was previously configured at %s.", file.Encryptions[0].DeclRange), + Subject: &file.Encryptions[1].DeclRange, + }) + } + } + for _, v := range file.Variables { existing, exists := m.Variables[v.Name] if !exists { diff --git a/internal/configs/parser_config.go b/internal/configs/parser_config.go index 72c08ebd29..9e723e6f11 100644 --- a/internal/configs/parser_config.go +++ b/internal/configs/parser_config.go @@ -7,6 +7,7 @@ package configs import ( "github.com/hashicorp/hcl/v2" + "github.com/opentofu/opentofu/internal/encryption/config" ) // LoadConfigFile reads the file at the given path and parses it as a config @@ -111,6 +112,13 @@ func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnost file.ProviderMetas = append(file.ProviderMetas, providerCfg) } + case "encryption": + encryptionCfg, cfgDiags := config.DecodeConfig(innerBlock.Body, innerBlock.DefRange) + diags = append(diags, cfgDiags...) + if encryptionCfg != nil { + file.Encryptions = append(file.Encryptions, encryptionCfg) + } + default: // Should never happen because the above cases should be exhaustive // for all block type names in our schema. diff --git a/internal/configs/shim.go b/internal/configs/shim.go new file mode 100644 index 0000000000..327d1220aa --- /dev/null +++ b/internal/configs/shim.go @@ -0,0 +1,25 @@ +package configs + +import ( + "github.com/hashicorp/hcl/v2" + "github.com/opentofu/opentofu/internal/configs/hcl2shim" + "github.com/zclconf/go-cty/cty" +) + +// These were all moved to the hcl2shim package, but still have uses referenced from this package +// TODO Call sites through opentofu to these functions should be migrated to hcl2shim eventually and this file removed +func MergeBodies(base, override hcl.Body) hcl.Body { + return hcl2shim.MergeBodies(base, override) +} + +func exprIsNativeQuotedString(expr hcl.Expression) bool { + return hcl2shim.ExprIsNativeQuotedString(expr) +} + +func schemaForOverrides(schema *hcl.BodySchema) *hcl.BodySchema { + return hcl2shim.SchemaForOverrides(schema) +} + +func SynthBody(filename string, values map[string]cty.Value) hcl.Body { + return hcl2shim.SynthBody(filename, values) +} diff --git a/internal/encryption/config/config.go b/internal/encryption/config/config.go index 54f1a405ef..ddf1f1cde5 100644 --- a/internal/encryption/config/config.go +++ b/internal/encryption/config/config.go @@ -11,9 +11,9 @@ import ( "github.com/opentofu/opentofu/internal/encryption/method" ) -// Config describes the terraform.encryption HCL block you can use to configure the state and plan encryption. +// EncryptionConfig describes the terraform.encryption HCL block you can use to configure the state and plan encryption. // The individual fields of this struct match the HCL structure directly. -type Config struct { +type EncryptionConfig struct { KeyProviderConfigs []KeyProviderConfig `hcl:"key_provider,block"` MethodConfigs []MethodConfig `hcl:"method,block"` @@ -21,11 +21,14 @@ type Config struct { StateFile *EnforcableTargetConfig `hcl:"statefile,block"` PlanFile *EnforcableTargetConfig `hcl:"planfile,block"` Remote *RemoteConfig `hcl:"remote_data_source,block"` + + // Not preserved through merge operations + DeclRange hcl.Range } // Merge returns a merged configuration with the current config and the specified override combined, the override // taking precedence. -func (c *Config) Merge(override *Config) *Config { +func (c *EncryptionConfig) Merge(override *EncryptionConfig) *EncryptionConfig { return MergeConfigs(c, override) } diff --git a/internal/encryption/config/config_load.go b/internal/encryption/config/config_load.go index 62f5712d44..d3a516bc8c 100644 --- a/internal/encryption/config/config_load.go +++ b/internal/encryption/config/config_load.go @@ -18,7 +18,7 @@ import ( // This method serves as an example for how someone using this library might want to load a configuration. // if they were not using gohcl directly. // However! Right now, this method should only be used in tests, as OpenTofu should be using gohcl to parse the configuration. -func LoadConfigFromString(sourceName string, rawInput string) (*Config, hcl.Diagnostics) { +func LoadConfigFromString(sourceName string, rawInput string) (*EncryptionConfig, hcl.Diagnostics) { var diags hcl.Diagnostics var file *hcl.File diff --git a/internal/encryption/config/config_merge.go b/internal/encryption/config/config_merge.go index 6927a17c22..ab4f2cf20a 100644 --- a/internal/encryption/config/config_merge.go +++ b/internal/encryption/config/config_merge.go @@ -7,12 +7,18 @@ package config import ( "github.com/hashicorp/hcl/v2" - "github.com/opentofu/opentofu/internal/configs" + "github.com/opentofu/opentofu/internal/configs/hcl2shim" ) // MergeConfigs merges two Configs together, with the override taking precedence. -func MergeConfigs(cfg *Config, override *Config) *Config { - return &Config{ +func MergeConfigs(cfg *EncryptionConfig, override *EncryptionConfig) *EncryptionConfig { + if cfg == nil { + return override + } + if override == nil { + return cfg + } + return &EncryptionConfig{ KeyProviderConfigs: mergeKeyProviderConfigs(cfg.KeyProviderConfigs, override.KeyProviderConfigs), MethodConfigs: mergeMethodConfigs(cfg.MethodConfigs, override.MethodConfigs), @@ -162,5 +168,5 @@ func mergeBody(base hcl.Body, override hcl.Body) hcl.Body { return base } - return configs.MergeBodies(base, override) + return hcl2shim.MergeBodies(base, override) } diff --git a/internal/encryption/config/config_merge_test.go b/internal/encryption/config/config_merge_test.go index 95e02fc37e..d07656b66b 100644 --- a/internal/encryption/config/config_merge_test.go +++ b/internal/encryption/config/config_merge_test.go @@ -12,7 +12,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hcltest" - "github.com/opentofu/opentofu/internal/configs" + "github.com/opentofu/opentofu/internal/configs/hcl2shim" "github.com/zclconf/go-cty/cty" ) @@ -21,7 +21,7 @@ func TestMergeMethodConfigs(t *testing.T) { return MethodConfig{ Type: typeName, Name: name, - Body: configs.SynthBody("method", map[string]cty.Value{ + Body: hcl2shim.SynthBody("method", map[string]cty.Value{ key: cty.StringVal(value), }), } @@ -132,7 +132,7 @@ func TestMergeKeyProviderConfigs(t *testing.T) { return KeyProviderConfig{ Type: typeName, Name: name, - Body: configs.SynthBody("key_provider", map[string]cty.Value{ + Body: hcl2shim.SynthBody("key_provider", map[string]cty.Value{ key: cty.StringVal(value), }), } diff --git a/internal/encryption/config/config_parse.go b/internal/encryption/config/config_parse.go index a5a48eed31..ab8c0cdaf2 100644 --- a/internal/encryption/config/config_parse.go +++ b/internal/encryption/config/config_parse.go @@ -16,8 +16,8 @@ import ( // This method is here as an example for how someone using this library might want to decode a configuration. // if they were not using gohcl directly. // Right now for real world use this is only intended to be used in tests, until we publish this publicly. -func DecodeConfig(body hcl.Body, rng hcl.Range) (*Config, hcl.Diagnostics) { - cfg := &Config{} +func DecodeConfig(body hcl.Body, rng hcl.Range) (*EncryptionConfig, hcl.Diagnostics) { + cfg := &EncryptionConfig{DeclRange: rng} diags := gohcl.DecodeBody(body, nil, cfg) if diags.HasErrors() { diff --git a/internal/encryption/encryption.go b/internal/encryption/encryption.go index 664a3f6cf1..cf6aa0749e 100644 --- a/internal/encryption/encryption.go +++ b/internal/encryption/encryption.go @@ -30,12 +30,12 @@ type Encryption interface { type encryption struct { // Inputs - cfg *config.Config + cfg *config.EncryptionConfig reg registry.Registry } // New creates a new Encryption provider from the given configuration and registry. -func New(reg registry.Registry, cfg *config.Config) Encryption { +func New(reg registry.Registry, cfg *config.EncryptionConfig) Encryption { return &encryption{ cfg: cfg, reg: reg, diff --git a/internal/encryption/targets.go b/internal/encryption/targets.go index b7cc4640c6..3365c91b8f 100644 --- a/internal/encryption/targets.go +++ b/internal/encryption/targets.go @@ -18,7 +18,7 @@ import ( ) type targetBuilder struct { - cfg *config.Config + cfg *config.EncryptionConfig reg registry.Registry // Used to evaluate hcl expressions