mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-05-26 13:03:36 -04:00
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" . GOOS=js GOARCH=wasm go build -ldflags="-X 'main.experimentsAllowed=yes'" -o main.wasm ./cmd/tofu/ Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
163 lines
6.7 KiB
Go
163 lines
6.7 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package command
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"maps"
|
|
"os"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/opentofu/opentofu/internal/addrs"
|
|
"github.com/opentofu/opentofu/internal/depsfile"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
// dependencyLockFilename 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 := m.WorkingDir.FS.Stat(dependencyLockFilename)
|
|
if os.IsNotExist(err) {
|
|
return m.annotateDependencyLocksWithOverrides(depsfile.NewLocks()), nil
|
|
}
|
|
|
|
ret, diags := depsfile.LoadLocksFromFile(m.WorkingDir.FS, dependencyLockFilename)
|
|
return m.annotateDependencyLocksWithOverrides(ret), diags
|
|
}
|
|
|
|
// lockedDependenciesWithPredecessorRegistryShimmed is a wrapper around
|
|
// [Meta.lockedDependencies] that adds some extra synthetic entries for any
|
|
// existing lock entry that matches "registry.terraform.io/hashicorp/*", to
|
|
// encourage the provider installer to select the same version of the
|
|
// corresponding provider in OpenTofu's registry, to keep dependency selections
|
|
// consistent as folks migrate over from our predecessor.
|
|
//
|
|
// This variant should be used only by commands that will perform provider
|
|
// installation based on the result, such as the implementation "tofu init".
|
|
// This is not appropriate to use when the result will be used to call
|
|
// [Meta.providerFactories]; that function needs to be given exactly the
|
|
// dependencies from the lock file, because it expects to find every listed
|
|
// provider in the cache directory and will fail if not.
|
|
func (m *Meta) lockedDependenciesWithPredecessorRegistryShimmed() (*depsfile.Locks, tfdiags.Diagnostics) {
|
|
ret, diags := m.lockedDependencies()
|
|
if ret == nil {
|
|
return nil, diags
|
|
}
|
|
|
|
// If this is the first run after switching from OpenTofu's predecessor,
|
|
// the lock file might contain some entries from the predecessor's registry
|
|
// which we can translate into similar entries for OpenTofu's registry.
|
|
changed := ret.UpgradeFromPredecessorProject()
|
|
if len(changed) != 0 {
|
|
oldAddrs := slices.Collect(maps.Keys(changed))
|
|
slices.SortFunc(oldAddrs, func(a, b addrs.Provider) int {
|
|
if a.LessThan(b) {
|
|
return -1
|
|
} else if b.LessThan(a) {
|
|
return 1
|
|
} else {
|
|
return 0
|
|
}
|
|
})
|
|
var buf strings.Builder // strings.Builder writes cannot fail
|
|
_, _ = buf.WriteString("OpenTofu automatically rewrote some entries in your dependency lock file:\n")
|
|
for _, oldAddr := range oldAddrs {
|
|
newAddr := changed[oldAddr]
|
|
// We intentionally use String instead of ForDisplay here because
|
|
// this message won't make much sense without using fully-qualified
|
|
// addresses with explicit registry hostnames.
|
|
_, _ = fmt.Fprintf(&buf, " - %s => %s\n", oldAddr.String(), newAddr.String())
|
|
}
|
|
_, _ = buf.WriteString("\nThe version selections were preserved, but the hashes were not because the OpenTofu project's provider releases are not byte-for-byte identical.")
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Warning,
|
|
"Dependency lock file entries automatically updated",
|
|
buf.String(),
|
|
))
|
|
|
|
// The newly-added entries might also be subject to one of the various
|
|
// kinds of "overrides" we support.
|
|
ret = m.annotateDependencyLocksWithOverrides(ret)
|
|
}
|
|
|
|
return 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(ctx context.Context, new *depsfile.Locks) tfdiags.Diagnostics {
|
|
return depsfile.SaveLocksToFile(ctx, m.WorkingDir.FS, 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
|
|
}
|