You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/internal/command/meta_dependencies.go

134 lines
5.7 KiB

// 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
}