cli: Make state commands check required version (#30511)

uk1288/update-changelog-md
gabriel376 4 years ago committed by GitHub
parent 6ce6955a4f
commit f5a8608989
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,7 +19,6 @@ import (
"testing"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/internal/addrs"
backendInit "github.com/hashicorp/terraform/internal/backend/init"

@ -732,3 +732,35 @@ func (m *Meta) applyStateArguments(args *arguments.State) {
m.stateOutPath = args.StateOutPath
m.backupPath = args.BackupPath
}
// checkRequiredVersion loads the config and check if the
// core version requirements are satisfied.
func (m *Meta) checkRequiredVersion() tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
return diags
}
pwd, err := os.Getwd()
if err != nil {
diags = diags.Append(fmt.Errorf("Error getting pwd: %s", err))
return diags
}
config, configDiags := loader.LoadConfig(pwd)
if configDiags.HasErrors() {
diags = diags.Append(configDiags)
return diags
}
versionDiags := terraform.CheckCoreVersionRequirements(config)
if versionDiags.HasErrors() {
diags = diags.Append(versionDiags)
return diags
}
return nil
}

@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
@ -13,6 +14,7 @@ import (
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/backend/local"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/mitchellh/cli"
)
func TestMetaColorize(t *testing.T) {
@ -386,3 +388,32 @@ func TestMeta_process(t *testing.T) {
})
}
}
func TestCommand_checkRequiredVersion(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("command-check-required-version"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
ui := cli.NewMockUi()
meta := Meta{
Ui: ui,
}
diags := meta.checkRequiredVersion()
if diags == nil {
t.Fatalf("diagnostics should contain unmet version constraint, but is nil")
}
meta.showDiagnostics(diags)
// Required version diags are correct
errStr := ui.ErrorWriter.String()
if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
}
if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
}
}

@ -43,6 +43,11 @@ func (c *StateMvCommand) Run(args []string) int {
return cli.RunResultHelp
}
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
return 1
}
// If backup or backup-out options are set
// and the state option is not set, make sure
// the backend is local

@ -1713,6 +1713,84 @@ func TestStateMvInvalidSourceAddress(t *testing.T) {
}
}
func TestStateMv_checkRequiredVersion(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("command-check-required-version"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "baz",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.foo")},
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
})
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{
StateMeta{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
View: view,
},
},
}
args := []string{
"-state", statePath,
"test_instance.foo",
"test_instance.bar",
}
if code := c.Run(args); code != 1 {
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
}
// State is unchanged
testStateOutput(t, statePath, testStateMvOutputOriginal)
// Required version diags are correct
errStr := ui.ErrorWriter.String()
if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
}
if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
}
}
const testStateMvOutputOriginal = `
test_instance.baz:
ID = foo

@ -23,6 +23,11 @@ func (c *StatePullCommand) Run(args []string) int {
return 1
}
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
return 1
}
// Load the backend
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {

@ -4,6 +4,7 @@ import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/mitchellh/cli"
@ -64,3 +65,34 @@ func TestStatePull_noState(t *testing.T) {
t.Fatalf("bad: %s", actual)
}
}
func TestStatePull_checkRequiredVersion(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("command-check-required-version"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
p := testProvider()
ui := cli.NewMockUi()
c := &StatePullCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{}
if code := c.Run(args); code != 1 {
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
}
// Required version diags are correct
errStr := ui.ErrorWriter.String()
if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
}
if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
}
}

@ -38,6 +38,11 @@ func (c *StatePushCommand) Run(args []string) int {
return cli.RunResultHelp
}
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
return 1
}
// Determine our reader for the input state. This is the filepath
// or stdin if "-" is given.
var r io.Reader = os.Stdin

@ -291,3 +291,36 @@ func TestStatePush_forceRemoteState(t *testing.T) {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}
func TestStatePush_checkRequiredVersion(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("command-check-required-version"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
p := testProvider()
ui := cli.NewMockUi()
view, _ := testView(t)
c := &StatePushCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
View: view,
},
}
args := []string{"replace.tfstate"}
if code := c.Run(args); code != 1 {
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
}
// Required version diags are correct
errStr := ui.ErrorWriter.String()
if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
}
if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
}
}

@ -42,6 +42,11 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
return cli.RunResultHelp
}
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
return 1
}
var diags tfdiags.Diagnostics
// Parse from/to arguments into providers

@ -2,6 +2,7 @@ package command
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
@ -294,6 +295,100 @@ func TestStateReplaceProvider_docs(t *testing.T) {
}
}
func TestStateReplaceProvider_checkRequiredVersion(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("command-check-required-version"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "alpha",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"alpha","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("aws"),
Module: addrs.RootModule,
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "beta",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"beta","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("aws"),
Module: addrs.RootModule,
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "azurerm_virtual_machine",
Name: "gamma",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"gamma","baz":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewLegacyProvider("azurerm"),
Module: addrs.RootModule,
},
)
})
statePath := testStateFile(t, state)
ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{
StateMeta{
Meta: Meta{
Ui: ui,
View: view,
},
},
}
inputBuf := &bytes.Buffer{}
ui.InputReader = inputBuf
inputBuf.WriteString("yes\n")
args := []string{
"-state", statePath,
"hashicorp/aws",
"acmecorp/aws",
}
if code := c.Run(args); code != 1 {
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
}
// State is unchanged
testStateOutput(t, statePath, testStateReplaceProviderOutputOriginal)
// Required version diags are correct
errStr := ui.ErrorWriter.String()
if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
}
if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
}
}
const testStateReplaceProviderOutputOriginal = `
aws_instance.alpha:
ID = alpha

