Skip to content

Commit 2e5791a

Browse files
committed
Allow the HCL input when prompted
We already accept HCL encoded input for -vars, and this expands that to accept HCL when prompted for a value on the command line as well.
1 parent 012090f commit 2e5791a

5 files changed

Lines changed: 138 additions & 22 deletions

File tree

‎terraform/context.go‎

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -317,16 +317,18 @@ func (c *Context) Input(mode InputMode) error {
317317
}
318318
}
319319

320+
var valueType config.VariableType
321+
320322
v := m[n]
321-
switch v.Type() {
323+
switch valueType = v.Type(); valueType {
322324
case config.VariableTypeUnknown:
323325
continue
324326
case config.VariableTypeMap:
325-
continue
327+
// OK
326328
case config.VariableTypeList:
327-
continue
329+
// OK
328330
case config.VariableTypeString:
329-
// Good!
331+
// OK
330332
default:
331333
panic(fmt.Sprintf("Unknown variable type: %#v", v.Type()))
332334
}
@@ -340,6 +342,12 @@ func (c *Context) Input(mode InputMode) error {
340342
}
341343
}
342344

345+
// this should only happen during tests
346+
if c.uiInput == nil {
347+
log.Println("[WARN] Content.uiInput is nil")
348+
continue
349+
}
350+
343351
// Ask the user for a value for this variable
344352
var value string
345353
retry := 0
@@ -355,27 +363,33 @@ func (c *Context) Input(mode InputMode) error {
355363
"Error asking for %s: %s", n, err)
356364
}
357365

358-
if value == "" && v.Required() {
359-
// Redo if it is required, but abort if we keep getting
360-
// blank entries
361-
if retry > 2 {
362-
return fmt.Errorf("missing required value for %q", n)
363-
}
364-
retry++
365-
continue
366-
}
367-
368366
if value == "" {
369-
// No value, just exit the loop. With no value, we just
370-
// use whatever is currently set in variables.
371-
break
367+
if v.Required() {
368+
// Redo if it is required, but abort if we keep getting
369+
// blank entries
370+
if retry > 2 {
371+
return fmt.Errorf("missing required value for %q", n)
372+
}
373+
retry++
374+
continue
375+
}
372376
}
373377

374378
break
375379
}
376380

