mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-11 00:22:32 -06:00
Adds prompt for missing static variables (#2047)
Signed-off-by: Andrew Hayes <andrew.hayes@harness.io>
This commit is contained in:
parent
26fd6394c7
commit
de69070b02
@ -2930,6 +2930,8 @@ func TestInit_moduleSource(t *testing.T) {
|
|||||||
|
|
||||||
ui := cli.NewMockUi()
|
ui := cli.NewMockUi()
|
||||||
view, _ := testView(t)
|
view, _ := testView(t)
|
||||||
|
closeInput := testInteractiveInput(t, []string{"./mod"})
|
||||||
|
defer closeInput()
|
||||||
c := &InitCommand{
|
c := &InitCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
Ui: ui,
|
Ui: ui,
|
||||||
@ -2937,13 +2939,54 @@ func TestInit_moduleSource(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := c.Run(nil); code != 1 {
|
if code := c.Run(nil); code != 0 {
|
||||||
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
t.Fatalf("got exit status %d; want 0\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing-twice", func(t *testing.T) {
|
||||||
|
td := t.TempDir()
|
||||||
|
testCopyDir(t, testFixturePath("init-module-variable-source-multiple"), td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
view, _ := testView(t)
|
||||||
|
closeInput := testInteractiveInput(t, []string{"./mod"})
|
||||||
|
defer closeInput()
|
||||||
|
c := &InitCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
Ui: ui,
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
errStr := ui.ErrorWriter.String()
|
if code := c.Run(nil); code != 0 {
|
||||||
if !strings.Contains(errStr, `Variable not provided`) {
|
t.Fatalf("got exit status %d; want 0\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
||||||
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no-input", func(t *testing.T) {
|
||||||
|
td := t.TempDir()
|
||||||
|
testCopyDir(t, testFixturePath("init-module-variable-source"), td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
view, _ := testView(t)
|
||||||
|
closeInput := testInteractiveInput(t, []string{})
|
||||||
|
defer closeInput()
|
||||||
|
c := &InitCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
Ui: ui,
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-input=false",
|
||||||
|
}
|
||||||
|
|
||||||
|
if code := c.Run(args); code != 1 {
|
||||||
|
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@ -129,18 +130,26 @@ func (m *Meta) rootModuleCall(rootDir string) (configs.StaticModuleCall, tfdiags
|
|||||||
name := variable.Name
|
name := variable.Name
|
||||||
v, ok := variables[name]
|
v, ok := variables[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
// For now, we are simply failing when the user omits a required variable. This is due to complex interactions between variables in different code paths (apply existing plan for example)
|
|
||||||
// Ideally, we should be able to use something like backend_local.go:interactiveCollectVariables() in the future to allow users to provide values if input is enabled
|
|
||||||
// This is probably blocked by command package refactoring
|
|
||||||
if variable.Required() {
|
if variable.Required() {
|
||||||
// Not specified on CLI or in var files, without a valid default.
|
// User prompts are best efforts, so we accept the input here
|
||||||
return cty.NilVal, hcl.Diagnostics{&hcl.Diagnostic{
|
// and rely on downstream checks to validate it
|
||||||
Severity: hcl.DiagError,
|
rawVal, err := m.getInput(context.Background(), variable)
|
||||||
Summary: "Variable not provided via -var, tfvars files, or env",
|
if err != nil {
|
||||||
Subject: variable.DeclRange.Ptr(),
|
return cty.NilVal, hcl.Diagnostics{&hcl.Diagnostic{
|
||||||
}}
|
Severity: hcl.DiagError,
|
||||||
|
Summary: fmt.Sprintf("Failed to request input from user for variable var.%s", variable.Name),
|
||||||
|
Subject: variable.DeclRange.Ptr(),
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
v = unparsedVariableValueString{
|
||||||
|
str: rawVal,
|
||||||
|
name: name,
|
||||||
|
sourceType: tofu.ValueFromInput,
|
||||||
|
}
|
||||||
|
m.updateInputVariableCache(name, v)
|
||||||
|
} else {
|
||||||
|
return variable.Default, nil
|
||||||
}
|
}
|
||||||
return variable.Default, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed, parsedDiags := v.ParseVariableValue(variable.ParsingMode)
|
parsed, parsedDiags := v.ParseVariableValue(variable.ParsingMode)
|
||||||
@ -150,6 +159,24 @@ func (m *Meta) rootModuleCall(rootDir string) (configs.StaticModuleCall, tfdiags
|
|||||||
return call, diags
|
return call, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Meta) getInput(ctx context.Context, variable *configs.Variable) (string, error) {
|
||||||
|
if !m.Input() {
|
||||||
|
return "", fmt.Errorf("input is disabled")
|
||||||
|
}
|
||||||
|
uiInput := m.UIInput()
|
||||||
|
rawValue, err := uiInput.Input(ctx, &tofu.InputOpts{
|
||||||
|
Id: fmt.Sprintf("var.%s", variable.Name),
|
||||||
|
Query: fmt.Sprintf("var.%s", variable.Name),
|
||||||
|
Description: variable.Description,
|
||||||
|
Secret: variable.Sensitive,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[TRACE] Meta.getInput Failed to prompt for %s: %s", variable.Name, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return rawValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
// loadSingleModuleWithTests matches loadSingleModule except it also loads any
|
// loadSingleModuleWithTests matches loadSingleModule except it also loads any
|
||||||
// tests for the target module.
|
// tests for the target module.
|
||||||
func (m *Meta) loadSingleModuleWithTests(dir string, testDir string) (*configs.Module, tfdiags.Diagnostics) {
|
func (m *Meta) loadSingleModuleWithTests(dir string, testDir string) (*configs.Module, tfdiags.Diagnostics) {
|
||||||
|
@ -132,6 +132,10 @@ func (m *Meta) collectVariableValues() (map[string]backend.UnparsedVariableValue
|
|||||||
return ret, diags
|
return ret, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Meta) updateInputVariableCache(key string, value backend.UnparsedVariableValue) {
|
||||||
|
m.inputVariableCache[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Meta) addVarsFromDir(currDir string, ret map[string]backend.UnparsedVariableValue) tfdiags.Diagnostics {
|
func (m *Meta) addVarsFromDir(currDir string, ret map[string]backend.UnparsedVariableValue) tfdiags.Diagnostics {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
|
@ -918,6 +918,41 @@ func TestPlan_providerArgumentUnset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// based on the TestPlan_varsUnset test
|
||||||
|
// this is to check if we use the static variable in a resource
|
||||||
|
// does Plan ask for input
|
||||||
|
func TestPlan_resource_variable_inputs(t *testing.T) {
|
||||||
|
td := t.TempDir()
|
||||||
|
testCopyDir(t, testFixturePath("plan-vars-unset"), td)
|
||||||
|
defer testChdir(t, td)()
|
||||||
|
|
||||||
|
// The plan command will prompt for interactive input of var.src.
|
||||||
|
// We'll answer "mod" to that prompt, which should then allow this
|
||||||
|
// configuration to apply even though var.src doesn't have a
|
||||||
|
// default value and there are no -var arguments on our command line.
|
||||||
|
|
||||||
|
// This will (helpfully) panic if more than one variable is requested during plan:
|
||||||
|
// https://github.com/hashicorp/terraform/issues/26027
|
||||||
|
inputClose := testInteractiveInput(t, []string{"./mod"})
|
||||||
|
defer inputClose()
|
||||||
|
|
||||||
|
p := planVarsFixtureProvider()
|
||||||
|
view, done := testView(t)
|
||||||
|
c := &PlanCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: metaOverridesForProvider(p),
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
code := c.Run(args)
|
||||||
|
output := done(t)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test that tofu properly merges provider configuration that's split
|
// Test that tofu properly merges provider configuration that's split
|
||||||
// between config files and interactive input variables.
|
// between config files and interactive input variables.
|
||||||
// https://github.com/hashicorp/terraform/issues/28956
|
// https://github.com/hashicorp/terraform/issues/28956
|
||||||
|
11
internal/command/testdata/init-module-variable-source-multiple/main.tf
vendored
Normal file
11
internal/command/testdata/init-module-variable-source-multiple/main.tf
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
variable "src" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
module "mod" {
|
||||||
|
source = var.src
|
||||||
|
}
|
||||||
|
|
||||||
|
module "mod2" {
|
||||||
|
source = var.src
|
||||||
|
}
|
0
internal/command/testdata/init-module-variable-source-multiple/mod/mod.tf
vendored
Normal file
0
internal/command/testdata/init-module-variable-source-multiple/mod/mod.tf
vendored
Normal file
14
internal/command/testdata/plan-vars-unset/.terraform/modules/modules.json
vendored
Normal file
14
internal/command/testdata/plan-vars-unset/.terraform/modules/modules.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Key": "",
|
||||||
|
"Source": "",
|
||||||
|
"Dir": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": "mod",
|
||||||
|
"Source": "./mod",
|
||||||
|
"Dir": "mod"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
internal/command/testdata/plan-vars-unset/main.tf
vendored
Normal file
11
internal/command/testdata/plan-vars-unset/main.tf
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
variable "src" {
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
module "mod" {
|
||||||
|
source = "${var.src}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "src" {
|
||||||
|
value = "${var.src}"
|
||||||
|
}
|
3
internal/command/testdata/plan-vars-unset/mod/mod.tf
vendored
Normal file
3
internal/command/testdata/plan-vars-unset/mod/mod.tf
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
output "stuff" {
|
||||||
|
value = "things"
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user