Skip to content

Commit

Permalink
allow terraform init to fail in additional codepaths
Browse files Browse the repository at this point in the history
allow `terraform init` to fail -- as expected -- in instances where a
non-legacy Terraform is used against a legacy TF state for the
purposes of `state replace-provider`-ing _also_ when invoking `terraform
init` from within `OverrideBackendToLocal`.

This addresses the _second_ `init` error seen in the logs:

```
2023/08/28 14:52:35 [INFO] [runner] load migration file: test.hcl
2023/08/28 14:52:35 [INFO] [migrator] start state migrator plan
2023/08/28 14:52:35 [INFO] [migrator@.] terraform version: 1.5.5
2023/08/28 14:52:35 [INFO] [migrator@.] initialize work dir
2023/08/28 14:52:35 [INFO] [migrator@.] ignoring error 'Error: Invalid legacy provider address' initilizing work dir; the error is expected when using Terraform >= 0.13 with a legacy Terraform state
2023/08/28 14:52:35 [INFO] [migrator@.] get the current remote state
2023/08/28 14:52:35 [INFO] [migrator@.] override backend to local
2023/08/28 14:52:35 [INFO] [executor@.] create an override file
2023/08/28 14:52:35 [INFO] [migrator@.] creating local workspace folder in: terraform.tfstate.d/default
2023/08/28 14:52:35 [INFO] [executor@.] switch backend to local
2023/08/28 14:52:35 [INFO] [migrator@.] compute a new state
2023/08/28 14:52:36 [INFO] [migrator@.] check diffs
2023/08/28 14:52:36 [INFO] [executor@.] remove the override file
2023/08/28 14:52:36 [INFO] [executor@.] remove the workspace state folder
2023/08/28 14:52:36 [INFO] [executor@.] switch back to remote
2023/08/28 14:52:36 [ERROR] [executor@.] failed to switch back to remote: failed to run command (exited 1): terraform init -input=false -no-color -reconfigure
stdout:

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

stderr:

Error: Invalid legacy provider address

This configuration or its associated state refers to the unqualified provider
"null".

You must complete the Terraform 0.13 upgrade process before upgrading to
later versions.

2023/08/28 14:52:36 [ERROR] [executor@.] please re-run terraform init -reconfigure
2023/08/28 14:52:36 [INFO] [migrator] state migrator plan success!
```

This seeks to address feedback from @minamijoyo:
#145 (comment)

Signed-off-by: Mike Ball <mikedball@gmail.com>
  • Loading branch information
mdb committed Sep 2, 2023
1 parent 4ecad8d commit 37e2834
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 13 deletions.
16 changes: 10 additions & 6 deletions tfexec/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ type TerraformCLI interface {
// so we need to switch the backend to local for temporary state operations.
// The filename argument must meet constraints for override file.
// (e.g.) _tfexec_override.tf
OverrideBackendToLocal(ctx context.Context, filename string, workspace string, isBackendTerraformCloud bool, backendConfig []string) (func(), error)
OverrideBackendToLocal(ctx context.Context, filename string, workspace string, isBackendTerraformCloud bool, backendConfig []string, supportsStateReplaceProvider bool) (func(), error)

// PlanHasChange is a helper method which runs plan and return true if the plan has change.
PlanHasChange(ctx context.Context, state *State, opts ...string) (bool, error)
Expand Down Expand Up @@ -213,7 +213,7 @@ func (c *terraformCLI) SetExecPath(execPath string) {
// The filename argument must meet constraints in order to override the file.
// (e.g.) _tfexec_override.tf
func (c *terraformCLI) OverrideBackendToLocal(ctx context.Context, filename string,
workspace string, isBackendTerraformCloud bool, backendConfig []string) (func(), error) {
workspace string, isBackendTerraformCloud bool, backendConfig []string, supportsStateReplaceProvider bool) (func(), error) {
// create local backend override file.
path := filepath.Join(c.Dir(), filename)
contents := `
Expand Down Expand Up @@ -277,12 +277,16 @@ terraform {
if !isBackendTerraformCloud {
args = append(args, "-reconfigure")
}
err = c.Init(ctx, args...)

err = c.Init(ctx, args...)
if err != nil {
// we cannot return error here.
log.Printf("[ERROR] [executor@%s] failed to switch back to remote: %s\n", c.Dir(), err)
log.Printf("[ERROR] [executor@%s] please re-run terraform init -reconfigure\n", c.Dir())
if supportsStateReplaceProvider && strings.Contains(err.Error(), AcceptableLegacyStateInitError) {
log.Printf("[INFO] [migrator@%s] ignoring error '%s'; the error is expected when using Terraform with a legacy Terraform state\n", c.Dir(), AcceptableLegacyStateInitError)
} else {
// we cannot return error here.
log.Printf("[ERROR] [executor@%s] failed to switch back to remote: %s\n", c.Dir(), err)
log.Printf("[ERROR] [executor@%s] please re-run terraform init -reconfigure\n", c.Dir())
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions tfexec/terraform_state_replace_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ const (
// MinimumTerraformVersionForStateReplaceProvider specifies the minimum
// supported Terraform version for StateReplaceProvider.
MinimumTerraformVersionForStateReplaceProvider = "0.13"

// AcceptableLegacyStateInitError is the error message returned by `terraform
// init` when a non-legacy Terraform CLI is used against a legacy Terraform state.
// When invoking `state replace-provider`, it's necessary to first
// invoke `terraform init`. However, when using a non-legacy Terraform CLI
// against a legacy Terraform state, this error is expected.
AcceptableLegacyStateInitError = "Error: Invalid legacy provider address"
)

// SupportsStateReplaceProvider returns true if the terraform version is greater
Expand Down
14 changes: 12 additions & 2 deletions tfexec/terraform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,12 @@ resource "null_resource" "bar" {}
t.Fatalf("an override file already exists: %s", err)
}

switchBackToRemotekFunc, err := terraformCLI.OverrideBackendToLocal(context.Background(), filename, workspace, false, nil)
supportsStateReplaceProvider, _, err := terraformCLI.SupportsStateReplaceProvider(context.Background())
if err != nil {
t.Fatalf("failed to determine if Terraform version supports state replace-provider: %s", err)
}

switchBackToRemotekFunc, err := terraformCLI.OverrideBackendToLocal(context.Background(), filename, workspace, false, nil, supportsStateReplaceProvider)
if err != nil {
t.Fatalf("failed to run OverrideBackendToLocal: %s", err)
}
Expand Down Expand Up @@ -254,7 +259,12 @@ resource "null_resource" "bar" {}
t.Fatalf("an override file already exists: %s", err)
}

