mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
df578afd7e
In historical versions of Terraform the responsibility to check this was inside the terraform.NewContext function, along with various other assorted concerns that made that function particularly complicated. More recently, we reduced the responsibility of the "terraform" package only to instantiating particular named plugins, assuming that its caller is responsible for selecting appropriate versions of any providers that _are_ external. However, until this commit we were just assuming that "terraform init" had correctly selected appropriate plugins and recorded them in the lock file, and so nothing was dealing with the problem of ensuring that there haven't been any changes to the lock file or config since the most recent "terraform init" which would cause us to need to re-evaluate those decisions. Part of the game here is to slightly extend the role of the dependency locks object to also carry information about a subset of provider addresses whose lock entries we're intentionally disregarding as part of the various little edge-case features we have for overridding providers: dev_overrides, "unmanaged providers", and the testing overrides in our own unit tests. This is an in-memory-only annotation, never included in the serialized plan files on disk. I had originally intended to create a new package to encapsulate all of this plugin-selection logic, including both the version constraint checking here and also the handling of the provider factory functions, but as an interim step I've just made version constraint consistency checks the responsibility of the backend/local package, which means that we'll always catch problems as part of preparing for local operations, while not imposing these additional checks on commands that _don't_ run local operations, such as "terraform apply" when in remote operations mode.
94 lines
3.9 KiB
Go
94 lines
3.9 KiB
Go
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)
|
|
}
|
|
|
|
// 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
|
|
}
|