From faff6b61042eb7bd1c4db10d0d0941b8597ec9d3 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sat, 12 Feb 2022 14:29:26 -0300 Subject: [PATCH 1/3] Make state commands check required version (#28025) --- internal/command/command.go | 36 +++++++ internal/command/command_test.go | 31 +++++- internal/command/state_list.go | 5 + internal/command/state_list_test.go | 36 +++++++ internal/command/state_mv.go | 5 + internal/command/state_mv_test.go | 78 +++++++++++++++ internal/command/state_pull.go | 5 + internal/command/state_pull_test.go | 32 +++++++ internal/command/state_push.go | 5 + internal/command/state_push_test.go | 33 +++++++ internal/command/state_replace_provider.go | 5 + .../command/state_replace_provider_test.go | 95 +++++++++++++++++++ internal/command/state_rm.go | 5 + internal/command/state_rm_test.go | 75 +++++++++++++++ internal/command/state_show.go | 5 + internal/command/state_show_test.go | 68 +++++++++++++ internal/command/taint.go | 27 +----- internal/command/taint_test.go | 2 +- .../main.tf | 0 19 files changed, 520 insertions(+), 28 deletions(-) rename internal/command/testdata/{taint-check-required-version => command-check-required-version}/main.tf (100%) diff --git a/internal/command/command.go b/internal/command/command.go index 41748d652f96..291d23ba1f15 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -4,6 +4,9 @@ import ( "fmt" "os" "runtime" + + "github.com/hashicorp/terraform/internal/terraform" + "github.com/hashicorp/terraform/internal/tfdiags" ) // Set to true when we're testing @@ -69,3 +72,36 @@ func ModulePath(args []string) (string, error) { return path, nil } + +// CheckRequiredVersion loads the config and check if the +// core version requirements are satisfied. +func CheckRequiredVersion(c *Meta) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + loader, err := c.initConfigLoader() + if err != nil { + diags = diags.Append(err) + return diags + } + + pwd, err := os.Getwd() + fmt.Println(pwd) + 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 +} diff --git a/internal/command/command_test.go b/internal/command/command_test.go index 4a56ecc73b23..3a0869efbd91 100644 --- a/internal/command/command_test.go +++ b/internal/command/command_test.go @@ -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" @@ -45,6 +44,7 @@ import ( "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/version" + "github.com/mitchellh/cli" "github.com/zclconf/go-cty/cty" ) @@ -1108,3 +1108,32 @@ func testView(t *testing.T) (*views.View, func(*testing.T) *terminal.TestOutput) streams, done := terminal.StreamsForTesting(t) return views.NewView(streams), done } + +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 := CheckRequiredVersion(&meta) + 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) + } +} diff --git a/internal/command/state_list.go b/internal/command/state_list.go index 54358b28d702..dda82ab74f1e 100644 --- a/internal/command/state_list.go +++ b/internal/command/state_list.go @@ -33,6 +33,11 @@ func (c *StateListCommand) Run(args []string) int { c.Meta.statePath = statePath } + if diags := CheckRequiredVersion(&c.Meta); diags != nil { + c.showDiagnostics(diags) + return 1 + } + // Load the backend b, backendDiags := c.Backend(nil) if backendDiags.HasErrors() { diff --git a/internal/command/state_list_test.go b/internal/command/state_list_test.go index e66a7968c7bc..7bc5d8766505 100644 --- a/internal/command/state_list_test.go +++ b/internal/command/state_list_test.go @@ -271,6 +271,42 @@ func TestStateList_modules(t *testing.T) { } +func TestStateList_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 := testState() + statePath := testStateFile(t, state) + + p := testProvider() + ui := cli.NewMockUi() + c := &StateListCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + } + 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) + } +} + const testStateListOutput = ` test_instance.foo ` diff --git a/internal/command/state_mv.go b/internal/command/state_mv.go index 99bc82aa53a0..10159dc02051 100644 --- a/internal/command/state_mv.go +++ b/internal/command/state_mv.go @@ -43,6 +43,11 @@ func (c *StateMvCommand) Run(args []string) int { return cli.RunResultHelp } + if diags := CheckRequiredVersion(&c.Meta); 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 diff --git a/internal/command/state_mv_test.go b/internal/command/state_mv_test.go index b183a8668779..7a5d889cf07a 100644 --- a/internal/command/state_mv_test.go +++ b/internal/command/state_mv_test.go @@ -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 diff --git a/internal/command/state_pull.go b/internal/command/state_pull.go index 8ce16ff57bc5..a291a65305c7 100644 --- a/internal/command/state_pull.go +++ b/internal/command/state_pull.go @@ -23,6 +23,11 @@ func (c *StatePullCommand) Run(args []string) int { return 1 } + if diags := CheckRequiredVersion(&c.Meta); diags != nil { + c.showDiagnostics(diags) + return 1 + } + // Load the backend b, backendDiags := c.Backend(nil) if backendDiags.HasErrors() { diff --git a/internal/command/state_pull_test.go b/internal/command/state_pull_test.go index ec0d233dda71..fb54629721af 100644 --- a/internal/command/state_pull_test.go +++ b/internal/command/state_pull_test.go @@ -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) + } +} diff --git a/internal/command/state_push.go b/internal/command/state_push.go index eb0ea1679654..5cd303c747a8 100644 --- a/internal/command/state_push.go +++ b/internal/command/state_push.go @@ -38,6 +38,11 @@ func (c *StatePushCommand) Run(args []string) int { return cli.RunResultHelp } + if diags := CheckRequiredVersion(&c.Meta); 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 diff --git a/internal/command/state_push_test.go b/internal/command/state_push_test.go index db708c8a424b..251d952d0b60 100644 --- a/internal/command/state_push_test.go +++ b/internal/command/state_push_test.go @@ -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) + } +} diff --git a/internal/command/state_replace_provider.go b/internal/command/state_replace_provider.go index c6c47e669759..bb823dcc7b16 100644 --- a/internal/command/state_replace_provider.go +++ b/internal/command/state_replace_provider.go @@ -42,6 +42,11 @@ func (c *StateReplaceProviderCommand) Run(args []string) int { return cli.RunResultHelp } + if diags := CheckRequiredVersion(&c.Meta); diags != nil { + c.showDiagnostics(diags) + return 1 + } + var diags tfdiags.Diagnostics // Parse from/to arguments into providers diff --git a/internal/command/state_replace_provider_test.go b/internal/command/state_replace_provider_test.go index 7ac5f42c40fa..dd6b0a852da2 100644 --- a/internal/command/state_replace_provider_test.go +++ b/internal/command/state_replace_provider_test.go @@ -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 diff --git a/internal/command/state_rm.go b/internal/command/state_rm.go index 03547f658f04..85fe8673f2cf 100644 --- a/internal/command/state_rm.go +++ b/internal/command/state_rm.go @@ -37,6 +37,11 @@ func (c *StateRmCommand) Run(args []string) int { return cli.RunResultHelp } + if diags := CheckRequiredVersion(&c.Meta); diags != nil { + c.showDiagnostics(diags) + return 1 + } + // Get the state stateMgr, err := c.State() if err != nil { diff --git a/internal/command/state_rm_test.go b/internal/command/state_rm_test.go index a3e954c436ca..3ec0e41cc969 100644 --- a/internal/command/state_rm_test.go +++ b/internal/command/state_rm_test.go @@ -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 diff --git a/internal/command/state_show.go b/internal/command/state_show.go index 7ee86624dfbc..bc62200daf8f 100644 --- a/internal/command/state_show.go +++ b/internal/command/state_show.go @@ -32,6 +32,11 @@ func (c *StateShowCommand) Run(args []string) int { return cli.RunResultHelp } + if diags := CheckRequiredVersion(&c.Meta); diags != nil { + c.showDiagnostics(diags) + return 1 + } + // Check for user-supplied plugin path var err error if c.pluginPath, err = c.loadPluginPath(); err != nil { diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index 32e4178b8133..9e836939e161 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -1,6 +1,7 @@ package command import ( + "os" "strings" "testing" @@ -258,6 +259,73 @@ func TestStateShow_configured_provider(t *testing.T) { } } +func TestStateShow_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, + }, + ) + }) + statePath := testStateFile(t, state) + + p := testProvider() + p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ + ResourceTypes: map[string]providers.Schema{ + "test_instance": { + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "foo": {Type: cty.String, Optional: true}, + "bar": {Type: cty.String, Optional: true}, + }, + }, + }, + }, + } + + ui := new(cli.MockUi) + c := &StateShowCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + 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()) + } + + // 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 testStateShowOutput = ` # test_instance.foo: resource "test_instance" "foo" { diff --git a/internal/command/taint.go b/internal/command/taint.go index f2fbbb1efba9..a016a818cc5a 100644 --- a/internal/command/taint.go +++ b/internal/command/taint.go @@ -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 := CheckRequiredVersion(&c.Meta); diags != nil { c.showDiagnostics(diags) return 1 } diff --git a/internal/command/taint_test.go b/internal/command/taint_test.go index 57c626e97b3e..ee41ad382a77 100644 --- a/internal/command/taint_test.go +++ b/internal/command/taint_test.go @@ -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)() diff --git a/internal/command/testdata/taint-check-required-version/main.tf b/internal/command/testdata/command-check-required-version/main.tf similarity index 100% rename from internal/command/testdata/taint-check-required-version/main.tf rename to internal/command/testdata/command-check-required-version/main.tf From 5faaf56f8593a9ea22829a71736d127751e3c8dd Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 29 Mar 2022 20:00:15 -0300 Subject: [PATCH 2/3] Remove CheckRequireVersion() from 'state list' and 'state show' --- internal/command/command.go | 36 ------------ internal/command/command_test.go | 2 +- internal/command/meta.go | 32 ++++++++++ internal/command/state_list.go | 5 -- internal/command/state_list_test.go | 36 ------------ internal/command/state_mv.go | 2 +- internal/command/state_pull.go | 2 +- internal/command/state_push.go | 2 +- internal/command/state_replace_provider.go | 2 +- internal/command/state_rm.go | 2 +- internal/command/state_show.go | 5 -- internal/command/state_show_test.go | 68 ---------------------- internal/command/taint.go | 2 +- 13 files changed, 39 insertions(+), 157 deletions(-) diff --git a/internal/command/command.go b/internal/command/command.go index 291d23ba1f15..41748d652f96 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -4,9 +4,6 @@ import ( "fmt" "os" "runtime" - - "github.com/hashicorp/terraform/internal/terraform" - "github.com/hashicorp/terraform/internal/tfdiags" ) // Set to true when we're testing @@ -72,36 +69,3 @@ func ModulePath(args []string) (string, error) { return path, nil } - -// CheckRequiredVersion loads the config and check if the -// core version requirements are satisfied. -func CheckRequiredVersion(c *Meta) tfdiags.Diagnostics { - var diags tfdiags.Diagnostics - - loader, err := c.initConfigLoader() - if err != nil { - diags = diags.Append(err) - return diags - } - - pwd, err := os.Getwd() - fmt.Println(pwd) - 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 -} diff --git a/internal/command/command_test.go b/internal/command/command_test.go index 3a0869efbd91..9aa26ef6fdee 100644 --- a/internal/command/command_test.go +++ b/internal/command/command_test.go @@ -1121,7 +1121,7 @@ func TestCommand_checkRequiredVersion(t *testing.T) { Ui: ui, } - diags := CheckRequiredVersion(&meta) + diags := meta.checkRequiredVersion() if diags == nil { t.Fatalf("diagnostics should contain unmet version constraint, but is nil") } diff --git a/internal/command/meta.go b/internal/command/meta.go index 6bfc2d8c71ec..459427a0df43 100644 --- a/internal/command/meta.go +++ b/internal/command/meta.go @@ -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 +} diff --git a/internal/command/state_list.go b/internal/command/state_list.go index dda82ab74f1e..54358b28d702 100644 --- a/internal/command/state_list.go +++ b/internal/command/state_list.go @@ -33,11 +33,6 @@ func (c *StateListCommand) Run(args []string) int { c.Meta.statePath = statePath } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { - c.showDiagnostics(diags) - return 1 - } - // Load the backend b, backendDiags := c.Backend(nil) if backendDiags.HasErrors() { diff --git a/internal/command/state_list_test.go b/internal/command/state_list_test.go index 7bc5d8766505..e66a7968c7bc 100644 --- a/internal/command/state_list_test.go +++ b/internal/command/state_list_test.go @@ -271,42 +271,6 @@ func TestStateList_modules(t *testing.T) { } -func TestStateList_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 := testState() - statePath := testStateFile(t, state) - - p := testProvider() - ui := cli.NewMockUi() - c := &StateListCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - args := []string{ - "-state", statePath, - } - 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) - } -} - const testStateListOutput = ` test_instance.foo ` diff --git a/internal/command/state_mv.go b/internal/command/state_mv.go index 10159dc02051..949f6c4b459d 100644 --- a/internal/command/state_mv.go +++ b/internal/command/state_mv.go @@ -43,7 +43,7 @@ func (c *StateMvCommand) Run(args []string) int { return cli.RunResultHelp } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { + if diags := c.Meta.checkRequiredVersion(); diags != nil { c.showDiagnostics(diags) return 1 } diff --git a/internal/command/state_pull.go b/internal/command/state_pull.go index a291a65305c7..8872cec65cfa 100644 --- a/internal/command/state_pull.go +++ b/internal/command/state_pull.go @@ -23,7 +23,7 @@ func (c *StatePullCommand) Run(args []string) int { return 1 } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { + if diags := c.Meta.checkRequiredVersion(); diags != nil { c.showDiagnostics(diags) return 1 } diff --git a/internal/command/state_push.go b/internal/command/state_push.go index 5cd303c747a8..0b863740c59f 100644 --- a/internal/command/state_push.go +++ b/internal/command/state_push.go @@ -38,7 +38,7 @@ func (c *StatePushCommand) Run(args []string) int { return cli.RunResultHelp } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { + if diags := c.Meta.checkRequiredVersion(); diags != nil { c.showDiagnostics(diags) return 1 } diff --git a/internal/command/state_replace_provider.go b/internal/command/state_replace_provider.go index bb823dcc7b16..ec5347a7697a 100644 --- a/internal/command/state_replace_provider.go +++ b/internal/command/state_replace_provider.go @@ -42,7 +42,7 @@ func (c *StateReplaceProviderCommand) Run(args []string) int { return cli.RunResultHelp } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { + if diags := c.Meta.checkRequiredVersion(); diags != nil { c.showDiagnostics(diags) return 1 } diff --git a/internal/command/state_rm.go b/internal/command/state_rm.go index 85fe8673f2cf..f126c5f5a561 100644 --- a/internal/command/state_rm.go +++ b/internal/command/state_rm.go @@ -37,7 +37,7 @@ func (c *StateRmCommand) Run(args []string) int { return cli.RunResultHelp } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { + if diags := c.Meta.checkRequiredVersion(); diags != nil { c.showDiagnostics(diags) return 1 } diff --git a/internal/command/state_show.go b/internal/command/state_show.go index bc62200daf8f..7ee86624dfbc 100644 --- a/internal/command/state_show.go +++ b/internal/command/state_show.go @@ -32,11 +32,6 @@ func (c *StateShowCommand) Run(args []string) int { return cli.RunResultHelp } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { - c.showDiagnostics(diags) - return 1 - } - // Check for user-supplied plugin path var err error if c.pluginPath, err = c.loadPluginPath(); err != nil { diff --git a/internal/command/state_show_test.go b/internal/command/state_show_test.go index 9e836939e161..32e4178b8133 100644 --- a/internal/command/state_show_test.go +++ b/internal/command/state_show_test.go @@ -1,7 +1,6 @@ package command import ( - "os" "strings" "testing" @@ -259,73 +258,6 @@ func TestStateShow_configured_provider(t *testing.T) { } } -func TestStateShow_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, - }, - ) - }) - statePath := testStateFile(t, state) - - p := testProvider() - p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ - ResourceTypes: map[string]providers.Schema{ - "test_instance": { - Block: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "id": {Type: cty.String, Optional: true, Computed: true}, - "foo": {Type: cty.String, Optional: true}, - "bar": {Type: cty.String, Optional: true}, - }, - }, - }, - }, - } - - ui := new(cli.MockUi) - c := &StateShowCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - 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()) - } - - // 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 testStateShowOutput = ` # test_instance.foo: resource "test_instance" "foo" { diff --git a/internal/command/taint.go b/internal/command/taint.go index a016a818cc5a..0c5a499f2e32 100644 --- a/internal/command/taint.go +++ b/internal/command/taint.go @@ -56,7 +56,7 @@ func (c *TaintCommand) Run(args []string) int { return 1 } - if diags := CheckRequiredVersion(&c.Meta); diags != nil { + if diags := c.Meta.checkRequiredVersion(); diags != nil { c.showDiagnostics(diags) return 1 } From 9cb958ec525b0d852c1c2058be6d8a8df1924841 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Tue, 29 Mar 2022 20:05:50 -0300 Subject: [PATCH 3/3] Move CheckRequiredVersion() test to correct source file --- internal/command/command_test.go | 30 ------------------------------ internal/command/meta_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/internal/command/command_test.go b/internal/command/command_test.go index 9aa26ef6fdee..b9c9bfc21587 100644 --- a/internal/command/command_test.go +++ b/internal/command/command_test.go @@ -44,7 +44,6 @@ import ( "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/version" - "github.com/mitchellh/cli" "github.com/zclconf/go-cty/cty" ) @@ -1108,32 +1107,3 @@ func testView(t *testing.T) (*views.View, func(*testing.T) *terminal.TestOutput) streams, done := terminal.StreamsForTesting(t) return views.NewView(streams), done } - -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) - } -} diff --git a/internal/command/meta_test.go b/internal/command/meta_test.go index e833f7d9c42f..5971840f014b 100644 --- a/internal/command/meta_test.go +++ b/internal/command/meta_test.go @@ -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) + } +}