diff --git a/internal/command/apply.go b/internal/command/apply.go index cd0b1e958ad4..9148f04dc4f5 100644 --- a/internal/command/apply.go +++ b/internal/command/apply.go @@ -219,19 +219,15 @@ func (c *ApplyCommand) PrepareBackend(planFile *planfile.WrappedPlanFile, args * )) return nil, diags } + // TODO: Update BackendForLocalPlan to use state storage, and plan to be able to contain State Store config details be, beDiags = c.BackendForLocalPlan(plan.Backend) } else { - // Both new plans and saved cloud plans load their backend from config. - backendConfig, configDiags := c.loadBackendConfig(".") - diags = diags.Append(configDiags) - if configDiags.HasErrors() { - return nil, diags - } - be, beDiags = c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - ViewType: viewType, - }) + // Load the backend + // + // Note: Both new plans and saved cloud plans load their backend from config, + // hence the config parsing in the method below. + be, beDiags = c.backend(".", viewType) } diags = diags.Append(beDiags) diff --git a/internal/command/autocomplete.go b/internal/command/autocomplete.go index 1a5cd047f45b..9ef76e3f7f2d 100644 --- a/internal/command/autocomplete.go +++ b/internal/command/autocomplete.go @@ -4,6 +4,7 @@ package command import ( + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/posener/complete" ) @@ -48,19 +49,18 @@ func (m *Meta) completePredictWorkspaceName() complete.Predictor { return nil } - backendConfig, diags := m.loadBackendConfig(configPath) + b, diags := m.backend(configPath, arguments.ViewHuman) if diags.HasErrors() { return nil } - b, diags := m.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) - if diags.HasErrors() { + names, _ := b.Workspaces() + if len(names) == 0 { + // Presence of the "default" isn't always guaranteed + // Backends will report it as always existing, pluggable + // state stores will only do so if it _actually_ exists. return nil } - - names, _ := b.Workspaces() return names }) } diff --git a/internal/command/autocomplete_test.go b/internal/command/autocomplete_test.go index dfda0d339cf9..c13be9fbb06d 100644 --- a/internal/command/autocomplete_test.go +++ b/internal/command/autocomplete_test.go @@ -4,37 +4,124 @@ package command import ( - "io/ioutil" - "os" "reflect" "testing" "github.com/hashicorp/cli" + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/command/workdir" + "github.com/hashicorp/terraform/internal/providers" "github.com/posener/complete" ) func TestMetaCompletePredictWorkspaceName(t *testing.T) { - // Create a temporary working directory that is empty - td := t.TempDir() - os.MkdirAll(td, 0755) - t.Chdir(td) - // make sure a vars file doesn't interfere - err := ioutil.WriteFile(DefaultVarsFilename, nil, 0644) - if err != nil { - t.Fatal(err) - } + t.Run("test autocompletion using the local backend", func(t *testing.T) { + // Create a temporary working directory that is empty + td := t.TempDir() + t.Chdir(td) - ui := new(cli.MockUi) - meta := &Meta{Ui: ui} + ui := new(cli.MockUi) + meta := &Meta{Ui: ui} - predictor := meta.completePredictWorkspaceName() + predictor := meta.completePredictWorkspaceName() - got := predictor.Predict(complete.Args{ - Last: "", + got := predictor.Predict(complete.Args{ + Last: "", + }) + want := []string{"default"} + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) + } + }) + + t.Run("test autocompletion using a state store", func(t *testing.T) { + // Create a temporary working directory with state_store config + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-unchanged"), td) + t.Chdir(td) + + // Set up pluggable state store provider mock + mockProvider := mockPluggableStateStorageProvider() + // Mock the existence of workspaces + mockProvider.MockStates = map[string]interface{}{ + "default": true, + "foobar": true, + } + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := new(cli.MockUi) + view, _ := testView(t) + wd := workdir.NewDir(".") + wd.OverrideOriginalWorkingDir(td) + meta := Meta{ + WorkingDir: wd, // Use the test's temp dir + Ui: ui, + View: view, + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + } + + predictor := meta.completePredictWorkspaceName() + + got := predictor.Predict(complete.Args{ + Last: "", + }) + want := []string{"default", "foobar"} + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) + } + }) + + t.Run("test autocompletion using a state store containing no workspaces", func(t *testing.T) { + // Create a temporary working directory with state_store config + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-unchanged"), td) + t.Chdir(td) + + // Set up pluggable state store provider mock + mockProvider := mockPluggableStateStorageProvider() + // No workspaces exist in the mock + mockProvider.MockStates = map[string]interface{}{} + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := new(cli.MockUi) + view, _ := testView(t) + wd := workdir.NewDir(".") + wd.OverrideOriginalWorkingDir(td) + meta := Meta{ + WorkingDir: wd, // Use the test's temp dir + Ui: ui, + View: view, + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + } + + predictor := meta.completePredictWorkspaceName() + + got := predictor.Predict(complete.Args{ + Last: "", + }) + if got != nil { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, nil) + } }) - want := []string{"default"} - if !reflect.DeepEqual(got, want) { - t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) - } } diff --git a/internal/command/console.go b/internal/command/console.go index a66b959d89a9..553d7a9c42ce 100644 --- a/internal/command/console.go +++ b/internal/command/console.go @@ -53,17 +53,8 @@ func (c *ConsoleCommand) Run(args []string) int { var diags tfdiags.Diagnostics - backendConfig, backendDiags := c.loadBackendConfig(configPath) - diags = diags.Append(backendDiags) - if diags.HasErrors() { - c.showDiagnostics(diags) - return 1 - } - // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) + b, backendDiags := c.backend(configPath, arguments.ViewHuman) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/graph.go b/internal/command/graph.go index 538438d772b8..ff243945928e 100644 --- a/internal/command/graph.go +++ b/internal/command/graph.go @@ -68,17 +68,8 @@ func (c *GraphCommand) Run(args []string) int { var diags tfdiags.Diagnostics - backendConfig, backendDiags := c.loadBackendConfig(configPath) - diags = diags.Append(backendDiags) - if diags.HasErrors() { - c.showDiagnostics(diags) - return 1 - } - // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) + b, backendDiags := c.backend(".", arguments.ViewHuman) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/import.go b/internal/command/import.go index b5b06c733487..2824397cbd1f 100644 --- a/internal/command/import.go +++ b/internal/command/import.go @@ -164,9 +164,7 @@ func (c *ImportCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: config.Module.Backend, - }) + b, backendDiags := c.backend(".", arguments.ViewHuman) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 46241932b09f..e0800b454608 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1580,6 +1580,82 @@ func (m *Meta) updateSavedBackendHash(cHash int, sMgr *clistate.LocalState) tfdi return diags } +// backend returns an operations backend that may use a backend, cloud, or state_store block for state storage. +// Based on the supplied config, it prepares arguments to pass into (Meta).Backend, which returns the operations backend. +// +// This method should be used in NON-init operations only; it's incapable of processing new init command CLI flags used +// for partial configuration, however it will use the backend state file to use partial configuration from a previous +// init command. +func (m *Meta) backend(configPath string, viewType arguments.ViewType) (backendrun.OperationsBackend, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + if configPath == "" { + configPath = "." + } + + // Only return error diagnostics at this point. Any warnings will be caught + // again later and duplicated in the output. + root, mDiags := m.loadSingleModule(configPath) + if mDiags.HasErrors() { + diags = diags.Append(mDiags) + return nil, diags + } + + var opts *BackendOpts + switch { + case root.Backend != nil: + opts = &BackendOpts{ + BackendConfig: root.Backend, + ViewType: viewType, + } + case root.CloudConfig != nil: + backendConfig := root.CloudConfig.ToBackendConfig() + opts = &BackendOpts{ + BackendConfig: &backendConfig, + ViewType: viewType, + } + case root.StateStore != nil: + // In addition to config, use of a state_store requires + // provider factory and provider locks data + locks, lDiags := m.lockedDependencies() + diags = diags.Append(lDiags) + if lDiags.HasErrors() { + return nil, diags + } + + factory, fDiags := m.GetStateStoreProviderFactory(root.StateStore, locks) + diags = diags.Append(fDiags) + if fDiags.HasErrors() { + return nil, diags + } + + opts = &BackendOpts{ + StateStoreConfig: root.StateStore, + ProviderFactory: factory, + Locks: locks, + ViewType: viewType, + } + default: + // there is no config; defaults to local state storage + opts = &BackendOpts{ + ViewType: viewType, + } + } + + // This method should not be used for init commands, + // so we always set this value as false. + opts.Init = false + + // Load the backend + be, beDiags := m.Backend(opts) + diags = diags.Append(beDiags) + if beDiags.HasErrors() { + return nil, diags + } + + return be, diags +} + //------------------------------------------------------------------- // State Store Config Scenarios // The functions below cover handling all the various scenarios that diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 116940796a72..77f1b37f4362 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -23,6 +23,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/cloud" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/clistate" "github.com/hashicorp/terraform/internal/command/workdir" "github.com/hashicorp/terraform/internal/configs" @@ -2962,6 +2963,126 @@ func Test_getStateStorageProviderVersion(t *testing.T) { }) } +func TestMetaBackend_prepareBackend(t *testing.T) { + + t.Run("it returns a cloud backend from cloud backend config", func(t *testing.T) { + // Create a temporary working directory with cloud configuration in + td := t.TempDir() + testCopyDir(t, testFixturePath("cloud-config"), td) + t.Chdir(td) + + m := testMetaBackend(t, nil) + + // We cannot initialize a cloud backend so we instead check + // the init error is referencing HCP Terraform + _, bDiags := m.backend(td, arguments.ViewHuman) + if !bDiags.HasErrors() { + t.Fatal("expected error but got none") + } + wantErr := "HCP Terraform or Terraform Enterprise initialization required: please run \"terraform init\"" + if !strings.Contains(bDiags.Err().Error(), wantErr) { + t.Fatalf("expected error to contain %q, but got: %q", + wantErr, + bDiags.Err()) + } + }) + + t.Run("it returns a backend from backend config", func(t *testing.T) { + // Create a temporary working directory with backend configuration in + td := t.TempDir() + testCopyDir(t, testFixturePath("backend-unchanged"), td) + t.Chdir(td) + + m := testMetaBackend(t, nil) + + b, bDiags := m.backend(td, arguments.ViewHuman) + if bDiags.HasErrors() { + t.Fatal("unexpected error: ", bDiags.Err()) + } + + if _, ok := b.(*local.Local); !ok { + t.Fatal("expected returned operations backend to be a Local backend") + } + // Check the type of backend inside the Local via schema + // In this case a `local` backend should have been returned by default. + // + // Look for the path attribute. + schema := b.ConfigSchema() + if _, ok := schema.Attributes["path"]; !ok { + t.Fatalf("expected the operations backend to report the schema of a local backend, but got something unexpected: %#v", schema) + } + }) + + t.Run("it returns a local backend when there is empty configuration", func(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("empty"), td) + t.Chdir(td) + + m := testMetaBackend(t, nil) + b, bDiags := m.backend(td, arguments.ViewHuman) + if bDiags.HasErrors() { + t.Fatal("unexpected error: ", bDiags.Err()) + } + + if _, ok := b.(*local.Local); !ok { + t.Fatal("expected returned operations backend to be a Local backend") + } + // Check the type of backend inside the Local via schema + // In this case a `local` backend should have been returned by default. + // + // Look for the path attribute. + schema := b.ConfigSchema() + if _, ok := schema.Attributes["path"]; !ok { + t.Fatalf("expected the operations backend to report the schema of a local backend, but got something unexpected: %#v", schema) + } + }) + + t.Run("it returns a state_store from state_store config", func(t *testing.T) { + // Create a temporary working directory with backend configuration in + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-unchanged"), td) + t.Chdir(td) + + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + mock := testStateStoreMockWithChunkNegotiation(t, 12345) // chunk size needs to be set, value is arbitrary + m.testingOverrides = &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): providers.FactoryFixed(mock), + }, + } + + // Prepare appropriate locks; config uses a hashicorp/test provider @ v1.2.3 + locks := depsfile.NewLocks() + providerAddr := addrs.MustParseProviderSourceString("registry.terraform.io/hashicorp/test") + constraint, err := providerreqs.ParseVersionConstraints(">1.0.0") + if err != nil { + t.Fatalf("test setup failed when making constraint: %s", err) + } + locks.SetProvider( + providerAddr, + versions.MustParseVersion("1.2.3"), + constraint, + []providerreqs.Hash{""}, + ) + + b, bDiags := m.backend(td, arguments.ViewHuman) + if bDiags.HasErrors() { + t.Fatalf("unexpected error: %s", bDiags.Err()) + } + + if _, ok := b.(*local.Local); !ok { + t.Fatal("expected returned operations backend to be a Local backend") + } + // Check the state_store inside the Local via schema + // Look for the mock state_store's attribute called `value`. + schema := b.ConfigSchema() + if _, ok := schema.Attributes["value"]; !ok { + t.Fatalf("expected the operations backend to report the schema of the state_store, but got something unexpected: %#v", schema) + } + }) +} + func testMetaBackend(t *testing.T, args []string) *Meta { var m Meta m.Ui = new(cli.MockUi) @@ -3011,6 +3132,21 @@ func testStateStoreMock(t *testing.T) *testing_provider.MockProvider { } } +// testStateStoreMockWithChunkNegotiation is just like testStateStoreMock but the returned mock is set up so it'll be configured +// without this error: `Failed to negotiate acceptable chunk size` +// +// This is meant to be a convenience method when a test is definitely not testing anything related to state store configuration. +func testStateStoreMockWithChunkNegotiation(t *testing.T, chunkSize int64) *testing_provider.MockProvider { + t.Helper() + mock := testStateStoreMock(t) + mock.ConfigureStateStoreResponse = &providers.ConfigureStateStoreResponse{ + Capabilities: providers.StateStoreServerCapabilities{ + ChunkSize: chunkSize, + }, + } + return mock +} + func configBodyForTest(t *testing.T, config string) hcl.Body { t.Helper() f, diags := hclsyntax.ParseConfig([]byte(config), "", hcl.Pos{Line: 1, Column: 1}) diff --git a/internal/command/output.go b/internal/command/output.go index 10828deea36d..1fb1d6ebcc51 100644 --- a/internal/command/output.go +++ b/internal/command/output.go @@ -35,7 +35,7 @@ func (c *OutputCommand) Run(rawArgs []string) int { view := views.NewOutput(args.ViewType, c.View) // Fetch data from state - outputs, diags := c.Outputs(args.StatePath) + outputs, diags := c.Outputs(args.StatePath, args.ViewType) if diags.HasErrors() { view.Diagnostics(diags) return 1 @@ -54,7 +54,7 @@ func (c *OutputCommand) Run(rawArgs []string) int { return 0 } -func (c *OutputCommand) Outputs(statePath string) (map[string]*states.OutputValue, tfdiags.Diagnostics) { +func (c *OutputCommand) Outputs(statePath string, view arguments.ViewType) (map[string]*states.OutputValue, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics // Allow state path override @@ -63,9 +63,9 @@ func (c *OutputCommand) Outputs(statePath string) (map[string]*states.OutputValu } // Load the backend - b, backendDiags := c.Backend(nil) + b, backendDiags := c.backend(".", view) diags = diags.Append(backendDiags) - if diags.HasErrors() { + if backendDiags.HasErrors() { return nil, diags } diff --git a/internal/command/plan.go b/internal/command/plan.go index a6f0b1400181..4172ff884872 100644 --- a/internal/command/plan.go +++ b/internal/command/plan.go @@ -124,18 +124,9 @@ func (c *PlanCommand) PrepareBackend(args *arguments.State, viewType arguments.V // difficult but would make their use easier to understand. c.Meta.applyStateArguments(args) - backendConfig, diags := c.loadBackendConfig(".") - if diags.HasErrors() { - return nil, diags - } - // Load the backend - be, beDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - ViewType: viewType, - }) - diags = diags.Append(beDiags) - if beDiags.HasErrors() { + be, diags := c.backend(".", viewType) + if diags.HasErrors() { return nil, diags } diff --git a/internal/command/providers.go b/internal/command/providers.go index 62f8fc3302df..61064f65ef5c 100644 --- a/internal/command/providers.go +++ b/internal/command/providers.go @@ -10,6 +10,7 @@ import ( "github.com/xlab/treeprint" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/getproviders" "github.com/hashicorp/terraform/internal/tfdiags" @@ -81,9 +82,7 @@ func (c *ProvidersCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: config.Module.Backend, - }) + b, backendDiags := c.backend(".", arguments.ViewHuman) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/providers_schema.go b/internal/command/providers_schema.go index 919e1a57078c..bd008449129d 100644 --- a/internal/command/providers_schema.go +++ b/internal/command/providers_schema.go @@ -45,6 +45,7 @@ func (c *ProvidersSchemaCommand) Run(args []string) int { cmdFlags.Usage() return 1 } + viewType := arguments.ViewJSON // See above; enforced use of JSON output // Check for user-supplied plugin path var err error @@ -56,7 +57,7 @@ func (c *ProvidersSchemaCommand) Run(args []string) int { var diags tfdiags.Diagnostics // Load the backend - b, backendDiags := c.Backend(nil) + b, backendDiags := c.backend(".", viewType) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/query.go b/internal/command/query.go index 5faf2f0616d8..91477bd6b0b2 100644 --- a/internal/command/query.go +++ b/internal/command/query.go @@ -154,21 +154,8 @@ func (c *QueryCommand) Run(rawArgs []string) int { } func (c *QueryCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backendrun.OperationsBackend, tfdiags.Diagnostics) { - backendConfig, diags := c.loadBackendConfig(".") - if diags.HasErrors() { - return nil, diags - } - // Load the backend - be, beDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - ViewType: viewType, - }) - diags = diags.Append(beDiags) - if beDiags.HasErrors() { - return nil, diags - } - + be, diags := c.backend(".", viewType) return be, diags } diff --git a/internal/command/refresh.go b/internal/command/refresh.go index 970e2a6167be..59c2a5097cc5 100644 --- a/internal/command/refresh.go +++ b/internal/command/refresh.go @@ -117,18 +117,9 @@ func (c *RefreshCommand) PrepareBackend(args *arguments.State, viewType argument // difficult but would make their use easier to understand. c.Meta.applyStateArguments(args) - backendConfig, diags := c.loadBackendConfig(".") - if diags.HasErrors() { - return nil, diags - } - // Load the backend - be, beDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - ViewType: viewType, - }) - diags = diags.Append(beDiags) - if beDiags.HasErrors() { + be, diags := c.backend(".", viewType) + if diags.HasErrors() { return nil, diags } diff --git a/internal/command/show.go b/internal/command/show.go index afe1fe567e20..8a7593c48519 100644 --- a/internal/command/show.go +++ b/internal/command/show.go @@ -150,7 +150,7 @@ func (c *ShowCommand) showFromLatestStateSnapshot() (*statefile.File, tfdiags.Di var diags tfdiags.Diagnostics // Load the backend - b, backendDiags := c.Backend(nil) + b, backendDiags := c.backend(".", c.viewType) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { return nil, diags @@ -278,9 +278,9 @@ func (c *ShowCommand) getPlanFromPath(path string) (*plans.Plan, *cloudplan.Remo func (c *ShowCommand) getDataFromCloudPlan(plan *cloudplan.SavedPlanBookmark, redacted bool) (*cloudplan.RemotePlanJSON, error) { // Set up the backend - b, backendDiags := c.Backend(nil) - if backendDiags.HasErrors() { - return nil, errUnusable(backendDiags.Err(), "cloud plan") + b, diags := c.backend(".", c.viewType) + if diags.HasErrors() { + return nil, errUnusable(diags.Err(), "cloud plan") } // Cloud plans only work if we're cloud. cl, ok := b.(*cloud.Cloud) diff --git a/internal/command/state_identities.go b/internal/command/state_identities.go index 99ef4e09e7b0..c87e31df7266 100644 --- a/internal/command/state_identities.go +++ b/internal/command/state_identities.go @@ -10,8 +10,8 @@ import ( "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/states" - "github.com/hashicorp/terraform/internal/tfdiags" ) // StateIdentitiesCommand is a Command implementation that lists the resource identities @@ -41,15 +41,16 @@ func (c *StateIdentitiesCommand) Run(args []string) int { cmdFlags.Usage() return 1 } + view := arguments.ViewJSON // See above if statePath != "" { c.Meta.statePath = statePath } // Load the backend - b, backendDiags := c.Backend(nil) - if backendDiags.HasErrors() { - c.showDiagnostics(backendDiags) + b, diags := c.backend(".", view) + if diags.HasErrors() { + c.showDiagnostics(diags) return 1 } @@ -79,7 +80,6 @@ func (c *StateIdentitiesCommand) Run(args []string) int { } var addrs []addrs.AbsResourceInstance - var diags tfdiags.Diagnostics if len(args) == 0 { addrs, diags = c.lookupAllResourceInstanceAddrs(state) } else { diff --git a/internal/command/state_list.go b/internal/command/state_list.go index dd211ebfe383..55d39934db73 100644 --- a/internal/command/state_list.go +++ b/internal/command/state_list.go @@ -9,8 +9,8 @@ import ( "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/states" - "github.com/hashicorp/terraform/internal/tfdiags" ) // StateListCommand is a Command implementation that lists the resources @@ -37,9 +37,9 @@ func (c *StateListCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(nil) - if backendDiags.HasErrors() { - c.showDiagnostics(backendDiags) + b, diags := c.backend(".", arguments.ViewHuman) + if diags.HasErrors() { + c.showDiagnostics(diags) return 1 } @@ -69,7 +69,6 @@ func (c *StateListCommand) Run(args []string) int { } var addrs []addrs.AbsResourceInstance - var diags tfdiags.Diagnostics if len(args) == 0 { addrs, diags = c.lookupAllResourceInstanceAddrs(state) } else { diff --git a/internal/command/state_meta.go b/internal/command/state_meta.go index 75c935d4ba91..edb3383b4f3b 100644 --- a/internal/command/state_meta.go +++ b/internal/command/state_meta.go @@ -9,6 +9,7 @@ import ( "time" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/terraform/internal/tfdiags" @@ -25,7 +26,7 @@ type StateMeta struct { // the backend, but changes the way that backups are done. This configures // backups to be timestamped rather than just the original state path plus a // backup path. -func (c *StateMeta) State() (statemgr.Full, error) { +func (c *StateMeta) State(view arguments.ViewType) (statemgr.Full, error) { var realState statemgr.Full backupPath := c.backupPath stateOutPath := c.statePath @@ -34,10 +35,11 @@ func (c *StateMeta) State() (statemgr.Full, error) { if c.statePath != "" { realState = statemgr.NewFilesystem(c.statePath) } else { + // Load the backend - b, backendDiags := c.Backend(nil) - if backendDiags.HasErrors() { - return nil, backendDiags.Err() + b, diags := c.backend(".", view) + if diags.HasErrors() { + return nil, diags.Err() } workspace, err := c.Workspace() diff --git a/internal/command/state_mv.go b/internal/command/state_mv.go index 7ce5d0bf027d..940a272c8e23 100644 --- a/internal/command/state_mv.go +++ b/internal/command/state_mv.go @@ -92,7 +92,8 @@ func (c *StateMvCommand) Run(args []string) int { } // Read the from state - stateFromMgr, err := c.State() + view := arguments.ViewHuman + stateFromMgr, err := c.State(view) if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) return 1 @@ -130,7 +131,7 @@ func (c *StateMvCommand) Run(args []string) int { c.statePath = statePathOut c.backupPath = backupPathOut - stateToMgr, err = c.State() + stateToMgr, err = c.State(view) if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) return 1 @@ -391,7 +392,8 @@ func (c *StateMvCommand) Run(args []string) int { return 0 // This is as far as we go in dry-run mode } - b, backendDiags := c.Backend(nil) + // Load the backend + b, backendDiags := c.backend(".", view) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/state_pull.go b/internal/command/state_pull.go index 45e9e6145941..19f3e76ede44 100644 --- a/internal/command/state_pull.go +++ b/internal/command/state_pull.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/states/statemgr" ) @@ -33,9 +34,10 @@ func (c *StatePullCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(nil) - if backendDiags.HasErrors() { - c.showDiagnostics(backendDiags) + view := arguments.ViewHuman + b, diags := c.backend(".", view) + if diags.HasErrors() { + c.showDiagnostics(diags) return 1 } diff --git a/internal/command/state_push.go b/internal/command/state_push.go index 0f013f4eb50d..b594eedc2947 100644 --- a/internal/command/state_push.go +++ b/internal/command/state_push.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/terraform/internal/states/statefile" "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/terraform/internal/terraform" - "github.com/hashicorp/terraform/internal/tfdiags" ) // StatePushCommand is a Command implementation that allows @@ -77,9 +76,10 @@ func (c *StatePushCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(nil) - if backendDiags.HasErrors() { - c.showDiagnostics(backendDiags) + view := arguments.ViewHuman + b, diags := c.backend(".", view) + if diags.HasErrors() { + c.showDiagnostics(diags) return 1 } @@ -135,7 +135,6 @@ func (c *StatePushCommand) Run(args []string) int { // Get schemas, if possible, before writing state var schemas *terraform.Schemas - var diags tfdiags.Diagnostics if isCloudMode(b) { schemas, diags = c.MaybeGetSchemas(srcStateFile.State, nil) } diff --git a/internal/command/state_replace_provider.go b/internal/command/state_replace_provider.go index 53796adedc9e..07ce2d83367f 100644 --- a/internal/command/state_replace_provider.go +++ b/internal/command/state_replace_provider.go @@ -76,7 +76,8 @@ func (c *StateReplaceProviderCommand) Run(args []string) int { } // Initialize the state manager as configured - stateMgr, err := c.State() + view := arguments.ViewHuman + stateMgr, err := c.State(view) if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) return 1 @@ -164,7 +165,8 @@ func (c *StateReplaceProviderCommand) Run(args []string) int { resource.ProviderConfig.Provider = to } - b, backendDiags := c.Backend(nil) + // Load the backend + b, backendDiags := c.backend(".", view) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/state_rm.go b/internal/command/state_rm.go index 805b8e91505b..b973f08148d5 100644 --- a/internal/command/state_rm.go +++ b/internal/command/state_rm.go @@ -48,7 +48,8 @@ func (c *StateRmCommand) Run(args []string) int { } // Get the state - stateMgr, err := c.State() + view := arguments.ViewHuman + stateMgr, err := c.State(view) if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) return 1 @@ -115,7 +116,8 @@ func (c *StateRmCommand) Run(args []string) int { return 0 // This is as far as we go in dry-run mode } - b, backendDiags := c.Backend(nil) + // Load the backend + b, backendDiags := c.backend(".", view) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/state_show.go b/internal/command/state_show.go index b76d398ccd45..61f09d8ae27c 100644 --- a/internal/command/state_show.go +++ b/internal/command/state_show.go @@ -48,9 +48,10 @@ func (c *StateShowCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(nil) - if backendDiags.HasErrors() { - c.showDiagnostics(backendDiags) + view := arguments.ViewHuman + b, diags := c.backend(".", view) + if diags.HasErrors() { + c.showDiagnostics(diags) return 1 } diff --git a/internal/command/state_test.go b/internal/command/state_test.go index 712c56e8a5ad..00d6dfffaa1b 100644 --- a/internal/command/state_test.go +++ b/internal/command/state_test.go @@ -9,6 +9,7 @@ import ( "sort" "testing" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/states/statemgr" ) @@ -31,7 +32,8 @@ func TestStateDefaultBackupExtension(t *testing.T) { tmp := t.TempDir() t.Chdir(tmp) - s, err := (&StateMeta{}).State() + view := arguments.ViewHuman + s, err := (&StateMeta{}).State(view) if err != nil { t.Fatal(err) } diff --git a/internal/command/taint.go b/internal/command/taint.go index cd9eeb6f01fe..9d5a9cb469e5 100644 --- a/internal/command/taint.go +++ b/internal/command/taint.go @@ -66,7 +66,8 @@ func (c *TaintCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(nil) + view := arguments.ViewHuman + b, backendDiags := c.backend(".", view) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/testdata/state-store-unchanged/main.tf b/internal/command/testdata/state-store-unchanged/main.tf index d32e0d51615a..df1eaa76b650 100644 --- a/internal/command/testdata/state-store-unchanged/main.tf +++ b/internal/command/testdata/state-store-unchanged/main.tf @@ -1,7 +1,8 @@ terraform { required_providers { test = { - source = "hashicorp/test" + source = "hashicorp/test" + version = "1.2.3" } } state_store "test_store" { diff --git a/internal/command/unlock.go b/internal/command/unlock.go index b66e13050157..67d7b31cf152 100644 --- a/internal/command/unlock.go +++ b/internal/command/unlock.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/cli" @@ -51,17 +52,9 @@ func (c *UnlockCommand) Run(args []string) int { var diags tfdiags.Diagnostics - backendConfig, backendDiags := c.loadBackendConfig(configPath) - diags = diags.Append(backendDiags) - if diags.HasErrors() { - c.showDiagnostics(diags) - return 1 - } - // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) + view := arguments.ViewHuman + b, backendDiags := c.backend(configPath, view) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/untaint.go b/internal/command/untaint.go index ed82ed97299d..d9beb118d68d 100644 --- a/internal/command/untaint.go +++ b/internal/command/untaint.go @@ -56,7 +56,8 @@ func (c *UntaintCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(nil) + view := arguments.ViewHuman + b, backendDiags := c.backend(".", view) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/workspace_delete.go b/internal/command/workspace_delete.go index 8213014579c5..a0334425750e 100644 --- a/internal/command/workspace_delete.go +++ b/internal/command/workspace_delete.go @@ -59,17 +59,9 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int { var diags tfdiags.Diagnostics - backendConfig, backendDiags := c.loadBackendConfig(configPath) - diags = diags.Append(backendDiags) - if diags.HasErrors() { - c.showDiagnostics(diags) - return 1 - } - // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) + view := arguments.ViewHuman + b, backendDiags := c.backend(configPath, view) diags = diags.Append(backendDiags) if backendDiags.HasErrors() { c.showDiagnostics(diags) diff --git a/internal/command/workspace_list.go b/internal/command/workspace_list.go index 17e85938dceb..69eddc803276 100644 --- a/internal/command/workspace_list.go +++ b/internal/command/workspace_list.go @@ -8,7 +8,7 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform/internal/tfdiags" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/posener/complete" ) @@ -35,21 +35,10 @@ func (c *WorkspaceListCommand) Run(args []string) int { return 1 } - var diags tfdiags.Diagnostics - - backendConfig, backendDiags := c.loadBackendConfig(configPath) - diags = diags.Append(backendDiags) - if diags.HasErrors() { - c.showDiagnostics(diags) - return 1 - } - // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) - diags = diags.Append(backendDiags) - if backendDiags.HasErrors() { + view := arguments.ViewHuman + b, diags := c.backend(configPath, view) + if diags.HasErrors() { c.showDiagnostics(diags) return 1 } @@ -58,6 +47,7 @@ func (c *WorkspaceListCommand) Run(args []string) int { c.ignoreRemoteVersionConflict(b) states, wDiags := b.Workspaces() + diags = diags.Append(wDiags) if wDiags.HasErrors() { c.Ui.Error(wDiags.Err().Error()) return 1 diff --git a/internal/command/workspace_new.go b/internal/command/workspace_new.go index b0cd57007640..d36c76efef61 100644 --- a/internal/command/workspace_new.go +++ b/internal/command/workspace_new.go @@ -68,19 +68,10 @@ func (c *WorkspaceNewCommand) Run(args []string) int { var diags tfdiags.Diagnostics - backendConfig, backendDiags := c.loadBackendConfig(configPath) - diags = diags.Append(backendDiags) - if diags.HasErrors() { - c.showDiagnostics(diags) - return 1 - } - // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) - diags = diags.Append(backendDiags) - if backendDiags.HasErrors() { + view := arguments.ViewHuman + b, diags := c.backend(configPath, view) + if diags.HasErrors() { c.showDiagnostics(diags) return 1 } diff --git a/internal/command/workspace_select.go b/internal/command/workspace_select.go index 0696fa677b3a..2f11bad3d813 100644 --- a/internal/command/workspace_select.go +++ b/internal/command/workspace_select.go @@ -8,7 +8,7 @@ import ( "strings" "github.com/hashicorp/cli" - "github.com/hashicorp/terraform/internal/tfdiags" + "github.com/hashicorp/terraform/internal/command/arguments" "github.com/posener/complete" ) @@ -42,15 +42,6 @@ func (c *WorkspaceSelectCommand) Run(args []string) int { return 1 } - var diags tfdiags.Diagnostics - - backendConfig, backendDiags := c.loadBackendConfig(configPath) - diags = diags.Append(backendDiags) - if diags.HasErrors() { - c.showDiagnostics(diags) - return 1 - } - current, isOverridden := c.WorkspaceOverridden() if isOverridden { c.Ui.Error(envIsOverriddenSelectError) @@ -58,11 +49,9 @@ func (c *WorkspaceSelectCommand) Run(args []string) int { } // Load the backend - b, backendDiags := c.Backend(&BackendOpts{ - BackendConfig: backendConfig, - }) - diags = diags.Append(backendDiags) - if backendDiags.HasErrors() { + view := arguments.ViewHuman + b, diags := c.backend(configPath, view) + if diags.HasErrors() { c.showDiagnostics(diags) return 1 }