mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-23 07:33: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()
|
||||
view, _ := testView(t)
|
||||
closeInput := testInteractiveInput(t, []string{"./mod"})
|
||||
defer closeInput()
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
@ -2937,13 +2939,54 @@ func TestInit_moduleSource(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
if code := c.Run(nil); code != 1 {
|
||||
t.Fatalf("got exit status %d; want 1\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
||||
if code := c.Run(nil); code != 0 {
|
||||
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 !strings.Contains(errStr, `Variable not provided`) {
|
||||
t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
|
||||
if code := c.Run(nil); code != 0 {
|
||||
t.Fatalf("got exit status %d; want 0\nstderr:\n%s\n\nstdout:\n%s", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
|
||||
}
|
||||
})
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@ -129,18 +130,26 @@ func (m *Meta) rootModuleCall(rootDir string) (configs.StaticModuleCall, tfdiags
|
||||
name := variable.Name
|
||||
v, ok := variables[name]
|
||||
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() {
|
||||
// Not specified on CLI or in var files, without a valid default.
|
||||
return cty.NilVal, hcl.Diagnostics{&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Variable not provided via -var, tfvars files, or env",
|
||||
Subject: variable.DeclRange.Ptr(),
|
||||
}}
|
||||
// User prompts are best efforts, so we accept the input here
|
||||
// and rely on downstream checks to validate it
|
||||
rawVal, err := m.getInput(context.Background(), variable)
|
||||
if err != nil {
|
||||
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)
|
||||
@ -150,6 +159,24 @@ func (m *Meta) rootModuleCall(rootDir string) (configs.StaticModuleCall, tfdiags
|
||||
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
|
||||
// tests for the target module.
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
// between config files and interactive input variables.
|
||||
// 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