mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-19 02:39:17 -05:00
* Add forked version of `run` logic that's only used if experiments are enabled * Reorder actions in experimental init - load in full config before configuring the backend. * Add getProvidersFromConfig method, initially as an exact copy of getProviders * Make getProvidersFromConfig not use state to get providers * Add `appendLockedDependencies` method to `Meta` to allow multi-phase saving to the dep locks file * Update experimental init to use new getProvidersFromConfig method * Add new getProvidersFromState method that only accepts state information as input for getting providers. Use in experimental init and append values to existing deps lock file * Update messages sent to view about provider download phases * Change init to save updates to the deps lock file only once * Make Terraform output report that a lock file _will_ be made after providers are determined from config * Remove use of `ProviderDownloadOutcome`s * Move repeated code into separate method * Change provider download approach: determine if locks changed at point of attempting to update the lockfile, keep record of incomplete providers inside init command struct * Refactor `mergeLockedDependencies` and update test * Add comments to provider download methods * Fix issue where incorrect message ouput to view when downloading providers * Update `mergeLockedDependencies` method to be more generic * Update `getProvidersFromState` method to receive in-progress config locks and merge those with any locks on file. This allows re-use of providers downloaded by `getProvidersFromConfig` in the same init command * Fix config for `TestInit_stateStoreBlockIsExperimental` * Improve testing of mergeLockedDependencies; state locks are always missing version constraints * Add tests for 2 phase provider download * Add test case to cover use of the `-upgrade` flag * Change the message shown when a provider is reused during the second provider download step. When downloading providers described only in the state then the provider may already be downloaded from a previous init (i.e. is recorded in the deps lock file) or downloaded during step 1 of provider download. The message here needs to cover both potential scenarios. * Update mergeLockedDependencies comment * fix: completely remove use of upgrade flag in getProvidersFromState * Fix: avoid nil pointer errors by returning an empty collection of locks when there is no state * Fix: use state store data only in diagnostic * Change how we make PSS experimental - avoid relying on a package level variable that causes tests to interact. * Remove full-stop in view message, update tests * Update span names to be unique * Re-add lost early returns * Remove unused view messages * Add comments to new view messages
133 lines
5.7 KiB
Go
133 lines
5.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package command
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
|
|
"github.com/hashicorp/terraform/internal/depsfile"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// dependenclyLockFilename is the filename of the dependency lock file.
|
|
//
|
|
// This file should live in the same directory as the .tf files for the
|
|
// root module of the configuration, alongside the .terraform directory
|
|
// as long as that directory's path isn't overridden by the TF_DATA_DIR
|
|
// environment variable.
|
|
//
|
|
// We always expect to find this file in the current working directory
|
|
// because that should also be the root module directory.
|
|
//
|
|
// Some commands have legacy command line arguments that make the root module
|
|
// directory something other than the root module directory; when using those,
|
|
// the lock file will be written in the "wrong" place (the current working
|
|
// directory instead of the root module directory) but we do that intentionally
|
|
// to match where the ".terraform" directory would also be written in that
|
|
// case. Eventually we will phase out those legacy arguments in favor of the
|
|
// global -chdir=... option, which _does_ preserve the intended invariant
|
|
// that the root module directory is always the current working directory.
|
|
const dependencyLockFilename = ".terraform.lock.hcl"
|
|
|
|
// lockedDependencies reads the dependency lock information from the lock file
|
|
// in the current working directory.
|
|
//
|
|
// If the lock file doesn't exist at the time of the call, lockedDependencies
|
|
// indicates success and returns an empty Locks object. If the file does
|
|
// exist then the result is either a representation of the contents of that
|
|
// file at the instant of the call or error diagnostics explaining some way
|
|
// in which the lock file is invalid.
|
|
//
|
|
// The result is a snapshot of the locked dependencies at the time of the call
|
|
// and does not update as a result of calling replaceLockedDependencies
|
|
// or any other modification method.
|
|
func (m *Meta) lockedDependencies() (*depsfile.Locks, tfdiags.Diagnostics) {
|
|
// We check that the file exists first, because the underlying HCL
|
|
// parser doesn't distinguish that error from other error types
|
|
// in a machine-readable way but we want to treat that as a success
|
|
// with no locks. There is in theory a race condition here in that
|
|
// the file could be created or removed in the meantime, but we're not
|
|
// promising to support two concurrent dependency installation processes.
|
|
_, err := os.Stat(dependencyLockFilename)
|
|
if os.IsNotExist(err) {
|
|
return m.annotateDependencyLocksWithOverrides(depsfile.NewLocks()), nil
|
|
}
|
|
|
|
ret, diags := depsfile.LoadLocksFromFile(dependencyLockFilename)
|
|
return m.annotateDependencyLocksWithOverrides(ret), diags
|
|
}
|
|
|
|
// replaceLockedDependencies creates or overwrites the lock file in the
|
|
// current working directory to contain the information recorded in the given
|
|
// locks object.
|
|
func (m *Meta) replaceLockedDependencies(new *depsfile.Locks) tfdiags.Diagnostics {
|
|
return depsfile.SaveLocksToFile(new, dependencyLockFilename)
|
|
}
|
|
|
|
// mergeLockedDependencies combines two sets of locks. The 'base' locks are copied, and any providers
|
|
// present in the additional locks that aren't present in the base are added to that copy. The merged
|
|
// combination is returned.
|
|
//
|
|
// If you're combining locks derived from config with other locks (from state or deps locks file), then
|
|
// the config locks need to be the first argument to ensure that the merged locks contain any
|
|
// version constraints. Version constraint data is only present in configuration.
|
|
// This allows code in the init command to download providers in separate phases and
|
|
// keep the lock file updated accurately after each phase.
|
|
//
|
|
// This method supports downloading providers in 2 steps, and is used during the second download step and
|
|
// while updating the dependency lock file.
|
|
func (m *Meta) mergeLockedDependencies(baseLocks, additionalLocks *depsfile.Locks) *depsfile.Locks {
|
|
|
|
mergedLocks := baseLocks.DeepCopy()
|
|
|
|
// Append locks derived from the state to locks derived from config.
|
|
for _, lock := range additionalLocks.AllProviders() {
|
|
match := mergedLocks.Provider(lock.Provider())
|
|
if match != nil {
|
|
log.Printf("[TRACE] Ignoring provider %s version %s in appendLockedDependencies; lock file contains %s version %s already",
|
|
lock.Provider(),
|
|
lock.Version(),
|
|
match.Provider(),
|
|
match.Version(),
|
|
)
|
|
} else {
|
|
// This is a new provider now present in the lockfile yet
|
|
log.Printf("[DEBUG] Appending provider %s to the lock file", lock.Provider())
|
|
mergedLocks.SetProvider(lock.Provider(), lock.Version(), lock.VersionConstraints(), lock.AllHashes())
|
|
}
|
|
}
|
|
|
|
// Override the locks file with the new combination of locks
|
|
return mergedLocks
|
|
}
|
|
|
|
// annotateDependencyLocksWithOverrides modifies the given Locks object in-place
|
|
// to track as overridden any provider address that's subject to testing
|
|
// overrides, development overrides, or "unmanaged provider" status.
|
|
//
|
|
// This is just an implementation detail of the lockedDependencies method,
|
|
// not intended for use anywhere else.
|
|
func (m *Meta) annotateDependencyLocksWithOverrides(ret *depsfile.Locks) *depsfile.Locks {
|
|
if ret == nil {
|
|
return ret
|
|
}
|
|
|
|
for addr := range m.ProviderDevOverrides {
|
|
log.Printf("[DEBUG] Provider %s is overridden by dev_overrides", addr)
|
|
ret.SetProviderOverridden(addr)
|
|
}
|
|
for addr := range m.UnmanagedProviders {
|
|
log.Printf("[DEBUG] Provider %s is overridden as an \"unmanaged provider\"", addr)
|
|
ret.SetProviderOverridden(addr)
|
|
}
|
|
if m.testingOverrides != nil {
|
|
for addr := range m.testingOverrides.Providers {
|
|
log.Printf("[DEBUG] Provider %s is overridden in Meta.testingOverrides", addr)
|
|
ret.SetProviderOverridden(addr)
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|