switchBackToRemotekFunc, err := terraformCLI.OverrideBackendToLocal(context.Background(), filename, workspace, false, backendConfig)
supportsStateReplaceProvider, _, err := terraformCLI.SupportsStateReplaceProvider(context.Background())
if err != nil {
t.Fatalf("failed to determine if Terraform version supports state replace-provider: %s", err)
}

switchBackToRemotekFunc, err := terraformCLI.OverrideBackendToLocal(context.Background(), filename, workspace, false, backendConfig, supportsStateReplaceProvider)
if err != nil {
t.Fatalf("failed to run OverrideBackendToLocal: %s", err)
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ func SetupTestAccForStateReplaceProvider(t *testing.T, workspace string, source

err := tf.Init(ctx, "-input=false", "-no-color")

if err != nil && !strings.Contains(err.Error(), "Error: Invalid legacy provider address") {
if err != nil && !strings.Contains(err.Error(), AcceptableLegacyStateInitError) {
t.Fatalf("failed to run terraform init: %s", err)
}

Expand Down
7 changes: 3 additions & 4 deletions tfmigrate/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ func setupWorkDir(ctx context.Context, tf tfexec.TerraformCLI, workspace string,
log.Printf("[INFO] [migrator@%s] initialize work dir\n", tf.Dir())
err = tf.Init(ctx, "-input=false", "-no-color")
if err != nil {
acceptable := "Error: Invalid legacy provider address"
if supportsStateReplaceProvider && strings.Contains(err.Error(), acceptable) {
log.Printf("[INFO] [migrator@%s] ignoring error '%s' initilizing work dir; the error is expected when using Terraform %s with a legacy Terraform state\n", tf.Dir(), acceptable, constraints)
if supportsStateReplaceProvider && strings.Contains(err.Error(), tfexec.AcceptableLegacyStateInitError) {
log.Printf("[INFO] [migrator@%s] ignoring error '%s' initilizing work dir; the error is expected when using Terraform %s with a legacy Terraform state\n", tf.Dir(), tfexec.AcceptableLegacyStateInitError, constraints)
} else {
return nil, nil, err
}
Expand Down Expand Up @@ -71,7 +70,7 @@ func setupWorkDir(ctx context.Context, tf tfexec.TerraformCLI, workspace string,
}
// override backend to local
log.Printf("[INFO] [migrator@%s] override backend to local\n", tf.Dir())
switchBackToRemoteFunc, err := tf.OverrideBackendToLocal(ctx, "_tfmigrate_override.tf", workspace, isBackendTerraformCloud, backendConfig)
switchBackToRemoteFunc, err := tf.OverrideBackendToLocal(ctx, "_tfmigrate_override.tf", workspace, isBackendTerraformCloud, backendConfig, supportsStateReplaceProvider)
if err != nil {
return nil, nil, err
}
Expand Down

0 comments on commit 37e2834

Please sign in to comment.