377-
if value != "" {
378-
c.variables[n] = value
381+
// no value provided, so don't set the variable at all
382+
if value == "" {
383+
continue
384+
}
385+
386+
decoded, err := parseVariableAsHCL(n, value, valueType)
387+
if err != nil {
388+
return err
389+
}
390+
391+
if decoded != nil {
392+
c.variables[n] = decoded
379393
}
380394
}
381395
}
@@ -656,9 +670,20 @@ func (c *Context) walk(
656670
// the name of the variable. In order to get around the restriction of HCL requiring a
657671
// top level object, we prepend a sentinel key, decode the user-specified value as its
658672
// value and pull the value back out of the resulting map.
659-
func parseVariableAsHCL(name string, input interface{}, targetType config.VariableType) (interface{}, error) {
673+
func parseVariableAsHCL(name string, input string, targetType config.VariableType) (interface{}, error) {
674+
// expecting a string so don't decode anything, just strip quotes
660675
if targetType == config.VariableTypeString {
661-
return input, nil
676+
return strings.Trim(input, `"`), nil
677+
}
678+
679+
// return empty types
680+
if strings.TrimSpace(input) == "" {
681+
switch targetType {
682+
case config.VariableTypeList:
683+
return []interface{}{}, nil
684+
case config.VariableTypeMap:
685+
return make(map[string]interface{}), nil
686+
}
662687
}
663688

664689
const sentinelValue = "SENTINEL_TERRAFORM_VAR_OVERRIDE_KEY"

‎terraform/context_input_test.go‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,44 @@ func TestContext2Input_interpolateVar(t *testing.T) {
617617
t.Fatalf("err: %s", err)
618618
}
619619
}
620+
621+
func TestContext2Input_hcl(t *testing.T) {
622+
input := new(MockUIInput)
623+
m := testModule(t, "input-hcl")
624+
p := testProvider("hcl")
625+
p.ApplyFn = testApplyFn
626+
p.DiffFn = testDiffFn
627+
ctx := testContext2(t, &ContextOpts{
628+
Module: m,
629+
Providers: map[string]ResourceProviderFactory{
630+
"hcl": testProviderFuncFixed(p),
631+
},
632+
Variables: map[string]interface{}{},
633+
UIInput: input,
634+
})
635+
636+
input.InputReturnMap = map[string]string{
637+
"var.listed": `["a", "b"]`,
638+
"var.mapped": `{x = "y", w = "z"}`,
639+
}
640+
641+
if err := ctx.Input(InputModeVar | InputModeVarUnset); err != nil {
642+
t.Fatalf("err: %s", err)
643+
}
644+
645+
if _, err := ctx.Plan(); err != nil {
646+
t.Fatalf("err: %s", err)
647+
}
648+
649+
state, err := ctx.Apply()
650+
if err != nil {
651+
t.Fatalf("err: %s", err)
652+
}
653+
654+
actualStr := strings.TrimSpace(state.String())
655+
expectedStr := strings.TrimSpace(testTerraformInputHCL)
656+
if actualStr != expectedStr {
657+
t.Logf("expected: \n%s", expectedStr)
658+
t.Fatalf("bad: \n%s", actualStr)
659+
}
660+
}

‎terraform/context_test.go‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"strings"
77
"testing"
88
"time"
9+
10+
"github.com/hashicorp/terraform/flatmap"
911
)
1012

1113
func TestNewContextState(t *testing.T) {
@@ -172,7 +174,16 @@ func testDiffFn(
172174
if reflect.DeepEqual(v, []interface{}{}) {
173175
attrDiff.New = ""
174176
} else {
175-
attrDiff.New = v.(string)
177+
if s, ok := v.(string); ok {
178+
attrDiff.New = s
179+
} else {
180+
// the value is something other than a string
181+
// flatmap it, adding the diff for each value.
182+
for k, attrDiff := range testFlatAttrDiffs(k, v) {
183+
diff.Attributes[k] = attrDiff
184+
}
185+
continue
186+
}
176187
}
177188

178189
if k == "require_new" {
@@ -219,6 +230,22 @@ func testDiffFn(
219230
return diff, nil
220231
}
221232

233+
// generate ResourceAttrDiffs for nested data structures in tests
234+
func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff {
235+
flat := flatmap.Flatten(map[string]interface{}{k: i})
236+
diffs := make(map[string]*ResourceAttrDiff)
237+
238+
for k, v := range flat {
239+
attrDiff := &ResourceAttrDiff{
240+
Old: "",
241+
New: v,
242+
}
243+
diffs[k] = attrDiff
244+
}
245+
246+
return diffs
247+
}
248+
222249
func testProvider(prefix string) *MockResourceProvider {
223250
p := new(MockResourceProvider)
224251
p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) {

‎terraform/terraform_test.go‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,3 +1413,14 @@ module.mod2:
14131413
STATE:
14141414
14151415
<no state>`
1416+
1417+
const testTerraformInputHCL = `
1418+
hcl_instance.hcltest:
1419+
ID = foo
1420+
bar.w = z
1421+
bar.x = y
1422+
foo.# = 2
1423+
foo.0 = a
1424+
foo.1 = b
1425+
type = hcl_instance
1426+
`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
variable "mapped" {
2+
type = "map"
3+
}
4+
5+
variable "listed" {
6+
type = "list"
7+
}
8+
9+
resource "hcl_instance" "hcltest" {
10+
foo = "${var.listed}"
11+
bar = "${var.mapped}"
12+
}

0 commit comments

Comments
 (0)