Adds prompt for missing static variables (#2047)

Signed-off-by: Andrew Hayes <andrew.hayes@harness.io>
This commit is contained in:
Andy Hayes 2024-10-07 18:30:42 +01:00 committed by GitHub
parent 26fd6394c7
commit de69070b02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 163 additions and 15 deletions

View File

@ -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())
}
})

View File

@ -8,6 +8,7 @@ package command
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"sort"
@ -129,19 +130,27 @@ 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.
// 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: "Variable not provided via -var, tfvars files, or env",
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
}
}
parsed, parsedDiags := v.ParseVariableValue(variable.ParsingMode)
return parsed.Value, parsedDiags.ToHCL()
@ -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) {

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,11 @@
variable "src" {
type = string
}
module "mod" {
source = var.src
}
module "mod2" {
source = var.src
}

View File

@ -0,0 +1,14 @@
{
"Modules": [
{
"Key": "",
"Source": "",
"Dir": "."
},
{
"Key": "mod",
"Source": "./mod",
"Dir": "mod"
}
]
}

View File

@ -0,0 +1,11 @@
variable "src" {
type = string
}
module "mod" {
source = "${var.src}"
}
resource "test_instance" "src" {
value = "${var.src}"
}

View File

@ -0,0 +1,3 @@
output "stuff" {
value = "things"
}