From 4eda8c1f82fe417801813deb8c4ad8ae47cce53b Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 20:09:13 +0000 Subject: [PATCH 1/8] command/init: Merge PSS codepath with main codepath --- internal/command/init.go | 610 +++++------------------- internal/command/init_run.go | 179 ++++--- internal/command/init_run_experiment.go | 527 -------------------- 3 files changed, 234 insertions(+), 1082 deletions(-) delete mode 100644 internal/command/init_run_experiment.go diff --git a/internal/command/init.go b/internal/command/init.go index 6bf73c9cf116..4fb09300cbc6 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -7,7 +7,9 @@ import ( "context" "fmt" "log" + "maps" "reflect" + "slices" "sort" "strings" @@ -26,6 +28,7 @@ import ( "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/depsfile" + "github.com/hashicorp/terraform/internal/didyoumean" "github.com/hashicorp/terraform/internal/getproviders" "github.com/hashicorp/terraform/internal/getproviders/providerreqs" "github.com/hashicorp/terraform/internal/providercache" @@ -55,17 +58,7 @@ func (c *InitCommand) Run(args []string) int { return 1 } - // The else condition below invokes the original logic of the init command. - // An experimental version of the init code will be used if: - // > The user uses an experimental version of TF (alpha or built from source) - // > Either the flag -enable-pluggable-state-storage-experiment is passed to the init command. - // > Or, the environment variable TF_ENABLE_PLUGGABLE_STATE_STORAGE is set to any value. - if c.Meta.AllowExperimentalFeatures && initArgs.EnablePssExperiment { - // TODO(SarahFrench/radeksimko): Remove forked init logic once feature is no longer experimental - return c.runPssInit(initArgs, view) - } else { - return c.run(initArgs, view) - } + return c.run(initArgs, view) } func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, earlyRoot *configs.Module, upgrade bool, view views.Init) (output bool, abort bool, diags tfdiags.Diagnostics) { @@ -152,16 +145,103 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra return back, true, diags } -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) { +func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, initArgs *arguments.Init, configLocks *depsfile.Locks, 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 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 + factory, fDiags := c.Meta.StateStoreProviderFactoryFromConfig(root.StateStore, configLocks) + diags = diags.Append(fDiags) + if fDiags.HasErrors() { + return nil, true, diags + } - var backendConfig *configs.Backend - var backendConfigOverride hcl.Body - if root.Backend != nil { + // If overrides supplied by -backend-config CLI flag, process them + var configOverride hcl.Body + if !initArgs.BackendConfig.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() + + 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(initArgs.BackendConfig, stateStoreSchema.Body) + diags = diags.Append(overrideDiags) + if overrideDiags.HasErrors() { + return nil, true, diags + } + } + + opts = &BackendOpts{ + StateStoreConfig: root.StateStore, + Locks: configLocks, + CreateDefaultWorkspace: initArgs.CreateDefaultWorkspace, + ConfigOverride: configOverride, + Init: true, + ViewType: initArgs.ViewType, + } + + case root.Backend != nil: + // backend config present backendType := root.Backend.Type if backendType == "cloud" { diags = diags.Append(&hcl.Diagnostic{ @@ -191,19 +271,34 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ext b := bf() backendSchema := b.ConfigSchema() - backendConfig = root.Backend + backendConfig := root.Backend + + // If overrides supplied by -backend-config CLI flag, process them + var configOverride hcl.Body + if !initArgs.BackendConfig.Empty() { + var overrideDiags tfdiags.Diagnostics + configOverride, overrideDiags = c.backendConfigOverrideBody(initArgs.BackendConfig, backendSchema) + diags = diags.Append(overrideDiags) + if overrideDiags.HasErrors() { + return nil, true, diags + } + } - var overrideDiags tfdiags.Diagnostics - backendConfigOverride, overrideDiags = c.backendConfigOverrideBody(extraConfig, backendSchema) - diags = diags.Append(overrideDiags) - if overrideDiags.HasErrors() { - return nil, true, diags + opts = &BackendOpts{ + BackendConfig: backendConfig, + Locks: configLocks, + ConfigOverride: configOverride, + Init: true, + ViewType: initArgs.ViewType, } - } else { + + 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. - if !extraConfig.Empty() { + if !initArgs.BackendConfig.Empty() { diags = diags.Append(tfdiags.Sourceless( tfdiags.Warning, "Missing backend configuration", @@ -222,13 +317,12 @@ the backend configuration is present and valid. `, )) } - } - opts := &BackendOpts{ - BackendConfig: backendConfig, - ConfigOverride: backendConfigOverride, - Init: true, - ViewType: viewType, + opts = &BackendOpts{ + Init: true, + Locks: configLocks, + ViewType: initArgs.ViewType, + } } back, backDiags := c.Backend(opts) @@ -236,460 +330,6 @@ the backend configuration is present and valid. return back, true, diags } -// getProviders determines what providers are required given configuration and state data. The method downloads any missing providers -// and replaces the contents of the dependency lock file if any changes happen. -// The calling code is expected to have loaded the complete module tree and read the state file, and passes that data into this method. -// -// This method outputs to the provided view. The returned `output` boolean lets calling code know if anything has been rendered via the view. -func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config, state *states.State, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init) (output, abort bool, diags tfdiags.Diagnostics) { - ctx, span := tracer.Start(ctx, "install providers") - defer span.End() - - // Dev overrides cause the result of "terraform init" to be irrelevant for - // any overridden providers, so we'll warn about it to avoid later - // confusion when Terraform ends up using a different provider than the - // lock file called for. - diags = diags.Append(c.providerDevOverrideInitWarnings()) - - // First we'll collect all the provider dependencies we can see in the - // configuration and the state. - reqs, hclDiags := config.ProviderRequirements() - diags = diags.Append(hclDiags) - if hclDiags.HasErrors() { - return false, true, diags - } - - reqs = c.removeDevOverrides(reqs) - if state != nil { - stateReqs := state.ProviderRequirements() - reqs = reqs.Merge(stateReqs) - } - for providerAddr := range reqs { - if providerAddr.IsLegacy() { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid legacy provider address", - fmt.Sprintf( - "This configuration or its associated state refers to the unqualified provider %q.\n\nYou must complete the Terraform 0.13 upgrade process before upgrading to later versions.", - providerAddr.Type, - ), - )) - } - } - - previousLocks, moreDiags := c.lockedDependencies() - diags = diags.Append(moreDiags) - - if diags.HasErrors() { - return false, true, diags - } - - var inst *providercache.Installer - if len(pluginDirs) == 0 { - // By default we use a source that looks for providers in all of the - // standard locations, possibly customized by the user in CLI config. - inst = c.providerInstaller() - } else { - // If the user passes at least one -plugin-dir then that circumvents - // the usual sources and forces Terraform to consult only the given - // directories. Anything not available in one of those directories - // is not available for installation. - source := c.providerCustomLocalDirectorySource(pluginDirs) - inst = c.providerInstallerCustomSource(source) - - // The default (or configured) search paths are logged earlier, in provider_source.go - // Log that those are being overridden by the `-plugin-dir` command line options - log.Println("[DEBUG] init: overriding provider plugin search paths") - log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs) - } - - // We want to print out a nice warning if we don't manage to pull - // checksums for all our providers. This is tracked via callbacks - // and incomplete providers are stored here for later analysis. - var incompleteProviders []string - - // Because we're currently just streaming a series of events sequentially - // into the terminal, we're showing only a subset of the events to keep - // things relatively concise. Later it'd be nice to have a progress UI - // where statuses update in-place, but we can't do that as long as we - // are shimming our vt100 output to the legacy console API on Windows. - evts := &providercache.InstallerEvents{ - PendingProviders: func(reqs map[addrs.Provider]getproviders.VersionConstraints) { - view.Output(views.InitializingProviderPluginMessage) - }, - ProviderAlreadyInstalled: func(provider addrs.Provider, selectedVersion getproviders.Version) { - view.LogInitMessage(views.ProviderAlreadyInstalledMessage, provider.ForDisplay(), selectedVersion) - }, - BuiltInProviderAvailable: func(provider addrs.Provider) { - view.LogInitMessage(views.BuiltInProviderAvailableMessage, provider.ForDisplay()) - }, - BuiltInProviderFailure: func(provider addrs.Provider, err error) { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid dependency on built-in provider", - fmt.Sprintf("Cannot use %s: %s.", provider.ForDisplay(), err), - )) - }, - QueryPackagesBegin: func(provider addrs.Provider, versionConstraints getproviders.VersionConstraints, locked bool) { - if locked { - view.LogInitMessage(views.ReusingPreviousVersionInfo, provider.ForDisplay()) - } else { - if len(versionConstraints) > 0 { - view.LogInitMessage(views.FindingMatchingVersionMessage, provider.ForDisplay(), getproviders.VersionConstraintsString(versionConstraints)) - } else { - view.LogInitMessage(views.FindingLatestVersionMessage, provider.ForDisplay()) - } - } - }, - LinkFromCacheBegin: func(provider addrs.Provider, version getproviders.Version, cacheRoot string) { - view.LogInitMessage(views.UsingProviderFromCacheDirInfo, provider.ForDisplay(), version) - }, - FetchPackageBegin: func(provider addrs.Provider, version getproviders.Version, location getproviders.PackageLocation) { - view.LogInitMessage(views.InstallingProviderMessage, provider.ForDisplay(), version) - }, - QueryPackagesFailure: func(provider addrs.Provider, err error) { - switch errorTy := err.(type) { - case getproviders.ErrProviderNotFound: - sources := errorTy.Sources - displaySources := make([]string, len(sources)) - for i, source := range sources { - displaySources[i] = fmt.Sprintf(" - %s", source) - } - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to query available provider packages", - fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s\n\n%s", - provider.ForDisplay(), err, strings.Join(displaySources, "\n"), - ), - )) - case getproviders.ErrRegistryProviderNotKnown: - // We might be able to suggest an alternative provider to use - // instead of this one. - suggestion := fmt.Sprintf("\n\nAll modules should specify their required_providers so that external consumers will get the correct providers when using a module. To see which modules are currently depending on %s, run the following command:\n terraform providers", provider.ForDisplay()) - alternative := getproviders.MissingProviderSuggestion(ctx, provider, inst.ProviderSource(), reqs) - if alternative != provider { - suggestion = fmt.Sprintf( - "\n\nDid you intend to use %s? If so, you must specify that source address in each module which requires that provider. To see which modules are currently depending on %s, run the following command:\n terraform providers", - alternative.ForDisplay(), provider.ForDisplay(), - ) - } - - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to query available provider packages", - fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s", - provider.ForDisplay(), err, suggestion, - ), - )) - case getproviders.ErrHostNoProviders: - switch { - case errorTy.Hostname == svchost.Hostname("github.com") && !errorTy.HasOtherVersion: - // If a user copies the URL of a GitHub repository into - // the source argument and removes the schema to make it - // provider-address-shaped then that's one way we can end up - // here. We'll use a specialized error message in anticipation - // of that mistake. We only do this if github.com isn't a - // provider registry, to allow for the (admittedly currently - // rather unlikely) possibility that github.com starts being - // a real Terraform provider registry in the future. - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid provider registry host", - fmt.Sprintf("The given source address %q specifies a GitHub repository rather than a Terraform provider. Refer to the documentation of the provider to find the correct source address to use.", - provider.String(), - ), - )) - - case errorTy.HasOtherVersion: - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid provider registry host", - fmt.Sprintf("The host %q given in provider source address %q does not offer a Terraform provider registry that is compatible with this Terraform version, but it may be compatible with a different Terraform version.", - errorTy.Hostname, provider.String(), - ), - )) - - default: - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid provider registry host", - fmt.Sprintf("The host %q given in provider source address %q does not offer a Terraform provider registry.", - errorTy.Hostname, provider.String(), - ), - )) - } - - case getproviders.ErrRequestCanceled: - // We don't attribute cancellation to any particular operation, - // but rather just emit a single general message about it at - // the end, by checking ctx.Err(). - - default: - suggestion := fmt.Sprintf("\n\nTo see which modules are currently depending on %s and what versions are specified, run the following command:\n terraform providers", provider.ForDisplay()) - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to query available provider packages", - fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s", - provider.ForDisplay(), err, suggestion, - ), - )) - } - - }, - QueryPackagesWarning: func(provider addrs.Provider, warnings []string) { - displayWarnings := make([]string, len(warnings)) - for i, warning := range warnings { - displayWarnings[i] = fmt.Sprintf("- %s", warning) - } - - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Warning, - "Additional provider information from registry", - fmt.Sprintf("The remote registry returned warnings for %s:\n%s", - provider.String(), - strings.Join(displayWarnings, "\n"), - ), - )) - }, - LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to install provider from shared cache", - fmt.Sprintf("Error while importing %s v%s from the shared cache directory: %s.", provider.ForDisplay(), version, err), - )) - }, - FetchPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) { - const summaryIncompatible = "Incompatible provider version" - switch err := err.(type) { - case getproviders.ErrProtocolNotSupported: - closestAvailable := err.Suggestion - switch { - case closestAvailable == getproviders.UnspecifiedVersion: - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - summaryIncompatible, - fmt.Sprintf(errProviderVersionIncompatible, provider.String()), - )) - case version.GreaterThan(closestAvailable): - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - summaryIncompatible, - fmt.Sprintf(providerProtocolTooNew, provider.ForDisplay(), - version, tfversion.String(), closestAvailable, closestAvailable, - getproviders.VersionConstraintsString(reqs[provider]), - ), - )) - default: // version is less than closestAvailable - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - summaryIncompatible, - fmt.Sprintf(providerProtocolTooOld, provider.ForDisplay(), - version, tfversion.String(), closestAvailable, closestAvailable, - getproviders.VersionConstraintsString(reqs[provider]), - ), - )) - } - case getproviders.ErrPlatformNotSupported: - switch { - case err.MirrorURL != nil: - // If we're installing from a mirror then it may just be - // the mirror lacking the package, rather than it being - // unavailable from upstream. - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - summaryIncompatible, - fmt.Sprintf( - "Your chosen provider mirror at %s does not have a %s v%s package available for your current platform, %s.\n\nProvider releases are separate from Terraform CLI releases, so this provider might not support your current platform. Alternatively, the mirror itself might have only a subset of the plugin packages available in the origin registry, at %s.", - err.MirrorURL, err.Provider, err.Version, err.Platform, - err.Provider.Hostname, - ), - )) - default: - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - summaryIncompatible, - fmt.Sprintf( - "Provider %s v%s does not have a package available for your current platform, %s.\n\nProvider releases are separate from Terraform CLI releases, so not all providers are available for all platforms. Other versions of this provider may have different platforms supported.", - err.Provider, err.Version, err.Platform, - ), - )) - } - - case getproviders.ErrRequestCanceled: - // We don't attribute cancellation to any particular operation, - // but rather just emit a single general message about it at - // the end, by checking ctx.Err(). - - default: - // We can potentially end up in here under cancellation too, - // in spite of our getproviders.ErrRequestCanceled case above, - // because not all of the outgoing requests we do under the - // "fetch package" banner are source metadata requests. - // In that case we will emit a redundant error here about - // the request being cancelled, but we'll still detect it - // as a cancellation after the installer returns and do the - // normal cancellation handling. - - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to install provider", - fmt.Sprintf("Error while installing %s v%s: %s", provider.ForDisplay(), version, err), - )) - } - }, - FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) { - var keyID string - if authResult != nil && authResult.ThirdPartySigned() { - keyID = authResult.KeyID - } - if keyID != "" { - keyID = view.PrepareMessage(views.KeyID, keyID) - } - - view.LogInitMessage(views.InstalledProviderVersionInfo, provider.ForDisplay(), version, authResult, keyID) - }, - ProvidersLockUpdated: func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) { - // We're going to use this opportunity to track if we have any - // "incomplete" installs of providers. An incomplete install is - // when we are only going to write the local hashes into our lock - // file which means a `terraform init` command will fail in future - // when used on machines of a different architecture. - // - // We want to print a warning about this. - - if len(signedHashes) > 0 { - // If we have any signedHashes hashes then we don't worry - as - // we know we retrieved all available hashes for this version - // anyway. - return - } - - // If local hashes and prior hashes are exactly the same then - // it means we didn't record any signed hashes previously, and - // we know we're not adding any extra in now (because we already - // checked the signedHashes), so that's a problem. - // - // In the actual check here, if we have any priorHashes and those - // hashes are not the same as the local hashes then we're going to - // accept that this provider has been configured correctly. - if len(priorHashes) > 0 && !reflect.DeepEqual(localHashes, priorHashes) { - return - } - - // Now, either signedHashes is empty, or priorHashes is exactly the - // same as our localHashes which means we never retrieved the - // signedHashes previously. - // - // Either way, this is bad. Let's complain/warn. - incompleteProviders = append(incompleteProviders, provider.ForDisplay()) - }, - ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) { - thirdPartySigned := false - for _, authResult := range authResults { - if authResult.ThirdPartySigned() { - thirdPartySigned = true - break - } - } - if thirdPartySigned { - view.LogInitMessage(views.PartnerAndCommunityProvidersMessage) - } - }, - } - ctx = evts.OnContext(ctx) - - mode := providercache.InstallNewProvidersOnly - if upgrade { - if flagLockfile == "readonly" { - diags = diags.Append(fmt.Errorf("The -upgrade flag conflicts with -lockfile=readonly.")) - view.Diagnostics(diags) - return true, true, diags - } - - mode = providercache.InstallUpgrades - } - newLocks, err := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode) - if ctx.Err() == context.Canceled { - diags = diags.Append(fmt.Errorf("Provider installation was canceled by an interrupt signal.")) - view.Diagnostics(diags) - return true, true, diags - } - if err != nil { - // The errors captured in "err" should be redundant with what we - // received via the InstallerEvents callbacks above, so we'll - // just return those as long as we have some. - if !diags.HasErrors() { - diags = diags.Append(err) - } - - return true, true, diags - } - - // If the provider dependencies have changed since the last run then we'll - // say a little about that in case the reader wasn't expecting a change. - // (When we later integrate module dependencies into the lock file we'll - // probably want to refactor this so that we produce one lock-file related - // message for all changes together, but this is here for now just because - // it's the smallest change relative to what came before it, which was - // a hidden JSON file specifically for tracking providers.) - if !newLocks.Equal(previousLocks) { - // if readonly mode - if flagLockfile == "readonly" { - // check if required provider dependencies change - if !newLocks.EqualProviderAddress(previousLocks) { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - `Provider dependency changes detected`, - `Changes to the required provider dependencies were detected, but the lock file is read-only. To use and record these requirements, run "terraform init" without the "-lockfile=readonly" flag.`, - )) - return true, true, diags - } - - // suppress updating the file to record any new information it learned, - // such as a hash using a new scheme. - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Warning, - `Provider lock file not updated`, - `Changes to the provider selections were detected, but not saved in the .terraform.lock.hcl file. To record these selections, run "terraform init" without the "-lockfile=readonly" flag.`, - )) - return true, false, diags - } - - // Jump in here and add a warning if any of the providers are incomplete. - if len(incompleteProviders) > 0 { - // We don't really care about the order here, we just want the - // output to be deterministic. - sort.Slice(incompleteProviders, func(i, j int) bool { - return incompleteProviders[i] < incompleteProviders[j] - }) - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Warning, - incompleteLockFileInformationHeader, - fmt.Sprintf( - incompleteLockFileInformationBody, - strings.Join(incompleteProviders, "\n - "), - getproviders.CurrentPlatform.String()))) - } - - if previousLocks.Empty() { - // A change from empty to non-empty is special because it suggests - // we're running "terraform init" for the first time against a - // new configuration. In that case we'll take the opportunity to - // say a little about what the dependency lock file is, for new - // users or those who are upgrading from a previous Terraform - // version that didn't have dependency lock files. - view.Output(views.LockInfo) - } else { - view.Output(views.DependenciesLockChangesInfo) - } - - moreDiags = c.replaceLockedDependencies(newLocks) - diags = diags.Append(moreDiags) - } - - return true, false, diags -} - // getProvidersFromConfig determines what providers are required by the given configuration data. // The method downloads any missing providers that aren't already downloaded and then returns // dependency lock data based on the configuration. @@ -711,6 +351,8 @@ func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *config return false, nil, diags } + reqs = c.removeDevOverrides(reqs) + for providerAddr := range reqs { if providerAddr.IsLegacy() { diags = diags.Append(tfdiags.Sourceless( diff --git a/internal/command/init_run.go b/internal/command/init_run.go index 880b4b602737..d86e9c1ea5e3 100644 --- a/internal/command/init_run.go +++ b/internal/command/init_run.go @@ -163,18 +163,75 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { return 1 } + if initArgs.Get { + modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, initArgs.TestsDirectory, rootModEarly, initArgs.Upgrade, view) + diags = diags.Append(modsDiags) + if modsAbort || modsDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + if modsOutput { + header = true + } + } + + // With all of the modules (hopefully) installed, we can now try to load the + // whole configuration tree. + config, confDiags := c.loadConfigWithTests(path, initArgs.TestsDirectory) + // configDiags will be handled after: + // - the version constraint check has happened + // - and, the backend/state_store is initialised + + // Before we go further, we'll check to make sure none of the modules in + // the configuration declare that they don't support this Terraform + // version, so we can produce a version-related error message rather than + // potentially-confusing downstream errors. + versionDiags := terraform.CheckCoreVersionRequirements(config) + if versionDiags.HasErrors() { + view.Diagnostics(versionDiags) + return 1 + } + + // We've passed the core version check, now we can show errors from the early configuration. + // This prevents trying to initialise the backend with faulty configuration. + if earlyConfDiags.HasErrors() { + diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError)), earlyConfDiags) + view.Diagnostics(diags) + return 1 + } + + // Now the full configuration is loaded, we can download the providers specified in the configuration. + // This is step one of a two-step provider download process + // Providers may be downloaded by this code, but the dependency lock file is only updated later in `init` + // after step two of provider download is complete. + previousLocks, moreDiags := c.lockedDependencies() + diags = diags.Append(moreDiags) + + configProvidersOutput, configLocks, configProviderDiags := c.getProvidersFromConfig(ctx, config, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view) + diags = diags.Append(configProviderDiags) + if configProviderDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + if configProvidersOutput { + header = true + } + + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + if header { + view.Output(views.EmptyMessage) + } + var back backend.Backend - // There may be config errors or backend init errors but these will be shown later _after_ - // checking for core version requirement errors. var backDiags tfdiags.Diagnostics var backendOutput bool - switch { case initArgs.Cloud && rootModEarly.CloudConfig != nil: back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) case initArgs.Backend: - back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) + back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs, configLocks, view) default: // load the previously-stored backend config back, backDiags = c.Meta.backendFromState(ctx) @@ -182,6 +239,29 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { if backendOutput { header = true } + if header { + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + view.Output(views.EmptyMessage) + } + + // Show any errors from initializing the backend. + // No preamble using `InitConfigError` is present, as we expect + // any errors to from configuring the backend itself. + diags = diags.Append(backDiags) + if backDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + + // If everything is ok with the core version check and backend/state_store initialization, + // show other errors from loading the full configuration tree. + diags = diags.Append(confDiags) + if confDiags.HasErrors() { + diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError))) + view.Diagnostics(diags) + return 1 + } var state *states.State @@ -212,64 +292,38 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { state = sMgr.State() } - if initArgs.Get { - modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, initArgs.TestsDirectory, rootModEarly, initArgs.Upgrade, view) - diags = diags.Append(modsDiags) - if modsAbort || modsDiags.HasErrors() { - view.Diagnostics(diags) - return 1 - } - if modsOutput { - header = true - } - } - - // With all of the modules (hopefully) installed, we can now try to load the - // whole configuration tree. - config, confDiags := c.loadConfigWithTests(path, initArgs.TestsDirectory) - // configDiags will be handled after the version constraint check, since an - // incorrect version of terraform may be producing errors for configuration - // constructs added in later versions. - - // Before we go further, we'll check to make sure none of the modules in - // the configuration declare that they don't support this Terraform - // version, so we can produce a version-related error message rather than - // potentially-confusing downstream errors. - versionDiags := terraform.CheckCoreVersionRequirements(config) - if versionDiags.HasErrors() { - view.Diagnostics(versionDiags) - return 1 - } - - // We've passed the core version check, now we can show errors from the - // configuration and backend initialisation. - - // Now, we can check the diagnostics from the early configuration and the - // backend. - diags = diags.Append(earlyConfDiags) - diags = diags.Append(backDiags) - if earlyConfDiags.HasErrors() { - diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError))) + // Now the resource state is loaded, we can download the providers specified in the state but not the configuration. + // This is step two of a two-step provider download process + stateProvidersOutput, stateLocks, stateProvidersDiags := c.getProvidersFromState(ctx, state, configLocks, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view) + diags = diags.Append(stateProvidersDiags) + if stateProvidersDiags.HasErrors() { view.Diagnostics(diags) return 1 } - - // Now, we can show any errors from initializing the backend, but we won't - // show the InitConfigError preamble as we didn't detect problems with - // the early configuration. - if backDiags.HasErrors() { - view.Diagnostics(diags) - return 1 + if stateProvidersOutput { + header = true + } + if header { + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + view.Output(views.EmptyMessage) } - // If everything is ok with the core version check and backend initialization, - // show other errors from loading the full configuration tree. - diags = diags.Append(confDiags) - if confDiags.HasErrors() { - diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError))) + // Now the two steps of provider download have happened, update the dependency lock file if it has changed. + lockFileOutput, lockFileDiags := c.saveDependencyLockFile(previousLocks, configLocks, stateLocks, initArgs.Lockfile, view) + diags = diags.Append(lockFileDiags) + if lockFileDiags.HasErrors() { view.Diagnostics(diags) return 1 } + if lockFileOutput { + header = true + } + if header { + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + view.Output(views.EmptyMessage) + } if cb, ok := back.(*cloud.Cloud); ok { if c.RunningInAutomation { @@ -281,23 +335,6 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { } } - // Now that we have loaded all modules, check the module tree for missing providers. - providersOutput, providersAbort, providerDiags := c.getProviders(ctx, config, state, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view) - diags = diags.Append(providerDiags) - if providersAbort || providerDiags.HasErrors() { - view.Diagnostics(diags) - return 1 - } - if providersOutput { - header = true - } - - // If we outputted information, then we need to output a newline - // so that our success message is nicely spaced out from prior text. - if header { - view.Output(views.EmptyMessage) - } - // If we accumulated any warnings along the way that weren't accompanied // by errors then we'll output them here so that the success message is // still the final thing shown. diff --git a/internal/command/init_run_experiment.go b/internal/command/init_run_experiment.go deleted file mode 100644 index da6b2e6419ec..000000000000 --- a/internal/command/init_run_experiment.go +++ /dev/null @@ -1,527 +0,0 @@ -// Copyright IBM Corp. 2014, 2026 -// SPDX-License-Identifier: BUSL-1.1 - -package command - -import ( - "context" - "errors" - "fmt" - "maps" - "slices" - "strings" - - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/terraform/internal/backend" - backendInit "github.com/hashicorp/terraform/internal/backend/init" - "github.com/hashicorp/terraform/internal/cloud" - "github.com/hashicorp/terraform/internal/command/arguments" - "github.com/hashicorp/terraform/internal/command/views" - "github.com/hashicorp/terraform/internal/configs" - "github.com/hashicorp/terraform/internal/depsfile" - "github.com/hashicorp/terraform/internal/didyoumean" - "github.com/hashicorp/terraform/internal/states" - "github.com/hashicorp/terraform/internal/terraform" - "github.com/hashicorp/terraform/internal/tfdiags" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" -) - -// `runPssInit` is an altered version of the logic in `run` that contains changes -// related to the PSS project. This is used by the (InitCommand.Run method only if Terraform has -// experimental features enabled. -func (c *InitCommand) runPssInit(initArgs *arguments.Init, view views.Init) int { - var diags tfdiags.Diagnostics - - c.forceInitCopy = initArgs.ForceInitCopy - c.Meta.stateLock = initArgs.StateLock - c.Meta.stateLockTimeout = initArgs.StateLockTimeout - c.reconfigure = initArgs.Reconfigure - c.migrateState = initArgs.MigrateState - c.Meta.ignoreRemoteVersion = initArgs.IgnoreRemoteVersion - c.Meta.input = initArgs.InputEnabled - c.Meta.targetFlags = initArgs.TargetFlags - c.Meta.compactWarnings = initArgs.CompactWarnings - - // Copying the state only happens during backend migration, so setting - // -force-copy implies -migrate-state - if c.forceInitCopy { - c.migrateState = true - } - - if len(initArgs.PluginPath) > 0 { - c.pluginPath = initArgs.PluginPath - } - - // Validate the arg count and get the working directory - path, err := ModulePath(initArgs.Args) - if err != nil { - diags = diags.Append(err) - view.Diagnostics(diags) - return 1 - } - - if err := c.storePluginPath(c.pluginPath); err != nil { - diags = diags.Append(fmt.Errorf("Error saving -plugin-dir to workspace directory: %s", err)) - view.Diagnostics(diags) - return 1 - } - - // Initialization can be aborted by interruption signals - ctx, done := c.InterruptibleContext(c.CommandContext()) - defer done() - - // This will track whether we outputted anything so that we know whether - // to output a newline before the success message - var header bool - - if initArgs.FromModule != "" { - src := initArgs.FromModule - - empty, err := configs.IsEmptyDir(path, initArgs.TestsDirectory) - if err != nil { - diags = diags.Append(fmt.Errorf("Error validating destination directory: %s", err)) - view.Diagnostics(diags) - return 1 - } - if !empty { - diags = diags.Append(errors.New(strings.TrimSpace(errInitCopyNotEmpty))) - view.Diagnostics(diags) - return 1 - } - - view.Output(views.CopyingConfigurationMessage, src) - header = true - - hooks := uiModuleInstallHooks{ - Ui: c.Ui, - ShowLocalPaths: false, // since they are in a weird location for init - View: view, - } - - ctx, span := tracer.Start(ctx, "-from-module=...", trace.WithAttributes( - attribute.String("module_source", src), - )) - - initDirFromModuleAbort, initDirFromModuleDiags := c.initDirFromModule(ctx, path, src, hooks) - diags = diags.Append(initDirFromModuleDiags) - if initDirFromModuleAbort || initDirFromModuleDiags.HasErrors() { - view.Diagnostics(diags) - span.SetStatus(codes.Error, "module installation failed") - span.End() - return 1 - } - span.End() - - view.Output(views.EmptyMessage) - } - - // If our directory is empty, then we're done. We can't get or set up - // the backend with an empty directory. - empty, err := configs.IsEmptyDir(path, initArgs.TestsDirectory) - if err != nil { - diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err)) - view.Diagnostics(diags) - return 1 - } - if empty { - view.Output(views.OutputInitEmptyMessage) - return 0 - } - - // Load just the root module to begin backend and module initialization - rootModEarly, earlyConfDiags := c.loadSingleModuleWithTests(path, initArgs.TestsDirectory) - - // There may be parsing errors in config loading but these will be shown later _after_ - // checking for core version requirement errors. Not meeting the version requirement should - // be the first error displayed if that is an issue, but other operations are required - // before being able to check core version requirements. - if rootModEarly == nil { - diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError)), earlyConfDiags) - view.Diagnostics(diags) - - return 1 - } - - if initArgs.Get { - modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, initArgs.TestsDirectory, rootModEarly, initArgs.Upgrade, view) - diags = diags.Append(modsDiags) - if modsAbort || modsDiags.HasErrors() { - view.Diagnostics(diags) - return 1 - } - if modsOutput { - header = true - } - } - - // With all of the modules (hopefully) installed, we can now try to load the - // whole configuration tree. - config, confDiags := c.loadConfigWithTests(path, initArgs.TestsDirectory) - // configDiags will be handled after: - // - the version constraint check has happened - // - and, the backend/state_store is initialised - - // Before we go further, we'll check to make sure none of the modules in - // the configuration declare that they don't support this Terraform - // version, so we can produce a version-related error message rather than - // potentially-confusing downstream errors. - versionDiags := terraform.CheckCoreVersionRequirements(config) - if versionDiags.HasErrors() { - view.Diagnostics(versionDiags) - return 1 - } - - // We've passed the core version check, now we can show errors from the early configuration. - // This prevents trying to initialise the backend with faulty configuration. - if earlyConfDiags.HasErrors() { - diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError)), earlyConfDiags) - view.Diagnostics(diags) - return 1 - } - - // Now the full configuration is loaded, we can download the providers specified in the configuration. - // This is step one of a two-step provider download process - // Providers may be downloaded by this code, but the dependency lock file is only updated later in `init` - // after step two of provider download is complete. - previousLocks, moreDiags := c.lockedDependencies() - diags = diags.Append(moreDiags) - - configProvidersOutput, configLocks, configProviderDiags := c.getProvidersFromConfig(ctx, config, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view) - diags = diags.Append(configProviderDiags) - if configProviderDiags.HasErrors() { - view.Diagnostics(diags) - return 1 - } - if configProvidersOutput { - header = true - } - - // If we outputted information, then we need to output a newline - // so that our success message is nicely spaced out from prior text. - if header { - view.Output(views.EmptyMessage) - } - - var back backend.Backend - - var backDiags tfdiags.Diagnostics - var backendOutput bool - switch { - case initArgs.Cloud && rootModEarly.CloudConfig != nil: - back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) - case initArgs.Backend: - back, backendOutput, backDiags = c.initPssBackend(ctx, rootModEarly, initArgs, configLocks, view) - default: - // load the previously-stored backend config - back, backDiags = c.Meta.backendFromState(ctx) - } - if backendOutput { - header = true - } - if header { - // If we outputted information, then we need to output a newline - // so that our success message is nicely spaced out from prior text. - view.Output(views.EmptyMessage) - } - - // Show any errors from initializing the backend. - // No preamble using `InitConfigError` is present, as we expect - // any errors to from configuring the backend itself. - diags = diags.Append(backDiags) - if backDiags.HasErrors() { - view.Diagnostics(diags) - return 1 - } - - // If everything is ok with the core version check and backend/state_store initialization, - // show other errors from loading the full configuration tree. - diags = diags.Append(confDiags) - if confDiags.HasErrors() { - diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError))) - view.Diagnostics(diags) - return 1 - } - - var state *states.State - - // If we have a functional backend (either just initialized or initialized - // on a previous run) we'll use the current state as a potential source - // of provider dependencies. - if back != nil { - c.ignoreRemoteVersionConflict(back) - workspace, err := c.Workspace() - if err != nil { - diags = diags.Append(fmt.Errorf("Error selecting workspace: %s", err)) - view.Diagnostics(diags) - return 1 - } - sMgr, sDiags := back.StateMgr(workspace) - if sDiags.HasErrors() { - diags = diags.Append(fmt.Errorf("Error loading state: %s", sDiags.Err())) - view.Diagnostics(diags) - return 1 - } - - if err := sMgr.RefreshState(); err != nil { - diags = diags.Append(fmt.Errorf("Error refreshing state: %s", err)) - view.Diagnostics(diags) - return 1 - } - - state = sMgr.State() - } - - // Now the resource state is loaded, we can download the providers specified in the state but not the configuration. - // This is step two of a two-step provider download process - stateProvidersOutput, stateLocks, stateProvidersDiags := c.getProvidersFromState(ctx, state, configLocks, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view) - diags = diags.Append(configProviderDiags) - if stateProvidersDiags.HasErrors() { - view.Diagnostics(diags) - return 1 - } - if stateProvidersOutput { - header = true - } - if header { - // If we outputted information, then we need to output a newline - // so that our success message is nicely spaced out from prior text. - view.Output(views.EmptyMessage) - } - - // Now the two steps of provider download have happened, update the dependency lock file if it has changed. - lockFileOutput, lockFileDiags := c.saveDependencyLockFile(previousLocks, configLocks, stateLocks, initArgs.Lockfile, view) - diags = diags.Append(lockFileDiags) - if lockFileDiags.HasErrors() { - view.Diagnostics(diags) - return 1 - } - if lockFileOutput { - header = true - } - if header { - // If we outputted information, then we need to output a newline - // so that our success message is nicely spaced out from prior text. - view.Output(views.EmptyMessage) - } - - if cb, ok := back.(*cloud.Cloud); ok { - if c.RunningInAutomation { - if err := cb.AssertImportCompatible(config); err != nil { - diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Compatibility error", err.Error())) - view.Diagnostics(diags) - return 1 - } - } - } - - // If we accumulated any warnings along the way that weren't accompanied - // by errors then we'll output them here so that the success message is - // still the final thing shown. - view.Diagnostics(diags) - _, cloud := back.(*cloud.Cloud) - output := views.OutputInitSuccessMessage - if cloud { - output = views.OutputInitSuccessCloudMessage - } - - view.Output(output) - - if !c.RunningInAutomation { - // If we're not running in an automation wrapper, give the user - // some more detailed next steps that are appropriate for interactive - // shell usage. - output = views.OutputInitSuccessCLIMessage - if cloud { - output = views.OutputInitSuccessCLICloudMessage - } - view.Output(output) - } - return 0 -} - -func (c *InitCommand) initPssBackend(ctx context.Context, root *configs.Module, initArgs *arguments.Init, configLocks *depsfile.Locks, 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 maintenance hazard of having the wrong ctx in scope here - defer span.End() - - 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 - factory, fDiags := c.Meta.StateStoreProviderFactoryFromConfig(root.StateStore, configLocks) - diags = diags.Append(fDiags) - if fDiags.HasErrors() { - return nil, true, diags - } - - // If overrides supplied by -backend-config CLI flag, process them - var configOverride hcl.Body - if !initArgs.BackendConfig.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() - - 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(initArgs.BackendConfig, stateStoreSchema.Body) - diags = diags.Append(overrideDiags) - if overrideDiags.HasErrors() { - return nil, true, diags - } - } - - opts = &BackendOpts{ - StateStoreConfig: root.StateStore, - Locks: configLocks, - CreateDefaultWorkspace: initArgs.CreateDefaultWorkspace, - ConfigOverride: configOverride, - Init: true, - ViewType: initArgs.ViewType, - } - - case root.Backend != nil: - // backend config present - backendType := root.Backend.Type - if backendType == "cloud" { - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unsupported backend type", - Detail: fmt.Sprintf("There is no explicit backend type named %q. To configure HCP Terraform, declare a 'cloud' block instead.", backendType), - Subject: &root.Backend.TypeRange, - }) - return nil, true, diags - } - - bf := backendInit.Backend(backendType) - if bf == nil { - detail := fmt.Sprintf("There is no backend type named %q.", backendType) - if msg, removed := backendInit.RemovedBackends[backendType]; removed { - detail = msg - } - - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unsupported backend type", - Detail: detail, - Subject: &root.Backend.TypeRange, - }) - return nil, true, diags - } - - b := bf() - backendSchema := b.ConfigSchema() - backendConfig := root.Backend - - // If overrides supplied by -backend-config CLI flag, process them - var configOverride hcl.Body - if !initArgs.BackendConfig.Empty() { - var overrideDiags tfdiags.Diagnostics - configOverride, overrideDiags = c.backendConfigOverrideBody(initArgs.BackendConfig, backendSchema) - diags = diags.Append(overrideDiags) - if overrideDiags.HasErrors() { - return nil, true, diags - } - } - - opts = &BackendOpts{ - BackendConfig: backendConfig, - Locks: configLocks, - ConfigOverride: configOverride, - Init: true, - ViewType: initArgs.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. - if !initArgs.BackendConfig.Empty() { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Warning, - "Missing backend configuration", - `-backend-config was used without a "backend" block in the configuration. - -If you intended to override the default local backend configuration, -no action is required, but you may add an explicit backend block to your -configuration to clear this warning: - -terraform { - backend "local" {} -} - -However, if you intended to override a defined backend, please verify that -the backend configuration is present and valid. -`, - )) - } - - opts = &BackendOpts{ - Init: true, - Locks: configLocks, - ViewType: initArgs.ViewType, - } - } - - back, backDiags := c.Backend(opts) - diags = diags.Append(backDiags) - return back, true, diags -} From 4161b8cb6f2314a87af88bf2d810065f8e716d4a Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 19:54:35 +0000 Subject: [PATCH 2/8] command/init: Do early backend validation before full init This enables us to present most of the useful diagnostics relating to the backend block together with other configuration errors, rather than making the user do two cycles. Consequently this alings with existing tests which would otherwise fail (or need to be amended) if we didn't do this. --- internal/backend/init/init.go | 7 +++ internal/command/init.go | 104 ++++++++++++++++++---------------- internal/command/init_run.go | 5 +- 3 files changed, 67 insertions(+), 49 deletions(-) diff --git a/internal/backend/init/init.go b/internal/backend/init/init.go index 24f4c5787bea..5b1549edeaf5 100644 --- a/internal/backend/init/init.go +++ b/internal/backend/init/init.go @@ -93,6 +93,13 @@ func Backend(name string) backend.InitFn { return backends[name] } +func BackendExists(name string) bool { + backendsLock.Lock() + defer backendsLock.Unlock() + _, ok := backends[name] + return ok +} + // Set sets a new backend in the list of backends. If f is nil then the // backend will be removed from the map. If this backend already exists // then it will be overwritten. diff --git a/internal/command/init.go b/internal/command/init.go index 4fb09300cbc6..824faa20ec9d 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -158,19 +158,6 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ini 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 factory, fDiags := c.Meta.StateStoreProviderFactoryFromConfig(root.StateStore, configLocks) @@ -243,32 +230,7 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ini case root.Backend != nil: // backend config present backendType := root.Backend.Type - if backendType == "cloud" { - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unsupported backend type", - Detail: fmt.Sprintf("There is no explicit backend type named %q. To configure HCP Terraform, declare a 'cloud' block instead.", backendType), - Subject: &root.Backend.TypeRange, - }) - return nil, true, diags - } - bf := backendInit.Backend(backendType) - if bf == nil { - detail := fmt.Sprintf("There is no backend type named %q.", backendType) - if msg, removed := backendInit.RemovedBackends[backendType]; removed { - detail = msg - } - - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Unsupported backend type", - Detail: detail, - Subject: &root.Backend.TypeRange, - }) - return nil, true, diags - } - b := bf() backendSchema := b.ConfigSchema() backendConfig := root.Backend @@ -292,6 +254,61 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ini ViewType: initArgs.ViewType, } + default: + // No config; defaults to local state storage + opts = &BackendOpts{ + Init: true, + Locks: configLocks, + ViewType: initArgs.ViewType, + } + } + + back, backDiags := c.Backend(opts) + diags = diags.Append(backDiags) + return back, true, diags +} + +func (c *InitCommand) earlyValidateBackend(root *configs.Module, initArgs *arguments.Init) (diags tfdiags.Diagnostics) { + switch { + case root.StateStore != nil && root.Backend != nil: + 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 diags + case root.StateStore != nil: + // validation requires the provider to be installed so cannot be done early + case root.Backend != nil: + backendType := root.Backend.Type + if backendType == "cloud" { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unsupported backend type", + Detail: fmt.Sprintf("There is no explicit backend type named %q. To configure HCP Terraform, declare a 'cloud' block instead.", backendType), + Subject: &root.Backend.TypeRange, + }) + return diags + } + + if !backendInit.BackendExists(backendType) { + detail := fmt.Sprintf("There is no backend type named %q.", backendType) + if msg, removed := backendInit.RemovedBackends[backendType]; removed { + detail = msg + } + + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unsupported backend type", + Detail: detail, + Subject: &root.Backend.TypeRange, + }) + return diags + } default: // No config; defaults to local state storage @@ -317,17 +334,8 @@ the backend configuration is present and valid. `, )) } - - opts = &BackendOpts{ - Init: true, - Locks: configLocks, - ViewType: initArgs.ViewType, - } } - - back, backDiags := c.Backend(opts) - diags = diags.Append(backDiags) - return back, true, diags + return diags } // getProvidersFromConfig determines what providers are required by the given configuration data. diff --git a/internal/command/init_run.go b/internal/command/init_run.go index d86e9c1ea5e3..d63013f94eda 100644 --- a/internal/command/init_run.go +++ b/internal/command/init_run.go @@ -192,9 +192,12 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { return 1 } + earlyBdiags := c.earlyValidateBackend(rootModEarly, initArgs) + diags = diags.Append(earlyBdiags) + // We've passed the core version check, now we can show errors from the early configuration. // This prevents trying to initialise the backend with faulty configuration. - if earlyConfDiags.HasErrors() { + if earlyConfDiags.HasErrors() || earlyBdiags.HasErrors() { diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError)), earlyConfDiags) view.Diagnostics(diags) return 1 From 5d570ddc09f4b4a26c69502a04ffb1f40b387c33 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 20:42:59 +0000 Subject: [PATCH 3/8] fix: Ensure diagnostics can be appended during installation --- internal/command/init.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index 824faa20ec9d..f8a56dc403f9 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -396,7 +396,7 @@ func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *config log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs) } - evts := c.prepareInstallerEvents(ctx, reqs, diags, inst, view, views.InitializingProviderPluginFromConfigMessage, views.ReusingPreviousVersionInfo) + evts := c.prepareInstallerEvents(ctx, reqs, &diags, inst, view, views.InitializingProviderPluginFromConfigMessage, views.ReusingPreviousVersionInfo) ctx = evts.OnContext(ctx) mode := providercache.InstallNewProvidersOnly @@ -513,7 +513,7 @@ func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.S // things relatively concise. Later it'd be nice to have a progress UI // where statuses update in-place, but we can't do that as long as we // are shimming our vt100 output to the legacy console API on Windows. - evts := c.prepareInstallerEvents(ctx, reqs, diags, inst, view, views.InitializingProviderPluginFromStateMessage, views.ReusingVersionIdentifiedFromConfig) + evts := c.prepareInstallerEvents(ctx, reqs, &diags, inst, view, views.InitializingProviderPluginFromStateMessage, views.ReusingVersionIdentifiedFromConfig) ctx = evts.OnContext(ctx) mode := providercache.InstallNewProvidersOnly @@ -617,7 +617,7 @@ func (c *InitCommand) saveDependencyLockFile(previousLocks, configLocks, stateLo // prepareInstallerEvents returns an instance of *providercache.InstallerEvents. This struct defines callback functions that will be executed // when a specific type of event occurs during provider installation. // The calling code needs to provide a tfdiags.Diagnostics collection, so that provider installation code returns diags to the calling code using closures -func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerreqs.Requirements, diags tfdiags.Diagnostics, inst *providercache.Installer, view views.Init, initMsg views.InitMessageCode, reuseMsg views.InitMessageCode) *providercache.InstallerEvents { +func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerreqs.Requirements, diags *tfdiags.Diagnostics, inst *providercache.Installer, view views.Init, initMsg views.InitMessageCode, reuseMsg views.InitMessageCode) *providercache.InstallerEvents { // Because we're currently just streaming a series of events sequentially // into the terminal, we're showing only a subset of the events to keep @@ -635,7 +635,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr view.LogInitMessage(views.BuiltInProviderAvailableMessage, provider.ForDisplay()) }, BuiltInProviderFailure: func(provider addrs.Provider, err error) { - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Invalid dependency on built-in provider", fmt.Sprintf("Cannot use %s: %s.", provider.ForDisplay(), err), @@ -666,7 +666,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr for i, source := range sources { displaySources[i] = fmt.Sprintf(" - %s", source) } - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to query available provider packages", fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s\n\n%s", @@ -685,7 +685,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr ) } - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to query available provider packages", fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s", @@ -703,7 +703,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr // provider registry, to allow for the (admittedly currently // rather unlikely) possibility that github.com starts being // a real Terraform provider registry in the future. - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Invalid provider registry host", fmt.Sprintf("The given source address %q specifies a GitHub repository rather than a Terraform provider. Refer to the documentation of the provider to find the correct source address to use.", @@ -712,7 +712,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr )) case errorTy.HasOtherVersion: - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Invalid provider registry host", fmt.Sprintf("The host %q given in provider source address %q does not offer a Terraform provider registry that is compatible with this Terraform version, but it may be compatible with a different Terraform version.", @@ -721,7 +721,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr )) default: - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Invalid provider registry host", fmt.Sprintf("The host %q given in provider source address %q does not offer a Terraform provider registry.", @@ -737,7 +737,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr default: suggestion := fmt.Sprintf("\n\nTo see which modules are currently depending on %s and what versions are specified, run the following command:\n terraform providers", provider.ForDisplay()) - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to query available provider packages", fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s", @@ -753,7 +753,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr displayWarnings[i] = fmt.Sprintf("- %s", warning) } - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Warning, "Additional provider information from registry", fmt.Sprintf("The remote registry returned warnings for %s:\n%s", @@ -763,7 +763,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr )) }, LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) { - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to install provider from shared cache", fmt.Sprintf("Error while importing %s v%s from the shared cache directory: %s.", provider.ForDisplay(), version, err), @@ -776,13 +776,13 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr closestAvailable := err.Suggestion switch { case closestAvailable == getproviders.UnspecifiedVersion: - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, summaryIncompatible, fmt.Sprintf(errProviderVersionIncompatible, provider.String()), )) case version.GreaterThan(closestAvailable): - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, summaryIncompatible, fmt.Sprintf(providerProtocolTooNew, provider.ForDisplay(), @@ -791,7 +791,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr ), )) default: // version is less than closestAvailable - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, summaryIncompatible, fmt.Sprintf(providerProtocolTooOld, provider.ForDisplay(), @@ -806,7 +806,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr // If we're installing from a mirror then it may just be // the mirror lacking the package, rather than it being // unavailable from upstream. - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, summaryIncompatible, fmt.Sprintf( @@ -816,7 +816,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr ), )) default: - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, summaryIncompatible, fmt.Sprintf( @@ -841,7 +841,7 @@ func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerr // as a cancellation after the installer returns and do the // normal cancellation handling. - diags = diags.Append(tfdiags.Sourceless( + *diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to install provider", fmt.Sprintf("Error while installing %s v%s: %s", provider.ForDisplay(), version, err), From 55a706060ddaecaaae4c35fc11a716a077413acd Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 19:53:22 +0000 Subject: [PATCH 4/8] changes: Add changelog entry --- .changes/v1.15/NOTES-20260303-115443.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/v1.15/NOTES-20260303-115443.yaml diff --git a/.changes/v1.15/NOTES-20260303-115443.yaml b/.changes/v1.15/NOTES-20260303-115443.yaml new file mode 100644 index 000000000000..2b8be19f294f --- /dev/null +++ b/.changes/v1.15/NOTES-20260303-115443.yaml @@ -0,0 +1,5 @@ +kind: NOTES +body: 'command/init: Provider installation was refactored to enable future enhancements in the area. This results in different order of operations during init and 2 new log messages replacing one (`initializing_provider_plugin_message`). The change should not have any end-user impact aside from the `init` command output.' +time: 2026-03-03T11:54:43.732353Z +custom: + Issue: "38227" From d8ad52f545305b77a52a75769ae97241a35a7bd5 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 19:53:08 +0000 Subject: [PATCH 5/8] command/init: Update tests to reflect 2-stage provider download --- .../command/testdata/init-get/output.jsonlog | 4 ++-- internal/command/views/init_test.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/command/testdata/init-get/output.jsonlog b/internal/command/testdata/init-get/output.jsonlog index 88acf532fd07..1fe0bb396eb3 100644 --- a/internal/command/testdata/init-get/output.jsonlog +++ b/internal/command/testdata/init-get/output.jsonlog @@ -1,7 +1,7 @@ {"@level":"info","@message":"Terraform 1.9.0-dev","@module":"terraform.ui","terraform":"1.9.0-dev","type":"version","ui":"1.2"} -{"@level":"info","@message":"Initializing the backend...","@module":"terraform.ui","message_code": "initializing_backend_message","type":"init_output"} {"@level":"info","@message":"Initializing modules...","@module":"terraform.ui","message_code": "initializing_modules_message","type":"init_output"} {"@level":"info","@message":"- foo in foo","@module":"terraform.ui","type":"log"} -{"@level":"info","@message":"Initializing provider plugins...","@module":"terraform.ui","message_code": "initializing_provider_plugin_message","type":"init_output"} +{"@level":"info","@message":"Initializing provider plugins found in the configuration...","@module":"terraform.ui","message_code": "initializing_provider_plugin_from_config_message","type":"init_output"} +{"@level":"info","@message":"Initializing the backend...","@module":"terraform.ui","message_code": "initializing_backend_message","type":"init_output"} {"@level":"info","@message":"Terraform has been successfully initialized!","@module":"terraform.ui","message_code": "output_init_success_message","type":"init_output"} {"@level":"info","@message":"You may now begin working with Terraform. Try running \"terraform plan\" to see\nany changes that are required for your infrastructure. All Terraform commands\nshould now work.\n\nIf you ever set or change modules or backend configuration for Terraform,\nrerun this command to reinitialize your working directory. If you forget, other\ncommands will detect it and remind you to do so if necessary.","@module":"terraform.ui","message_code": "output_init_success_cli_message","type":"init_output"} diff --git a/internal/command/views/init_test.go b/internal/command/views/init_test.go index 4382564f8a59..13847a5068d5 100644 --- a/internal/command/views/init_test.go +++ b/internal/command/views/init_test.go @@ -129,10 +129,10 @@ func TestNewInit_jsonViewOutput(t *testing.T) { t.Fatalf("unexpected return type %t", newInit) } - newInit.Output(InitializingProviderPluginMessage) + newInit.Output(InitializingProviderPluginFromConfigMessage) version := tfversion.String() - want := []map[string]interface{}{ + want := []map[string]any{ { "@level": "info", "@message": fmt.Sprintf("Terraform %s", version), @@ -143,8 +143,8 @@ func TestNewInit_jsonViewOutput(t *testing.T) { }, { "@level": "info", - "@message": "Initializing provider plugins...", - "message_code": "initializing_provider_plugin_message", + "@message": "Initializing provider plugins found in the configuration...", + "message_code": "initializing_provider_plugin_from_config_message", "@module": "terraform.ui", "type": "init_output", }, @@ -231,7 +231,7 @@ func TestNewInit_jsonViewLog(t *testing.T) { t.Fatalf("unexpected return type %t", newInit) } - newInit.LogInitMessage(InitializingProviderPluginMessage) + newInit.LogInitMessage(InitializingProviderPluginFromConfigMessage) version := tfversion.String() want := []map[string]interface{}{ @@ -245,7 +245,7 @@ func TestNewInit_jsonViewLog(t *testing.T) { }, { "@level": "info", - "@message": "Initializing provider plugins...", + "@message": "Initializing provider plugins found in the configuration...", "@module": "terraform.ui", "type": "log", }, @@ -282,10 +282,10 @@ func TestNewInit_humanViewOutput(t *testing.T) { t.Fatalf("unexpected return type %t", newInit) } - newInit.Output(InitializingProviderPluginMessage) + newInit.Output(InitializingProviderPluginFromConfigMessage) actual := done(t).All() - expected := "Initializing provider plugins..." + expected := "Initializing provider plugins found in the configuration..." if !strings.Contains(actual, expected) { t.Fatalf("expected output to contain: %s, but got %s", expected, actual) } From f4371d94ca2232c9f5ebc076aa07a54dd52c7b6b Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 20:03:34 +0000 Subject: [PATCH 6/8] command/views/init: Reflect PSS related message codes are public --- internal/command/views/init.go | 42 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/internal/command/views/init.go b/internal/command/views/init.go index f051425d4897..4efe716cd373 100644 --- a/internal/command/views/init.go +++ b/internal/command/views/init.go @@ -331,32 +331,28 @@ const ( // Following message codes are used and documented EXTERNALLY // Keep docs/internals/machine-readable-ui.mdx up to date with // this list when making changes here. - CopyingConfigurationMessage InitMessageCode = "copying_configuration_message" - EmptyMessage InitMessageCode = "empty_message" - OutputInitEmptyMessage InitMessageCode = "output_init_empty_message" - OutputInitSuccessMessage InitMessageCode = "output_init_success_message" - OutputInitSuccessCloudMessage InitMessageCode = "output_init_success_cloud_message" - OutputInitSuccessCLIMessage InitMessageCode = "output_init_success_cli_message" - OutputInitSuccessCLICloudMessage InitMessageCode = "output_init_success_cli_cloud_message" - UpgradingModulesMessage InitMessageCode = "upgrading_modules_message" - InitializingTerraformCloudMessage InitMessageCode = "initializing_terraform_cloud_message" - InitializingModulesMessage InitMessageCode = "initializing_modules_message" - InitializingBackendMessage InitMessageCode = "initializing_backend_message" - InitializingStateStoreMessage InitMessageCode = "initializing_state_store_message" - DefaultWorkspaceCreatedMessage InitMessageCode = "default_workspace_created_message" - InitializingProviderPluginMessage InitMessageCode = "initializing_provider_plugin_message" - LockInfo InitMessageCode = "lock_info" - DependenciesLockChangesInfo InitMessageCode = "dependencies_lock_changes_info" + CopyingConfigurationMessage InitMessageCode = "copying_configuration_message" + EmptyMessage InitMessageCode = "empty_message" + OutputInitEmptyMessage InitMessageCode = "output_init_empty_message" + OutputInitSuccessMessage InitMessageCode = "output_init_success_message" + OutputInitSuccessCloudMessage InitMessageCode = "output_init_success_cloud_message" + OutputInitSuccessCLIMessage InitMessageCode = "output_init_success_cli_message" + OutputInitSuccessCLICloudMessage InitMessageCode = "output_init_success_cli_cloud_message" + UpgradingModulesMessage InitMessageCode = "upgrading_modules_message" + 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" + InitializingProviderPluginFromConfigMessage InitMessageCode = "initializing_provider_plugin_from_config_message" + InitializingProviderPluginFromStateMessage InitMessageCode = "initializing_provider_plugin_from_state_message" + ReusingVersionIdentifiedFromConfig InitMessageCode = "reusing_version_during_state_provider_init" + DefaultWorkspaceCreatedMessage InitMessageCode = "default_workspace_created_message" + LockInfo InitMessageCode = "lock_info" + DependenciesLockChangesInfo InitMessageCode = "dependencies_lock_changes_info" //// Message codes below are ONLY used INTERNALLY (for now) - // InitializingProviderPluginFromConfigMessage indicates the beginning of installing of providers described in configuration - InitializingProviderPluginFromConfigMessage InitMessageCode = "initializing_provider_plugin_from_config_message" - // InitializingProviderPluginFromStateMessage indicates the beginning of installing of providers described in state - InitializingProviderPluginFromStateMessage InitMessageCode = "initializing_provider_plugin_from_state_message" - // DependenciesLockPendingChangesInfo indicates when a provider installation step will reuse a provider from a previous installation step in the current operation - ReusingVersionIdentifiedFromConfig InitMessageCode = "reusing_version_during_state_provider_init" - // InitConfigError indicates problems encountered during initialisation InitConfigError InitMessageCode = "init_config_error" // BackendConfiguredSuccessMessage indicates successful backend configuration From 4ba8f025adda42859e4145e81598764a5c3a2bf8 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 21:02:46 +0000 Subject: [PATCH 7/8] command/views/init: Remove (now unused) initializing_provider_plugin_message message code --- internal/command/views/init.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/command/views/init.go b/internal/command/views/init.go index 4efe716cd373..9efa4394465d 100644 --- a/internal/command/views/init.go +++ b/internal/command/views/init.go @@ -343,7 +343,6 @@ const ( InitializingModulesMessage InitMessageCode = "initializing_modules_message" InitializingBackendMessage InitMessageCode = "initializing_backend_message" InitializingStateStoreMessage InitMessageCode = "initializing_state_store_message" - InitializingProviderPluginMessage InitMessageCode = "initializing_provider_plugin_message" InitializingProviderPluginFromConfigMessage InitMessageCode = "initializing_provider_plugin_from_config_message" InitializingProviderPluginFromStateMessage InitMessageCode = "initializing_provider_plugin_from_state_message" ReusingVersionIdentifiedFromConfig InitMessageCode = "reusing_version_during_state_provider_init" From 6f2622abf06429f92985020b10324925415efd2a Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2026 20:00:31 +0000 Subject: [PATCH 8/8] command/views/init: Fix typo in message --- internal/command/views/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/views/init.go b/internal/command/views/init.go index 9efa4394465d..1789c9b64578 100644 --- a/internal/command/views/init.go +++ b/internal/command/views/init.go @@ -225,7 +225,7 @@ var MessageRegistry map[InitMessageCode]InitMessage = map[InitMessageCode]InitMe }, "reusing_version_during_state_provider_init": { HumanValue: "- Reusing previous version of %s", - JSONValue: "%s: Reusing previous version of %s", + JSONValue: "Reusing previous version of %s", }, "finding_matching_version_message": { HumanValue: "- Finding %s versions matching %q...",