mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
cliconfig: Allow breaking the dependency lock file using the environment
Since it's already possible to activate the dependency lock file using an environment variable, we should allow opting in to it having broken behavior using the environment too. It's kinda odd in retrospect that TF_PLUGIN_CACHE_DIR is the only setting we allow to be configured both in the environment and the CLI configuration. That means that the infrastructure for dealing with that situation was relatively immature here and so I did some light refactoring to make it unit-testable without actually modifying the test program's environment.
This commit is contained in:
parent
3d1a58d5b5
commit
a86cef4d50
@ -14,6 +14,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const pluginCacheDirEnvVar = "TF_PLUGIN_CACHE_DIR"
|
const pluginCacheDirEnvVar = "TF_PLUGIN_CACHE_DIR"
|
||||||
|
const pluginCacheMayBreakLockFileEnvVar = "TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE"
|
||||||
|
|
||||||
// Config is the structure of the configuration for the Terraform CLI.
|
// Config is the structure of the configuration for the Terraform CLI.
|
||||||
//
|
//
|
||||||
@ -220,9 +222,14 @@ func loadConfigDir(path string) (*Config, tfdiags.Diagnostics) {
|
|||||||
// Any values specified in this config should override those set in the
|
// Any values specified in this config should override those set in the
|
||||||
// configuration file.
|
// configuration file.
|
||||||
func EnvConfig() *Config {
|
func EnvConfig() *Config {
|
||||||
|
env := makeEnvMap(os.Environ())
|
||||||
|
return envConfig(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envConfig(env map[string]string) *Config {
|
||||||
config := &Config{}
|
config := &Config{}
|
||||||
|
|
||||||
if envPluginCacheDir := os.Getenv(pluginCacheDirEnvVar); envPluginCacheDir != "" {
|
if envPluginCacheDir := env[pluginCacheDirEnvVar]; envPluginCacheDir != "" {
|
||||||
// No Expandenv here, because expanding environment variables inside
|
// No Expandenv here, because expanding environment variables inside
|
||||||
// an environment variable would be strange and seems unnecessary.
|
// an environment variable would be strange and seems unnecessary.
|
||||||
// (User can expand variables into the value while setting it using
|
// (User can expand variables into the value while setting it using
|
||||||
@ -230,9 +237,34 @@ func EnvConfig() *Config {
|
|||||||
config.PluginCacheDir = envPluginCacheDir
|
config.PluginCacheDir = envPluginCacheDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if envMayBreak := env[pluginCacheMayBreakLockFileEnvVar]; envMayBreak != "" && envMayBreak != "0" {
|
||||||
|
// This is an environment variable analog to the
|
||||||
|
// plugin_cache_may_break_dependency_lock_file setting. If either this
|
||||||
|
// or the config file setting are enabled then it's enabled; there is
|
||||||
|
// no way to override back to false if either location sets this to
|
||||||
|
// true.
|
||||||
|
config.PluginCacheMayBreakDependencyLockFile = true
|
||||||
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeEnvMap(environ []string) map[string]string {
|
||||||
|
if len(environ) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make(map[string]string, len(environ))
|
||||||
|
for _, entry := range environ {
|
||||||
|
eq := strings.IndexByte(entry, '=')
|
||||||
|
if eq == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret[entry[:eq]] = entry[eq+1:]
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// Validate checks for errors in the configuration that cannot be detected
|
// Validate checks for errors in the configuration that cannot be detected
|
||||||
// just by HCL decoding, returning any problems as diagnostics.
|
// just by HCL decoding, returning any problems as diagnostics.
|
||||||
//
|
//
|
||||||
@ -328,6 +360,12 @@ func (c *Config) Merge(c2 *Config) *Config {
|
|||||||
result.PluginCacheDir = c2.PluginCacheDir
|
result.PluginCacheDir = c2.PluginCacheDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.PluginCacheMayBreakDependencyLockFile || c2.PluginCacheMayBreakDependencyLockFile {
|
||||||
|
// This setting saturates to "on"; once either configuration sets it,
|
||||||
|
// there is no way to override it back to off again.
|
||||||
|
result.PluginCacheMayBreakDependencyLockFile = true
|
||||||
|
}
|
||||||
|
|
||||||
if (len(c.Hosts) + len(c2.Hosts)) > 0 {
|
if (len(c.Hosts) + len(c2.Hosts)) > 0 {
|
||||||
result.Hosts = make(map[string]*ConfigHost)
|
result.Hosts = make(map[string]*ConfigHost)
|
||||||
for name, host := range c.Hosts {
|
for name, host := range c.Hosts {
|
||||||
|
@ -31,7 +31,7 @@ func TestLoadConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfig_env(t *testing.T) {
|
func TestLoadConfig_envSubst(t *testing.T) {
|
||||||
defer os.Unsetenv("TFTEST")
|
defer os.Unsetenv("TFTEST")
|
||||||
os.Setenv("TFTEST", "hello")
|
os.Setenv("TFTEST", "hello")
|
||||||
|
|
||||||
@ -55,6 +55,141 @@ func TestLoadConfig_env(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnvConfig(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
env map[string]string
|
||||||
|
want *Config
|
||||||
|
}{
|
||||||
|
"no environment variables": {
|
||||||
|
nil,
|
||||||
|
&Config{},
|
||||||
|
},
|
||||||
|
"TF_PLUGIN_CACHE_DIR=boop": {
|
||||||
|
map[string]string{
|
||||||
|
"TF_PLUGIN_CACHE_DIR": "boop",
|
||||||
|
},
|
||||||
|
&Config{
|
||||||
|
PluginCacheDir: "boop",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE=anything_except_zero": {
|
||||||
|
map[string]string{
|
||||||
|
"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": "anything_except_zero",
|
||||||
|
},
|
||||||
|
&Config{
|
||||||
|
PluginCacheMayBreakDependencyLockFile: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE=0": {
|
||||||
|
map[string]string{
|
||||||
|
"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": "0",
|
||||||
|
},
|
||||||
|
&Config{},
|
||||||
|
},
|
||||||
|
"TF_PLUGIN_CACHE_DIR and TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": {
|
||||||
|
map[string]string{
|
||||||
|
"TF_PLUGIN_CACHE_DIR": "beep",
|
||||||
|
"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": "1",
|
||||||
|
},
|
||||||
|
&Config{
|
||||||
|
PluginCacheDir: "beep",
|
||||||
|
PluginCacheMayBreakDependencyLockFile: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := envConfig(test.env)
|
||||||
|
want := test.want
|
||||||
|
|
||||||
|
if diff := cmp.Diff(want, got); diff != "" {
|
||||||
|
t.Errorf("wrong result\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeEnvMap(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
environ []string
|
||||||
|
want map[string]string
|
||||||
|
}{
|
||||||
|
"nil": {
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"one": {
|
||||||
|
[]string{
|
||||||
|
"FOO=bar",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"FOO": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"many": {
|
||||||
|
[]string{
|
||||||
|
"FOO=1",
|
||||||
|
"BAR=2",
|
||||||
|
"BAZ=3",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"FOO": "1",
|
||||||
|
"BAR": "2",
|
||||||
|
"BAZ": "3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
[]string{
|
||||||
|
"FOO=1",
|
||||||
|
"BAR=1",
|
||||||
|
"FOO=2",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"BAR": "1",
|
||||||
|
"FOO": "2", // Last entry of each name wins
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty_val": {
|
||||||
|
[]string{
|
||||||
|
"FOO=",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"FOO": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"no_equals": {
|
||||||
|
[]string{
|
||||||
|
"FOO=bar",
|
||||||
|
"INVALID",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"FOO": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multi_equals": {
|
||||||
|
[]string{
|
||||||
|
"FOO=bar=baz=boop",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"FOO": "bar=baz=boop",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := makeEnvMap(test.environ)
|
||||||
|
want := test.want
|
||||||
|
|
||||||
|
if diff := cmp.Diff(want, got); diff != "" {
|
||||||
|
t.Errorf("wrong result\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadConfig_hosts(t *testing.T) {
|
func TestLoadConfig_hosts(t *testing.T) {
|
||||||
got, diags := loadConfigFile(filepath.Join(fixtureDir, "hosts"))
|
got, diags := loadConfigFile(filepath.Join(fixtureDir, "hosts"))
|
||||||
if len(diags) != 0 {
|
if len(diags) != 0 {
|
||||||
@ -284,6 +419,7 @@ func TestConfig_Merge(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PluginCacheMayBreakDependencyLockFile: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := &Config{
|
expected := &Config{
|
||||||
@ -338,6 +474,7 @@ func TestConfig_Merge(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PluginCacheMayBreakDependencyLockFile: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := c1.Merge(c2)
|
actual := c1.Merge(c2)
|
||||||
|
Loading…
Reference in New Issue
Block a user