cli: Fix init failure with deleted cache

The init command needs to initialize a backend, in order to access
state, in turn to derive provider requirements from state. The backend
initialization step requires building provider factories, which
previously would fail if a lockfile was present without a corresponding
local provider cache.

This commit ensures that in this situation only, errors with the
provider factories are temporarily ignored. This allows us to continue
to initialize the backend, fetch providers, and then report any errors
as necessary.
This commit is contained in:
Alisdair McDiarmid 2021-10-21 08:44:26 -04:00
parent 1190b95fe2
commit 39de3ebec7
3 changed files with 29 additions and 25 deletions

View File

@ -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 // by a previous run, so we must still expect that "back" may be nil
// in code that follows. // in code that follows.
var backDiags tfdiags.Diagnostics var backDiags tfdiags.Diagnostics
back, backDiags = c.Backend(nil) back, backDiags = c.Backend(&BackendOpts{Init: true})
if backDiags.HasErrors() { if backDiags.HasErrors() {
// This is fine. We'll proceed with no backend, then. // This is fine. We'll proceed with no backend, then.
back = nil back = nil

View File

@ -459,10 +459,8 @@ func (m *Meta) contextOpts() (*terraform.ContextOpts, error) {
opts.Providers = m.testingOverrides.Providers opts.Providers = m.testingOverrides.Providers
opts.Provisioners = m.testingOverrides.Provisioners opts.Provisioners = m.testingOverrides.Provisioners
} else { } else {
providerFactories, err := m.providerFactories() var providerFactories map[addrs.Provider]providers.Factory
if err != nil { providerFactories, err = m.providerFactories()
return nil, err
}
opts.Providers = providerFactories opts.Providers = providerFactories
opts.Provisioners = m.provisionerFactories() opts.Provisioners = m.provisionerFactories()
} }
@ -472,7 +470,7 @@ func (m *Meta) contextOpts() (*terraform.ContextOpts, error) {
OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(), OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(),
} }
return &opts, nil return &opts, err
} }
// defaultFlagSet creates a default flag set for commands. // defaultFlagSet creates a default flag set for commands.

View File

@ -112,28 +112,34 @@ func (m *Meta) Backend(opts *BackendOpts) (backend.Enhanced, tfdiags.Diagnostics
// indicates one or more inconsistencies between the dependency // indicates one or more inconsistencies between the dependency
// lock file and the provider plugins actually available in the // lock file and the provider plugins actually available in the
// local cache directory. // local cache directory.
var buf bytes.Buffer //
for addr, err := range errs { // If initialization is allowed, we ignore this error, as it may
fmt.Fprintf(&buf, "\n - %s: %s", addr, err) // 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 { } else {
// All other errors just get generic handling. // All other errors just get generic handling.
diags = diags.Append(err) diags = diags.Append(err)
return nil, diags
} }
return nil, diags
} }
cliOpts.Validation = true cliOpts.Validation = true
@ -337,7 +343,7 @@ func (m *Meta) BackendForPlan(settings plans.Backend) (backend.Enhanced, tfdiags
// a backend that supports local CLI operations. // a backend that supports local CLI operations.
func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) { func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) {
contextOpts, err := m.contextOpts() contextOpts, err := m.contextOpts()
if err != nil { if contextOpts == nil && err != nil {
return nil, err return nil, err
} }
return &backend.CLIOpts{ return &backend.CLIOpts{
@ -350,7 +356,7 @@ func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) {
ContextOpts: contextOpts, ContextOpts: contextOpts,
Input: m.Input(), Input: m.Input(),
RunningInAutomation: m.RunningInAutomation, RunningInAutomation: m.RunningInAutomation,
}, nil }, err
} }
// Operation initializes a new backend.Operation struct. // Operation initializes a new backend.Operation struct.