@ -37,6 +37,11 @@ func (c *StateRmCommand) Run(args []string) int {
return cli.RunResultHelp
}
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
return 1
}
// Get the state
stateMgr, err := c.State()
if err != nil {

@ -486,6 +486,81 @@ func TestStateRm_backendState(t *testing.T) {
testStateOutput(t, backupPath, testStateRmOutputOriginal)
}
func TestStateRm_checkRequiredVersion(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("command-check-required-version"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
state := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
})
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{
StateMeta{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
View: view,
},
},
}
args := []string{
"-state", statePath,
"test_instance.foo",
}
if code := c.Run(args); code != 1 {
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
}
// State is unchanged
testStateOutput(t, statePath, testStateRmOutputOriginal)
// Required version diags are correct
errStr := ui.ErrorWriter.String()
if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
}
if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
}
}
const testStateRmOutputOriginal = `
test_instance.bar:
ID = foo

@ -2,7 +2,6 @@ package command
import (
"fmt"
"os"
"strings"
"github.com/hashicorp/terraform/internal/addrs"
@ -10,7 +9,6 @@ import (
"github.com/hashicorp/terraform/internal/command/clistate"
"github.com/hashicorp/terraform/internal/command/views"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -58,30 +56,7 @@ func (c *TaintCommand) Run(args []string) int {
return 1
}
// Load the config and check the core version requirements are satisfied
loader, err := c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
c.showDiagnostics(diags)
return 1
}
pwd, err := os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
return 1
}
config, configDiags := loader.LoadConfig(pwd)
diags = diags.Append(configDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
versionDiags := terraform.CheckCoreVersionRequirements(config)
diags = diags.Append(versionDiags)
if diags.HasErrors() {
if diags := c.Meta.checkRequiredVersion(); diags != nil {
c.showDiagnostics(diags)
return 1
}

@ -494,7 +494,7 @@ func TestTaint_module(t *testing.T) {
func TestTaint_checkRequiredVersion(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
testCopyDir(t, testFixturePath("taint-check-required-version"), td)
testCopyDir(t, testFixturePath("command-check-required-version"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()

Loading…
Cancel
Save