PSS: Allow backend or state_store config to be passed via BackendOpts into method for initialising the operations backend for a command (#37346)

* Allow backend or state_store config to be passed via BackendOpts from calling code

* Update messages sent to view: make message specific to state storage mechanism in use

* Add nil pointer check

* Fix typos

* Pivot to `Len` method approach of nil check

* Pivot to the point of pirouetting
pull/37327/head
Sarah French 9 months ago committed by GitHub
parent f96a3883b7
commit 7b28a5d842
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -8,7 +8,9 @@ import (
"errors"
"fmt"
"log"
"maps"
"reflect"
"slices"
"sort"
"strings"
@ -28,6 +30,7 @@ import (
"github.com/hashicorp/terraform/internal/command/views"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/didyoumean"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/providercache"
"github.com/hashicorp/terraform/internal/states"
@ -390,7 +393,7 @@ func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, ear
func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
ctx, span := tracer.Start(ctx, "initialize HCP Terraform")
_ = ctx // prevent staticcheck from complaining to avoid a maintenence hazard of having the wrong ctx in scope here
_ = ctx // prevent staticcheck from complaining to avoid a maintenance hazard of having the wrong ctx in scope here
defer span.End()
view.Output(views.InitializingTerraformCloudMessage)
@ -419,14 +422,125 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra
func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
ctx, span := tracer.Start(ctx, "initialize backend")
_ = ctx // prevent staticcheck from complaining to avoid a maintenence hazard of having the wrong ctx in scope here
_ = ctx // prevent staticcheck from complaining to avoid a maintenance hazard of having the wrong ctx in scope here
defer span.End()
view.Output(views.InitializingBackendMessage)
if root.StateStore != nil {
view.Output(views.InitializingStateStoreMessage)
} else {
view.Output(views.InitializingBackendMessage)
}
var opts *BackendOpts
switch {
case root.StateStore != nil && root.Backend != nil:
// We expect validation during config parsing to prevent mutually exclusive backend and state_store blocks,
// but checking here just in case.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Conflicting backend and state_store configurations present during init",
Detail: fmt.Sprintf("When initializing the backend there was configuration data present for both backend %q and state store %q. This is a bug in Terraform and should be reported.",
root.Backend.Type,
root.StateStore.Type,
),
Subject: &root.Backend.TypeRange,
})
return nil, true, diags
case root.StateStore != nil:
// state_store config present
// Access provider factories
ctxOpts, err := c.contextOpts()
if err != nil {
diags = diags.Append(err)
return nil, true, diags
}
if root.StateStore.ProviderAddr.IsZero() {
// This should not happen; this data is populated when parsing config,
// even for builtin providers
panic(fmt.Sprintf("unknown provider while beginning to initialize state store %q from provider %q",
root.StateStore.Type,
root.StateStore.Provider.Name))
}
var exists bool
factory, exists := ctxOpts.Providers[root.StateStore.ProviderAddr]
if !exists {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Provider unavailable",
Detail: fmt.Sprintf("The provider %s (%q) is required to initialize the %q state store, but the matching provider factory is missing. This is a bug in Terraform and should be reported.",
root.StateStore.Provider.Name,
root.StateStore.ProviderAddr,
root.StateStore.Type,
),
Subject: &root.Backend.TypeRange,
})
return nil, true, diags
}
// If overrides supplied by -backend-config CLI flag, process them
var configOverride hcl.Body
if !extraConfig.Empty() {
// We need to launch an instance of the provider to get the config of the state store for processing any overrides.
provider, err := factory()
defer provider.Close() // Stop the child process once we're done with it here.
if err != nil {
diags = diags.Append(fmt.Errorf("error when obtaining provider instance during state store initialization: %w", err))
return nil, true, diags
}
resp := provider.GetProviderSchema()
var backendConfig *configs.Backend
var backendConfigOverride hcl.Body
if root.Backend != nil {
if len(resp.StateStores) == 0 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Provider does not support pluggable state storage",
Detail: fmt.Sprintf("There are no state stores implemented by provider %s (%q)",
root.StateStore.Provider.Name,
root.StateStore.ProviderAddr),
Subject: &root.StateStore.DeclRange,
})
return nil, true, diags
}
stateStoreSchema, exists := resp.StateStores[root.StateStore.Type]
if !exists {
suggestions := slices.Sorted(maps.Keys(resp.StateStores))
suggestion := didyoumean.NameSuggestion(root.StateStore.Type, suggestions)
if suggestion != "" {
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
}
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "State store not implemented by the provider",
Detail: fmt.Sprintf("State store %q is not implemented by provider %s (%q)%s",
root.StateStore.Type, root.StateStore.Provider.Name,
root.StateStore.ProviderAddr, suggestion),
Subject: &root.StateStore.DeclRange,
})
return nil, true, diags
}
// Handle any overrides supplied via -backend-config CLI flags
var overrideDiags tfdiags.Diagnostics
configOverride, overrideDiags = c.backendConfigOverrideBody(extraConfig, stateStoreSchema.Body)
diags = diags.Append(overrideDiags)
if overrideDiags.HasErrors() {
return nil, true, diags
}
}
opts = &BackendOpts{
StateStoreConfig: root.StateStore,
ProviderFactory: factory,
ConfigOverride: configOverride,
Init: true,
ViewType: viewType,
}
case root.Backend != nil:
// backend config present
backendType := root.Backend.Type
if backendType == "cloud" {
diags = diags.Append(&hcl.Diagnostic{
@ -456,15 +570,24 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ext
b := bf()
backendSchema := b.ConfigSchema()
backendConfig = root.Backend
backendConfig := root.Backend
var overrideDiags tfdiags.Diagnostics
backendConfigOverride, overrideDiags = c.backendConfigOverrideBody(extraConfig, backendSchema)
backendConfigOverride, overrideDiags := c.backendConfigOverrideBody(extraConfig, backendSchema)
diags = diags.Append(overrideDiags)
if overrideDiags.HasErrors() {
return nil, true, diags
}
} else {
opts = &BackendOpts{
BackendConfig: backendConfig,
ConfigOverride: backendConfigOverride,
Init: true,
ViewType: viewType,
}
default:
// No config; defaults to local state storage
// If the user supplied a -backend-config on the CLI but no backend
// block was found in the configuration, it's likely - but not
// necessarily - a mistake. Return a warning.
@ -486,14 +609,13 @@ However, if you intended to override a defined backend, please verify that
the backend configuration is present and valid.
`,
))
}
}
opts := &BackendOpts{
BackendConfig: backendConfig,
ConfigOverride: backendConfigOverride,
Init: true,
ViewType: viewType,
opts = &BackendOpts{
Init: true,
ViewType: viewType,
}
}
back, backDiags := c.Backend(opts)
@ -896,7 +1018,7 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config,
if !newLocks.Equal(previousLocks) {
// if readonly mode
if flagLockfile == "readonly" {
// check if required provider dependences change
// check if required provider dependencies change
if !newLocks.EqualProviderAddress(previousLocks) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -1114,7 +1236,7 @@ Options:
itself.
-force-copy Suppress prompts about copying state data when
initializating a new state backend. This is
initializing a new state backend. This is
equivalent to providing a "yes" to all confirmation
prompts.

@ -186,6 +186,10 @@ var MessageRegistry map[InitMessageCode]InitMessage = map[InitMessageCode]InitMe
HumanValue: "\n[reset][bold]Initializing provider plugins...",
JSONValue: "Initializing provider plugins...",
},
"initializing_state_store_message": {
HumanValue: "\n[reset][bold]Initializing the state store...",
JSONValue: "Initializing the state store...",
},
"dependencies_lock_changes_info": {
HumanValue: dependenciesLockChangesInfo,
JSONValue: dependenciesLockChangesInfo,
@ -258,6 +262,7 @@ const (
InitializingTerraformCloudMessage InitMessageCode = "initializing_terraform_cloud_message"
InitializingModulesMessage InitMessageCode = "initializing_modules_message"
InitializingBackendMessage InitMessageCode = "initializing_backend_message"
InitializingStateStoreMessage InitMessageCode = "initializing_state_store_message"
InitializingProviderPluginMessage InitMessageCode = "initializing_provider_plugin_message"
LockInfo InitMessageCode = "lock_info"
DependenciesLockChangesInfo InitMessageCode = "dependencies_lock_changes_info"

Loading…
Cancel
Save