wip: use installer hooks to detect and prevent upgrade of PSS provider.

This work is blocked by the branch it's based on.
refactor-pss-provider-install-blocks
Sarah French 1 week ago
commit 2d3c444d8f

@ -392,7 +392,7 @@ const (
// The dependency lock file itself isn't updated here.
//
// Calling code is responsible for validating inputs to this method, e.g. mutually exclusive flags.
func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *configs.Config, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init, installerHook *providerPolicyHook) (output bool, resultingLocks *depsfile.Locks, safeInitAction SafeInitAction, authResult *getproviders.PackageAuthenticationResult, diags tfdiags.Diagnostics) {
func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *configs.Config, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init, installerHook ...providercache.InstallerHook) (output bool, resultingLocks *depsfile.Locks, safeInitAction SafeInitAction, authResult *getproviders.PackageAuthenticationResult, diags tfdiags.Diagnostics) {
if config == nil {
return false, nil, SafeInitActionNotRelevant, nil, diags
}
@ -530,7 +530,7 @@ func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *config
// Determine which required providers are already downloaded, and download any
// new providers or newer versions of providers
configLocks, installErr := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode, installerHook)
configLocks, installErr := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode, installerHook...)
if ctx.Err() == context.Canceled {
diags = diags.Append(fmt.Errorf("Provider installation was canceled by an interrupt signal."))
view.Diagnostics(diags) // TODO: Why is the output viewed here?
@ -589,7 +589,7 @@ func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *config
// The calling code is assumed to have already called getProvidersFromConfig, which is used to
// supply the configLocks argument.
// The dependency lock file itself isn't updated here.
func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.State, configReqs providerreqs.Requirements, configLocks *depsfile.Locks, pluginDirs []string, view views.Init, installerHook *providerPolicyHook) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) {
func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.State, configReqs providerreqs.Requirements, configLocks *depsfile.Locks, pluginDirs []string, view views.Init, installerHooks ...providercache.InstallerHook) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) {
ctx, span := tracer.Start(ctx, "install providers from state")
defer span.End()
@ -703,7 +703,7 @@ func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.S
// would remove the effects of version constraints from the config.
// > Any validation of CLI flag usage is already done in getProvidersFromConfig
newLocks, err := inst.EnsureProviderVersions(ctx, inProgressLocks, reqs, mode, installerHook)
newLocks, err := inst.EnsureProviderVersions(ctx, inProgressLocks, reqs, mode, installerHooks...)
if ctx.Err() == context.Canceled {
diags = diags.Append(fmt.Errorf("Provider installation was canceled by an interrupt signal."))
view.Diagnostics(diags)

@ -18,8 +18,10 @@ import (
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/policy"
"github.com/hashicorp/terraform/internal/providercache"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -242,6 +244,8 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int {
previousLocks, moreDiags := c.lockedDependencies()
diags = diags.Append(moreDiags)
installerHooks := []providercache.InstallerHook{}
reqsByModule, reqDiags := config.ProviderRequirementsByModule()
if reqDiags.HasErrors() {
view.Diagnostics(diags.Append(reqDiags))
@ -254,8 +258,25 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int {
policyResults: policyResults,
config: config,
}
installerHooks = append(installerHooks, providerHook)
if config != nil && config.Module != nil && config.Module.StateStore != nil {
lock := previousLocks.Provider(config.Module.StateStore.ProviderAddr)
var priorVersion *providerreqs.Version
if lock != nil {
v := lock.Version()
priorVersion = &v
}
stateStorageHook := &stateStorageProviderInstallHook{
provider: config.Module.StateStore.ProviderAddr,
priorVersion: priorVersion,
supplyMode: config.Module.StateStore.ProviderSupplyMode,
reconfigure: initArgs.Reconfigure,
}
installerHooks = append(installerHooks, stateStorageHook)
}
configProvidersOutput, configLocks, safeInitAction, stateStoreProviderAuthResult, configProviderDiags := c.getProvidersFromConfig(ctx, config, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view, providerHook)
configProvidersOutput, configLocks, safeInitAction, stateStoreProviderAuthResult, configProviderDiags := c.getProvidersFromConfig(ctx, config, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view, installerHooks...)
diags = diags.Append(configProviderDiags)
if configProviderDiags.HasErrors() {
view.PolicyResults(policyResults)
@ -286,35 +307,6 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int {
panic(fmt.Sprintf("When installing providers described in the config Terraform couldn't determine what 'safe init' action should be taken and returned action type %T. This is a bug in Terraform and should be reported.", safeInitAction))
}
// The init command is not allowed to upgrade the provider used for state storage (unless we're reconfiguring the state store).
// Unless users choose to reconfigure, they must upgrade the state store provider separately using `terraform state migrate -upgrade`.
if initArgs.Upgrade && !initArgs.Reconfigure && config.Module.StateStore != nil {
pAddr := config.Module.StateStore.ProviderAddr
old := previousLocks.Provider(pAddr)
new := configLocks.Provider(pAddr)
if old == nil || new == nil {
panic(fmt.Sprintf(`Unexpected missing provider lock for %s during init -upgrade:
prior lock: %#v
new lock: %#v`, pAddr.ForDisplay(), old, new))
}
if !new.Version().Same((old.Version())) {
// The upgrade has impacted the provider
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot upgrade the provider used for state storage during \"terraform init -upgrade\"",
fmt.Sprintf(`While upgrading providers Terraform attempted to upgrade the %s (%q) provider, which is used by the state_store block in your configuration.
Please use \"terraform state migrate -upgrade\" to upgrade the state store provider and navigate migrating your state between the two versions. You can then re-attempt \"terraform init -upgrade\" to upgrade the rest of your providers.
If you do not intend to upgrade the state store provider, please update your configuration to pin to the current version (%s), and re-run \"terraform init -upgrade\" to upgrade the rest of your providers.
`,
pAddr.Type, pAddr.ForDisplay(), old.Version()),
),
)
view.Diagnostics(diags)
return 1
}
}
// 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 {

@ -2566,18 +2566,12 @@ terraform {
}
// Assert that no providers were upgraded.
//
// However, "test" v9.9.9 would be installed in the cache, because the error occurs after the upgrade
// process identifies that provider as a candidate for upgrade.
// Also, no packages were downloaded.
cacheDir := m.providerLocalCacheDir()
gotPackages := cacheDir.AllAvailablePackages()
wantPackages := map[addrs.Provider][]providercache.CachedProvider{
addrs.NewDefaultProvider("test"): {
{
Provider: addrs.NewDefaultProvider("test"),
Version: getproviders.MustParseVersion("9.9.9"),
PackageDir: expectedPackageInstallPath("test", "9.9.9", false),
},
// no 9.9.9 entry
{
Provider: addrs.NewDefaultProvider("test"),
Version: getproviders.MustParseVersion("1.2.3"),

@ -0,0 +1,59 @@
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package command
import (
"context"
"fmt"
tfaddr "github.com/hashicorp/terraform-registry-address"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
"github.com/hashicorp/terraform/internal/providercache"
)
var _ providercache.InstallerHook = stateStorageProviderInstallHook{}
type stateStorageProviderInstallHook struct {
provider tfaddr.Provider
priorVersion *providerreqs.Version
supplyMode getproviders.ProviderSupplyMode
reconfigure bool
}
func (h stateStorageProviderInstallHook) ProviderVersionSelected(ctx context.Context, provider addrs.Provider, version string) error {
if !provider.Equals(h.provider) {
return nil // irrelevant
}
if h.priorVersion == nil {
return nil // not an upgrade, install for first time
}
// if h.supplyMode != getproviders.ManagedByTerraform {
// return nil // not managed by Terraform, so upgrades won't change this provider
// }
if h.reconfigure {
return nil // user has opted out of state migration so no error
}
v := providerreqs.MustParseVersion(version)
if v.Same(*h.priorVersion) {
return nil // not an upgrade, same version selected
}
return fmt.Errorf(`Cannot upgrade the provider used for state storage during "terraform init -upgrade".
While upgrading providers Terraform attempted to upgrade the %s (%q) provider, which is used by the state_store block in your configuration.
Please use "terraform state migrate -upgrade" to upgrade the state store provider and navigate migrating your state between the two versions. You can then re-attempt "terraform init -upgrade" to upgrade the rest of your providers.
If you do not intend to upgrade the state store provider, please update your configuration to pin to the current version (%s), and re-run "terraform init -upgrade" to upgrade the rest of your providers.
`,
provider.Type,
provider.ForDisplay(),
h.priorVersion,
)
}

@ -350,7 +350,6 @@ NeedProvider:
// We do this before checking the lock file, so that we also
// evaluate policy for providers that are already installed.
err := hook.ProviderVersionSelected(ctx, provider, version.String())
// return a generic error here that the init command returns to the CLI.
// The detailed policy diagnostics are included in the policy results
// and will be formatted in the CLI output.

Loading…
Cancel
Save