mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Allowed variable to pass inside variables
block (#1488)
Signed-off-by: siddharthasonker95 <158144589+siddharthasonker95@users.noreply.github.com>
This commit is contained in:
parent
1fecaef9aa
commit
9138470a67
@ -9,6 +9,7 @@ ENHANCEMENTS:
|
||||
* Made `tofu plan` with `generate-config-out` flag replace JSON strings with `jsonencode` functions calls. ([#1595](https://github.com/opentofu/opentofu/pull/1595))
|
||||
* Make state persistence interval configurable via `TF_STATE_PERSIST_INTERVAL` environment variable ([#1591](https://github.com/opentofu/opentofu/pull/1591))
|
||||
* Improved performance of writing state files and reduced their size using compact json encoding. ([#1647](https://github.com/opentofu/opentofu/pull/1647))
|
||||
* Allow to reference variable inside the `variables` block of a test file. ([1488](https://github.com/opentofu/opentofu/pull/1488))
|
||||
|
||||
BUG FIXES:
|
||||
* Fixed crash in gcs backend when using certain commands ([#1618](https://github.com/opentofu/opentofu/pull/1618))
|
||||
|
@ -668,7 +668,10 @@ func (runner *TestFileRunner) destroy(config *configs.Config, state *states.Stat
|
||||
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables, runner.States)
|
||||
evalCtx, ctxDiags := getEvalContextForTest(runner.States, config, runner.Suite.GlobalVariables)
|
||||
diags = diags.Append(ctxDiags)
|
||||
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables, evalCtx)
|
||||
diags = diags.Append(variableDiags)
|
||||
|
||||
if diags.HasErrors() {
|
||||
@ -731,7 +734,10 @@ func (runner *TestFileRunner) plan(config *configs.Config, state *states.State,
|
||||
references, referenceDiags := run.GetReferences()
|
||||
diags = diags.Append(referenceDiags)
|
||||
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables, runner.States)
|
||||
evalCtx, ctxDiags := getEvalContextForTest(runner.States, config, runner.Suite.GlobalVariables)
|
||||
diags = diags.Append(ctxDiags)
|
||||
|
||||
variables, variableDiags := buildInputVariablesForTest(run, file, config, runner.Suite.GlobalVariables, evalCtx)
|
||||
diags = diags.Append(variableDiags)
|
||||
|
||||
if diags.HasErrors() {
|
||||
@ -1000,9 +1006,8 @@ func (runner *TestFileRunner) Cleanup(file *moduletest.File) {
|
||||
// Crucially, it differs from prepareInputVariablesForAssertions in that it only
|
||||
// includes variables that are reference by the config and not everything that
|
||||
// is defined within the test run block and test file.
|
||||
func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, config *configs.Config, globals map[string]backend.UnparsedVariableValue, states map[string]*TestFileState) (tofu.InputValues, tfdiags.Diagnostics) {
|
||||
func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, config *configs.Config, globals map[string]backend.UnparsedVariableValue, evalCtx *hcl.EvalContext) (tofu.InputValues, tfdiags.Diagnostics) {
|
||||
variables := make(map[string]backend.UnparsedVariableValue)
|
||||
evalCtx := getEvalContextFromStates(states)
|
||||
for name := range config.Module.Variables {
|
||||
if run != nil {
|
||||
if expr, exists := run.Config.Variables[name]; exists {
|
||||
@ -1019,9 +1024,10 @@ func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, conf
|
||||
if file != nil {
|
||||
if expr, exists := file.Config.Variables[name]; exists {
|
||||
// If it's not set locally, it maybe set for the entire file.
|
||||
variables[name] = unparsedVariableValueExpression{
|
||||
variables[name] = testVariableValueExpression{
|
||||
expr: expr,
|
||||
sourceType: tofu.ValueFromConfig,
|
||||
ctx: evalCtx,
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -1042,17 +1048,12 @@ func buildInputVariablesForTest(run *moduletest.Run, file *moduletest.File, conf
|
||||
return backend.ParseVariableValues(variables, config.Module.Variables)
|
||||
}
|
||||
|
||||
// getEvalContextFromStates constructs an hcl.EvalContext based on the provided map
|
||||
// of TestFileState instances. It extracts the relevant information from the
|
||||
// states to create a context suitable for HCL evaluation, including the output
|
||||
// values of modules.
|
||||
//
|
||||
// Parameters:
|
||||
// - states: A map of TestFileState instances containing the state information.
|
||||
//
|
||||
// Returns:
|
||||
// - *hcl.EvalContext: The constructed HCL evaluation context.
|
||||
func getEvalContextFromStates(states map[string]*TestFileState) *hcl.EvalContext {
|
||||
// getEvalContextForTest constructs an hcl.EvalContext based on the provided map of
|
||||
// TestFileState instances, configuration and global variables.
|
||||
// It extracts the relevant information from the input parameters to create a
|
||||
// context suitable for HCL evaluation.
|
||||
func getEvalContextForTest(states map[string]*TestFileState, config *configs.Config, globals map[string]backend.UnparsedVariableValue) (*hcl.EvalContext, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
runCtx := make(map[string]cty.Value)
|
||||
for _, state := range states {
|
||||
if state.Run == nil {
|
||||
@ -1065,9 +1066,24 @@ func getEvalContextFromStates(states map[string]*TestFileState) *hcl.EvalContext
|
||||
}
|
||||
runCtx[state.Run.Name] = cty.ObjectVal(outputs)
|
||||
}
|
||||
ctx := &hcl.EvalContext{Variables: map[string]cty.Value{"run": cty.ObjectVal(runCtx)}}
|
||||
|
||||
return ctx
|
||||
// If the variable is referenced in the tfvars file or TF_VAR_ environment variable, then lookup the value
|
||||
// in global variables; otherwise, assign the default value.
|
||||
inputValues, diags := parseAndApplyDefaultValues(globals, config.Module.Variables)
|
||||
diags.Append(diags)
|
||||
|
||||
varCtx := make(map[string]cty.Value)
|
||||
for name, val := range inputValues {
|
||||
varCtx[name] = val.Value
|
||||
}
|
||||
|
||||
ctx := &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"run": cty.ObjectVal(runCtx),
|
||||
"var": cty.ObjectVal(varCtx),
|
||||
},
|
||||
}
|
||||
return ctx, diags
|
||||
}
|
||||
|
||||
type testVariableValueExpression struct {
|
||||
@ -1090,6 +1106,40 @@ func (v testVariableValueExpression) ParseVariableValue(mode configs.VariablePar
|
||||
}, diags
|
||||
}
|
||||
|
||||
// parseAndApplyDefaultValues parses the given unparsed variables into tofu.InputValues
|
||||
// and applies default values from the configuration variables where applicable.
|
||||
// This ensures all variables are correctly initialized and returns the resulting tofu.InputValues.
|
||||
func parseAndApplyDefaultValues(unparsedVariables map[string]backend.UnparsedVariableValue, configVariables map[string]*configs.Variable) (tofu.InputValues, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
inputs := make(tofu.InputValues, len(unparsedVariables))
|
||||
for name, variable := range unparsedVariables {
|
||||
value, valueDiags := variable.ParseVariableValue(configs.VariableParseLiteral)
|
||||
diags = diags.Append(valueDiags)
|
||||
inputs[name] = value
|
||||
}
|
||||
|
||||
// Now, we're going to apply any default values from the configuration.
|
||||
// We do this after the conversion into tofu.InputValues, as the
|
||||
// defaults have already been converted into cty.Value objects.
|
||||
for name, variable := range configVariables {
|
||||
if _, exists := unparsedVariables[name]; exists {
|
||||
// Then we don't want to apply the default for this variable as we
|
||||
// already have a value.
|
||||
continue
|
||||
}
|
||||
|
||||
if variable.Default != cty.NilVal {
|
||||
inputs[name] = &tofu.InputValue{
|
||||
Value: variable.Default,
|
||||
SourceType: tofu.ValueFromConfig,
|
||||
SourceRange: tfdiags.SourceRangeFromHCL(variable.DeclRange),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputs, diags
|
||||
}
|
||||
|
||||
// prepareInputVariablesForAssertions creates a tofu.InputValues mapping
|
||||
// that contains all the variables defined for a given run and file, alongside
|
||||
// any unset variables that have defaults within the provided config.
|
||||
@ -1107,7 +1157,9 @@ func (v testVariableValueExpression) ParseVariableValue(mode configs.VariablePar
|
||||
// available are also defined in the config. It returns a function that resets
|
||||
// the config which must be called so the config can be reused going forward.
|
||||
func (runner *TestFileRunner) prepareInputVariablesForAssertions(config *configs.Config, run *moduletest.Run, file *moduletest.File, globals map[string]backend.UnparsedVariableValue) (tofu.InputValues, func(), tfdiags.Diagnostics) {
|
||||
ctx := getEvalContextFromStates(runner.States)
|
||||
var diags tfdiags.Diagnostics
|
||||
ctx, ctxDiags := getEvalContextForTest(runner.States, config, globals)
|
||||
diags = diags.Append(ctxDiags)
|
||||
|
||||
variables := make(map[string]backend.UnparsedVariableValue)
|
||||
|
||||
@ -1148,34 +1200,9 @@ func (runner *TestFileRunner) prepareInputVariablesForAssertions(config *configs
|
||||
|
||||
// We've gathered all the values we have, let's convert them into
|
||||
// tofu.InputValues so they can be passed into the OpenTofu graph.
|
||||
|
||||
inputs := make(tofu.InputValues, len(variables))
|
||||
var diags tfdiags.Diagnostics
|
||||
for name, variable := range variables {
|
||||
value, valueDiags := variable.ParseVariableValue(configs.VariableParseLiteral)
|
||||
diags = diags.Append(valueDiags)
|
||||
inputs[name] = value
|
||||
}
|
||||
|
||||
// Next, we're going to apply any default values from the configuration.
|
||||
// We do this after the conversion into tofu.InputValues, as the
|
||||
// defaults have already been converted into cty.Value objects.
|
||||
|
||||
for name, variable := range config.Module.Variables {
|
||||
if _, exists := variables[name]; exists {
|
||||
// Then we don't want to apply the default for this variable as we
|
||||
// already have a value.
|
||||
continue
|
||||
}
|
||||
|
||||
if variable.Default != cty.NilVal {
|
||||
inputs[name] = &tofu.InputValue{
|
||||
Value: variable.Default,
|
||||
SourceType: tofu.ValueFromConfig,
|
||||
SourceRange: tfdiags.SourceRangeFromHCL(variable.DeclRange),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also, apply default values from the configuration variables where applicable.
|
||||
inputs, valDiags := parseAndApplyDefaultValues(variables, config.Module.Variables)
|
||||
diags.Append(valDiags)
|
||||
|
||||
// Finally, we're going to do a some modifications to the config.
|
||||
// If we have got variable values from the test file we need to make sure
|
||||
|
@ -1132,48 +1132,13 @@ Condition expression could not be evaluated at this time.
|
||||
}
|
||||
|
||||
func TestTest_LocalVariables(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", "pass_with_local_variable")), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
provider := testing_command.NewProvider(nil)
|
||||
|
||||
providerSource, close := newMockProviderSource(t, map[string][]string{
|
||||
"test": {"1.0.0"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
ui := new(cli.MockUi)
|
||||
|
||||
meta := Meta{
|
||||
testingOverrides: metaOverridesForProvider(provider.Provider),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
Streams: streams,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
init := &InitCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
if code := init.Run(nil); code != 0 {
|
||||
t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
||||
c := &TestCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
code := c.Run([]string{"-verbose", "-no-color"})
|
||||
output := done(t)
|
||||
|
||||
if code != 0 {
|
||||
t.Errorf("expected status code 0 but got %d", code)
|
||||
}
|
||||
|
||||
expected := `tests/test.tftest.hcl... pass
|
||||
tcs := map[string]struct {
|
||||
expected string
|
||||
code int
|
||||
skip bool
|
||||
}{
|
||||
"pass_with_local_variable": {
|
||||
expected: `tests/test.tftest.hcl... pass
|
||||
run "first"... pass
|
||||
|
||||
|
||||
@ -1188,16 +1153,91 @@ OpenTofu has compared your real infrastructure against your configuration and
|
||||
found no differences, so no changes are needed.
|
||||
|
||||
Success! 2 passed, 0 failed.
|
||||
`
|
||||
`,
|
||||
code: 0,
|
||||
},
|
||||
"pass_var_inside_variables": {
|
||||
expected: `main.tftest.hcl... pass
|
||||
run "first"... pass
|
||||
|
||||
actual := output.All()
|
||||
|
||||
if diff := cmp.Diff(actual, expected); len(diff) > 0 {
|
||||
t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
|
||||
Outputs:
|
||||
|
||||
sss = "false"
|
||||
|
||||
Success! 1 passed, 0 failed.
|
||||
`,
|
||||
code: 0,
|
||||
},
|
||||
"pass_var_with_default_value_inside_variables": {
|
||||
expected: `main.tftest.hcl... pass
|
||||
run "first"... pass
|
||||
|
||||
|
||||
Outputs:
|
||||
|
||||
sss = "true"
|
||||
|
||||
Success! 1 passed, 0 failed.
|
||||
`,
|
||||
code: 0,
|
||||
},
|
||||
}
|
||||
|
||||
if provider.ResourceCount() > 0 {
|
||||
t.Errorf("should have deleted all resources on completion but left %v", provider.ResourceString())
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if tc.skip {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
file := name
|
||||
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(path.Join("test", file)), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
provider := testing_command.NewProvider(nil)
|
||||
providerSource, providerClose := newMockProviderSource(t, map[string][]string{
|
||||
"test": {"1.0.0"},
|
||||
})
|
||||
defer providerClose()
|
||||
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := views.NewView(streams)
|
||||
ui := new(cli.MockUi)
|
||||
meta := Meta{
|
||||
testingOverrides: metaOverridesForProvider(provider.Provider),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
Streams: streams,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
init := &InitCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
if code := init.Run(nil); code != 0 {
|
||||
t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
|
||||
}
|
||||
|
||||
command := &TestCommand{
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
code := command.Run([]string{"-verbose", "-no-color"})
|
||||
output := done(t)
|
||||
|
||||
if code != tc.code {
|
||||
t.Errorf("expected status code %d but got %d: %s", tc.code, code, output.All())
|
||||
}
|
||||
|
||||
actual := output.All()
|
||||
|
||||
if diff := cmp.Diff(actual, tc.expected); len(diff) > 0 {
|
||||
t.Errorf("output didn't match expected:\nexpected:\n%s\nactual:\n%s\ndiff:\n%s", tc.expected, actual, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
10
internal/command/testdata/test/pass_var_inside_variables/main.tf
vendored
Normal file
10
internal/command/testdata/test/pass_var_inside_variables/main.tf
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
variable "var1" {
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "var2" {
|
||||
}
|
||||
|
||||
output "sss" {
|
||||
value = var.var2
|
||||
}
|
10
internal/command/testdata/test/pass_var_inside_variables/main.tftest.hcl
vendored
Normal file
10
internal/command/testdata/test/pass_var_inside_variables/main.tftest.hcl
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
variables {
|
||||
var2 = var.var1 ? "true" : "false"
|
||||
}
|
||||
|
||||
run "first" {
|
||||
assert {
|
||||
condition = output.sss == "false"
|
||||
error_message = "Should work"
|
||||
}
|
||||
}
|
1
internal/command/testdata/test/pass_var_inside_variables/terraform.tfvars
vendored
Normal file
1
internal/command/testdata/test/pass_var_inside_variables/terraform.tfvars
vendored
Normal file
@ -0,0 +1 @@
|
||||
var1 = false
|
10
internal/command/testdata/test/pass_var_with_default_value_inside_variables/main.tf
vendored
Normal file
10
internal/command/testdata/test/pass_var_with_default_value_inside_variables/main.tf
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
variable "var1" {
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "var2" {
|
||||
}
|
||||
|
||||
output "sss" {
|
||||
value = var.var2
|
||||
}
|
10
internal/command/testdata/test/pass_var_with_default_value_inside_variables/main.tftest.hcl
vendored
Normal file
10
internal/command/testdata/test/pass_var_with_default_value_inside_variables/main.tftest.hcl
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
variables {
|
||||
var2 = var.var1 ? "true" : "false"
|
||||
}
|
||||
|
||||
run "first" {
|
||||
assert {
|
||||
condition = output.sss == "true"
|
||||
error_message = "Should work"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user