diff --git a/internal/command/e2etest/providers_tamper_test.go b/internal/command/e2etest/providers_tamper_test.go index ac5612192..5335c66f9 100644 --- a/internal/command/e2etest/providers_tamper_test.go +++ b/internal/command/e2etest/providers_tamper_test.go @@ -57,7 +57,7 @@ func TestProviderTampering(t *testing.T) { t.Fatal(err) } - _, stderr, err := tf.Run("plan") + stdout, stderr, err := tf.Run("plan") if err == nil { t.Fatalf("unexpected plan success\nstdout:\n%s", stdout) } @@ -67,6 +67,16 @@ func TestProviderTampering(t *testing.T) { if want := `terraform init`; !strings.Contains(stderr, want) { t.Errorf("missing expected error message\nwant substring: %s\ngot:\n%s", want, stderr) } + + // Running init as suggested resolves the problem + _, stderr, err = tf.Run("init") + if err != nil { + t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) + } + _, stderr, err = tf.Run("plan") + if err != nil { + t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) + } }) t.Run("null plugin package modified before plan", func(t *testing.T) { tf := e2e.NewBinary(terraformBin, seedDir) diff --git a/internal/command/init.go b/internal/command/init.go index 8083b7513..8f8c5b829 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -238,7 +238,7 @@ func (c *InitCommand) Run(args []string) int { // by a previous run, so we must still expect that "back" may be nil // in code that follows. var backDiags tfdiags.Diagnostics - back, backDiags = c.Backend(nil) + back, backDiags = c.Backend(&BackendOpts{Init: true}) if backDiags.HasErrors() { // This is fine. We'll proceed with no backend, then. back = nil diff --git a/internal/command/meta.go b/internal/command/meta.go index cd9387173..6bfc2d8c7 100644 --- a/internal/command/meta.go +++ b/internal/command/meta.go @@ -459,10 +459,8 @@ func (m *Meta) contextOpts() (*terraform.ContextOpts, error) { opts.Providers = m.testingOverrides.Providers opts.Provisioners = m.testingOverrides.Provisioners } else { - providerFactories, err := m.providerFactories() - if err != nil { - return nil, err - } + var providerFactories map[addrs.Provider]providers.Factory + providerFactories, err = m.providerFactories() opts.Providers = providerFactories opts.Provisioners = m.provisionerFactories() } @@ -472,7 +470,7 @@ func (m *Meta) contextOpts() (*terraform.ContextOpts, error) { OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(), } - return &opts, nil + return &opts, err } // defaultFlagSet creates a default flag set for commands. diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 4d9b115d4..2524ccb28 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -112,28 +112,34 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics // indicates one or more inconsistencies between the dependency // lock file and the provider plugins actually available in the // local cache directory. - var buf bytes.Buffer - for addr, err := range errs { - fmt.Fprintf(&buf, "\n - %s: %s", addr, err) + // + // If initialization is allowed, we ignore this error, as it may + // be resolved by the later step where providers are fetched. + if !opts.Init { + var buf bytes.Buffer + for addr, err := range errs { + fmt.Fprintf(&buf, "\n - %s: %s", addr, err) + } + suggestion := "To download the plugins required for this configuration, run:\n terraform init" + if m.RunningInAutomation { + // Don't mention "terraform init" specifically if we're running in an automation wrapper + suggestion = "You must install the required plugins before running Terraform operations." + } + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Required plugins are not installed", + fmt.Sprintf( + "The installed provider plugins are not consistent with the packages selected in the dependency lock file:%s\n\nTerraform uses external plugins to integrate with a variety of different infrastructure services. %s", + buf.String(), suggestion, + ), + )) + return nil, diags } - suggestion := "To download the plugins required for this configuration, run:\n terraform init" - if m.RunningInAutomation { - // Don't mention "terraform init" specifically if we're running in an automation wrapper - suggestion = "You must install the required plugins before running Terraform operations." - } - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Required plugins are not installed", - fmt.Sprintf( - "The installed provider plugins are not consistent with the packages selected in the dependency lock file:%s\n\nTerraform uses external plugins to integrate with a variety of different infrastructure services. %s", - buf.String(), suggestion, - ), - )) } else { // All other errors just get generic handling. diags = diags.Append(err) + return nil, diags } - return nil, diags } cliOpts.Validation = true @@ -337,7 +343,7 @@ func (m *Meta) BackendForPlan(settings plans.Backend) (backend.Enhanced, tfdiags // a backend that supports local CLI operations. func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) { contextOpts, err := m.contextOpts() - if err != nil { + if contextOpts == nil && err != nil { return nil, err } return &backend.CLIOpts{ @@ -350,7 +356,7 @@ func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) { ContextOpts: contextOpts, Input: m.Input(), RunningInAutomation: m.RunningInAutomation, - }, nil + }, err } // Operation initializes a new backend.Operation struct.