1package command
2
3import (
4	"os"
5
6	"github.com/hashicorp/terraform/internal/depsfile"
7	"github.com/hashicorp/terraform/internal/tfdiags"
8)
9
10// dependenclyLockFilename is the filename of the dependency lock file.
11//
12// This file should live in the same directory as the .tf files for the
13// root module of the configuration, alongside the .terraform directory
14// as long as that directory's path isn't overridden by the TF_DATA_DIR
15// environment variable.
16//
17// We always expect to find this file in the current working directory
18// because that should also be the root module directory.
19//
20// Some commands have legacy command line arguments that make the root module
21// directory something other than the root module directory; when using those,
22// the lock file will be written in the "wrong" place (the current working
23// directory instead of the root module directory) but we do that intentionally
24// to match where the ".terraform" directory would also be written in that
25// case. Eventually we will phase out those legacy arguments in favor of the
26// global -chdir=... option, which _does_ preserve the intended invariant
27// that the root module directory is always the current working directory.
28const dependencyLockFilename = ".terraform.lock.hcl"
29
30// lockedDependencies reads the dependency lock information from the lock file
31// in the current working directory.
32//
33// If the lock file doesn't exist at the time of the call, lockedDependencies
34// indicates success and returns an empty Locks object. If the file does
35// exist then the result is either a representation of the contents of that
36// file at the instant of the call or error diagnostics explaining some way
37// in which the lock file is invalid.
38//
39// The result is a snapshot of the locked dependencies at the time of the call
40// and does not update as a result of calling replaceLockedDependencies
41// or any other modification method.
42func (m *Meta) lockedDependencies() (*depsfile.Locks, tfdiags.Diagnostics) {
43	// We check that the file exists first, because the underlying HCL
44	// parser doesn't distinguish that error from other error types
45	// in a machine-readable way but we want to treat that as a success
46	// with no locks. There is in theory a race condition here in that
47	// the file could be created or removed in the meantime, but we're not
48	// promising to support two concurrent dependency installation processes.
49	_, err := os.Stat(dependencyLockFilename)
50	if os.IsNotExist(err) {
51		return depsfile.NewLocks(), nil
52	}
53
54	return depsfile.LoadLocksFromFile(dependencyLockFilename)
55}
56
57// replaceLockedDependencies creates or overwrites the lock file in the
58// current working directory to contain the information recorded in the given
59// locks object.
60func (m *Meta) replaceLockedDependencies(new *depsfile.Locks) tfdiags.Diagnostics {
61	return depsfile.SaveLocksToFile(new, dependencyLockFilename)
62}
63