Found another path to backend init error
There are actually a few different ways to get to this message. 1. Blank state — no previous terraform applied. Start with a cloud block. 1. Implicit local — start with no backend specified. This actually goes through the same code execution path as the first scenario. 1. Explicit local — start with a backend local block that has been applied, then change from the local backend to a cloud block. This will recognize the state, and is a different path through the code in the meta backend. This commit handles the last case. The messaging has also been tweaked. End to end test included as well.
This commit is contained in:
parent
d29532cfeb
commit
ab304d831f
|
@ -0,0 +1,141 @@
|
|||
//go:build e2e
|
||||
// +build e2e
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
expect "github.com/Netflix/go-expect"
|
||||
"github.com/hashicorp/terraform/internal/e2e"
|
||||
)
|
||||
|
||||
func Test_backend_apply_before_init(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
operations []operationSets
|
||||
}{
|
||||
"terraform apply with cloud block - blank state": {
|
||||
operations: []operationSets{
|
||||
{
|
||||
prep: func(t *testing.T, orgName, dir string) {
|
||||
wsName := "new-workspace"
|
||||
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||
writeMainTF(t, tfBlock, dir)
|
||||
},
|
||||
commands: []tfCommand{
|
||||
{
|
||||
command: []string{"apply"},
|
||||
expectedCmdOutput: `Terraform Cloud has been configured but needs to be initialized`,
|
||||
expectError: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"terraform apply with cloud block - local state": {
|
||||
operations: []operationSets{
|
||||
{
|
||||
prep: func(t *testing.T, orgName, dir string) {
|
||||
tfBlock := terraformConfigLocalBackend()
|
||||
writeMainTF(t, tfBlock, dir)
|
||||
},
|
||||
commands: []tfCommand{
|
||||
{
|
||||
command: []string{"init"},
|
||||
expectedCmdOutput: `Successfully configured the backend "local"!`,
|
||||
},
|
||||
{
|
||||
command: []string{"apply"},
|
||||
expectedCmdOutput: `Do you want to perform these actions?`,
|
||||
userInput: []string{"yes"},
|
||||
postInputOutput: []string{`Apply complete!`},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
prep: func(t *testing.T, orgName, dir string) {
|
||||
wsName := "new-workspace"
|
||||
tfBlock := terraformConfigCloudBackendName(orgName, wsName)
|
||||
writeMainTF(t, tfBlock, dir)
|
||||
},
|
||||
commands: []tfCommand{
|
||||
{
|
||||
command: []string{"apply"},
|
||||
expectedCmdOutput: `Terraform Cloud has been configured but needs to be initialized`,
|
||||
expectError: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
fmt.Println("Test: ", name)
|
||||
organization, cleanup := createOrganization(t)
|
||||
defer cleanup()
|
||||
exp, err := expect.NewConsole(expect.WithStdout(os.Stdout), expect.WithDefaultTimeout(expectConsoleTimeout))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer exp.Close()
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "terraform-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
tf := e2e.NewBinary(terraformBin, tmpDir)
|
||||
tf.AddEnv("TF_LOG=info")
|
||||
tf.AddEnv(cliConfigFileEnv)
|
||||
defer tf.Close()
|
||||
|
||||
for _, op := range tc.operations {
|
||||
op.prep(t, organization.Name, tf.WorkDir())
|
||||
for _, tfCmd := range op.commands {
|
||||
cmd := tf.Cmd(tfCmd.command...)
|
||||
cmd.Stdin = exp.Tty()
|
||||
cmd.Stdout = exp.Tty()
|
||||
cmd.Stderr = exp.Tty()
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if tfCmd.expectedCmdOutput != "" {
|
||||
_, err := exp.ExpectString(tfCmd.expectedCmdOutput)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
lenInput := len(tfCmd.userInput)
|
||||
lenInputOutput := len(tfCmd.postInputOutput)
|
||||
if lenInput > 0 {
|
||||
for i := 0; i < lenInput; i++ {
|
||||
input := tfCmd.userInput[i]
|
||||
exp.SendLine(input)
|
||||
// use the index to find the corresponding
|
||||
// output that matches the input.
|
||||
if lenInputOutput-1 >= i {
|
||||
output := tfCmd.postInputOutput[i]
|
||||
_, err := exp.ExpectString(output)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil && !tfCmd.expectError {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -593,13 +593,14 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
|||
return m.backend_c_r_S(c, cHash, sMgr, true)
|
||||
|
||||
// Configuring Terraform Cloud for the first time.
|
||||
// NOTE: There may be an implicit local backend with state that is not visible to this block.
|
||||
case c != nil && c.Type == "cloud" && s.Backend.Empty():
|
||||
log.Printf("[TRACE] Meta.Backend: moving from default local state only to Terraform Cloud")
|
||||
if !opts.Init {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Terraform Cloud has been configured but needs to be initialized.",
|
||||
"Run \"terraform init\" to initialize Terraform Cloud.",
|
||||
strings.TrimSpace(errBackendInitCloud),
|
||||
))
|
||||
return nil, diags
|
||||
}
|
||||
|
@ -640,17 +641,30 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di
|
|||
}
|
||||
log.Printf("[TRACE] Meta.Backend: backend configuration has changed (from type %q to type %q)", s.Backend.Type, c.Type)
|
||||
|
||||
initReason := fmt.Sprintf("Backend configuration changed for %q", c.Type)
|
||||
if s.Backend.Type != c.Type {
|
||||
initReason := ""
|
||||
switch {
|
||||
case c.Type == "cloud":
|
||||
initReason = fmt.Sprintf("Backend configuration changed from %q to Terraform Cloud", s.Backend.Type)
|
||||
case s.Backend.Type != c.Type:
|
||||
initReason = fmt.Sprintf("Backend configuration changed from %q to %q", s.Backend.Type, c.Type)
|
||||
default:
|
||||
initReason = fmt.Sprintf("Backend configuration changed for %q", c.Type)
|
||||
}
|
||||
|
||||
if !opts.Init {
|
||||
if c.Type == "cloud" {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Terraform Cloud has been configured but needs to be initialized.",
|
||||
strings.TrimSpace(errBackendInitCloud),
|
||||
))
|
||||
} else {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Backend initialization required, please run \"terraform init\"",
|
||||
fmt.Sprintf(strings.TrimSpace(errBackendInit), initReason),
|
||||
))
|
||||
}
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
|
@ -1315,6 +1329,24 @@ hasn't changed and try again. At this point, no changes to your existing
|
|||
configuration or state have been made.
|
||||
`
|
||||
|
||||
const errBackendInitCloud = `
|
||||
Changes to the Terraform Cloud configuration block require reinitialization.
|
||||
This allows Terraform to set up the new configuration, copy existing state,
|
||||
etc. Learn more about Terraform Settings:
|
||||
https://www.terraform.io/docs/language/settings/index.html
|
||||
|
||||
Please run "terraform init" with either the "-reconfigure" or "-migrate-state"
|
||||
flags. The "-reconfigure" option disregards any existing configuration,
|
||||
preventing migration of any existing state. The "-migrate-state" option
|
||||
will attempt to copy existing state to Terraform Cloud. Learn more about
|
||||
using "terraform init":
|
||||
https://www.terraform.io/docs/cli/commands/init.html#backend-initialization
|
||||
|
||||
If the change reason above is incorrect, please verify your configuration
|
||||
hasn't changed and try again. At this point, no changes to your existing
|
||||
configuration or state have been made.
|
||||
`
|
||||
|
||||
const errBackendWriteSaved = `
|
||||
Error saving the backend configuration: %s
|
||||
|
||||
|
|
Loading…
Reference in New Issue