mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
command/init: Support static eval for backend config migration check
The "backendConfigNeedsMigration" helper evaluates the backend configuration inline to compare it with the object previously saved in the .terraform/terraform.tfstate file. However, this wasn't updated to use the new "static eval" functionality and so was treating any references to variables or function calls as invalid, causing a spurious "backend configuration changed" error when re-initializing the working directory with identical backend configuration settings. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
parent
de69070b02
commit
8b0b5b271b
@ -20,7 +20,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||||
|
|
||||||
@ -1363,8 +1362,7 @@ func (m *Meta) backendConfigNeedsMigration(c *configs.Backend, s *legacy.Backend
|
|||||||
b := f(nil) // We don't need encryption here as it's only used for config/schema
|
b := f(nil) // We don't need encryption here as it's only used for config/schema
|
||||||
|
|
||||||
schema := b.ConfigSchema()
|
schema := b.ConfigSchema()
|
||||||
decSpec := schema.NoneRequired().DecoderSpec()
|
givenVal, diags := c.Decode(schema)
|
||||||
givenVal, diags := hcldec.Decode(c.Config, decSpec, nil)
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode given config; migration codepath must handle problem: %s", diags.Error())
|
log.Printf("[TRACE] backendConfigNeedsMigration: failed to decode given config; migration codepath must handle problem: %s", diags.Error())
|
||||||
return true // let the migration codepath deal with these errors
|
return true // let the migration codepath deal with these errors
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/hcltest"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
@ -655,6 +657,104 @@ func TestMetaBackend_configuredUnchanged(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saved backend state matching config when the configuration uses static eval references
|
||||||
|
// and there's an argument overridden on the commandl ine.
|
||||||
|
func TestMetaBackend_configuredUnchangedWithStaticEvalVars(t *testing.T) {
|
||||||
|
// This test is covering the fix for the following issue:
|
||||||
|
// https://github.com/opentofu/opentofu/issues/2024
|
||||||
|
//
|
||||||
|
// To match that issue's reproduction case the following must both be true:
|
||||||
|
// - The configuration written in the fixture's .tf file must include either a
|
||||||
|
// reference to a named value or a function call. Currently we use a reference
|
||||||
|
// to a variable.
|
||||||
|
// - There must be at least one -backend-config argument on the command line,
|
||||||
|
// which causes us to go into the trickier comparison codepath that has to
|
||||||
|
// re-evaluate _just_ the configuration to distinguish from the combined
|
||||||
|
// configuration plus command-line overrides. Without this the configuration
|
||||||
|
// doesn't get re-evaluated and so the expressions used to construct it are
|
||||||
|
// irrelevant.
|
||||||
|
//
|
||||||
|
// Although not strictly required for reproduction at the time of writing this
|
||||||
|
// test, the local-state.tfstate file in the fixture also includes an output
|
||||||
|
// value to ensure that it can't be classified as an "empty state" and thus
|
||||||
|
// have migration skipped, even if the rules for activating that fast path
|
||||||
|
// change in future.
|
||||||
|
|
||||||
|
defer testChdir(t, testFixturePath("backend-unchanged-vars"))()
|
||||||
|
|
||||||
|
// Setup the meta
|
||||||
|
m := testMetaBackend(t, nil)
|
||||||
|
// testMetaBackend normally sets migrateState on, because most of the tests
|
||||||
|
// _want_ to perform migration, but for this one we're behaving as if the
|
||||||
|
// user hasn't set the -migrate-state option and thus it should be an error
|
||||||
|
// if state migration is required.
|
||||||
|
m.migrateState = false
|
||||||
|
|
||||||
|
// Get the backend
|
||||||
|
b, diags := m.Backend(
|
||||||
|
&BackendOpts{
|
||||||
|
Init: true,
|
||||||
|
|
||||||
|
// ConfigOverride is the internal representation of the -backend-config
|
||||||
|
// command line options. In the normal codepath this gets built into
|
||||||
|
// a synthetic hcl.Body so it can be merged with the real hcl.Body
|
||||||
|
// for evaluation. For testing purposes here we're constructing the
|
||||||
|
// synthetic body using the hcltest package instead, but the effect
|
||||||
|
// is the same.
|
||||||
|
ConfigOverride: hcltest.MockBody(&hcl.BodyContent{
|
||||||
|
Attributes: hcl.Attributes{
|
||||||
|
"workspace_dir": {
|
||||||
|
Name: "workspace_dir",
|
||||||
|
// We're using the "default" workspace in this test and so the workspace_dir
|
||||||
|
// isn't actually significant -- we're setting it only to enter the full-evaluation
|
||||||
|
// codepath. The only thing that matters is that the value here matches the
|
||||||
|
// argument value stored in the .terraform/terraform.tfstate file in the
|
||||||
|
// test fixture, meaning that state migration is not required because the
|
||||||
|
// configuration is unchanged.
|
||||||
|
Expr: hcltest.MockExprLiteral(cty.StringVal("doesnt-actually-matter-what-this-is")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
encryption.StateEncryptionDisabled(),
|
||||||
|
)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
// The original problem reported in https://github.com/opentofu/opentofu/issues/2024
|
||||||
|
// would return an error here: "Backend configuration has changed".
|
||||||
|
t.Fatal(diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
// The remaining checks are not directly related to the issue that this test
|
||||||
|
// is covering, but are included for completeness to check that this situation
|
||||||
|
// also follows the usual invariants for a failed backend init.
|
||||||
|
|
||||||
|
// Check the state
|
||||||
|
s, err := b.StateMgr(backend.DefaultStateName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
if err := s.RefreshState(); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
state := s.State()
|
||||||
|
if state == nil {
|
||||||
|
t.Fatal("nil state")
|
||||||
|
}
|
||||||
|
if testStateMgrCurrentLineage(s) != "configuredUnchanged" {
|
||||||
|
t.Fatalf("bad: %#v", state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the default paths don't exist
|
||||||
|
if _, err := os.Stat(DefaultStateFilename); err == nil {
|
||||||
|
t.Fatal("file should not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a backup doesn't exist
|
||||||
|
if _, err := os.Stat(DefaultStateFilename + DefaultBackupExtension); err == nil {
|
||||||
|
t.Fatal("file should not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Changing a configured backend
|
// Changing a configured backend
|
||||||
func TestMetaBackend_configuredChange(t *testing.T) {
|
func TestMetaBackend_configuredChange(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
|
23
internal/command/testdata/backend-unchanged-vars/.terraform/terraform.tfstate
vendored
Normal file
23
internal/command/testdata/backend-unchanged-vars/.terraform/terraform.tfstate
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"serial": 1,
|
||||||
|
"lineage": "666f9301-7e65-4b19-ae23-71184bb19b03",
|
||||||
|
"backend": {
|
||||||
|
"type": "local",
|
||||||
|
"config": {
|
||||||
|
"path": "local-state.tfstate",
|
||||||
|
"workspace_dir": "doesnt-actually-matter-what-this-is"
|
||||||
|
},
|
||||||
|
"hash": 4282859327
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"root"
|
||||||
|
],
|
||||||
|
"outputs": {},
|
||||||
|
"resources": {},
|
||||||
|
"depends_on": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
12
internal/command/testdata/backend-unchanged-vars/local-state.tfstate
vendored
Normal file
12
internal/command/testdata/backend-unchanged-vars/local-state.tfstate
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": 4,
|
||||||
|
"terraform_version": "0.14.0",
|
||||||
|
"serial": 7,
|
||||||
|
"lineage": "configuredUnchanged",
|
||||||
|
"outputs": {
|
||||||
|
"foo": {
|
||||||
|
"type": "string",
|
||||||
|
"value": "This is only here so that the state snapshot isn't 'empty' and so can't enter a no-migration-needed fast path."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
internal/command/testdata/backend-unchanged-vars/main.tf
vendored
Normal file
10
internal/command/testdata/backend-unchanged-vars/main.tf
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
variable "state_filename" {
|
||||||
|
type = string
|
||||||
|
default = "local-state.tfstate"
|
||||||
|
}
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
backend "local" {
|
||||||
|
path = var.state_filename
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user