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"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
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
|
||||
|
||||
schema := b.ConfigSchema()
|
||||
decSpec := schema.NoneRequired().DecoderSpec()
|
||||
givenVal, diags := hcldec.Decode(c.Config, decSpec, nil)
|
||||
givenVal, diags := c.Decode(schema)
|
||||
if diags.HasErrors() {
|
||||
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
|
||||
|
@ -14,6 +14,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcltest"
|
||||
"github.com/mitchellh/cli"
|
||||
"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
|
||||
func TestMetaBackend_configuredChange(t *testing.T) {
|
||||
// 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