Merge pull request #8081 from hashicorp/jbardin/input-hcl
Allow the HCL input when prompted
This commit is contained in:
commit
ea2e875ea1
|
@ -317,16 +317,18 @@ func (c *Context) Input(mode InputMode) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var valueType config.VariableType
|
||||||
|
|
||||||
v := m[n]
|
v := m[n]
|
||||||
switch v.Type() {
|
switch valueType = v.Type(); valueType {
|
||||||
case config.VariableTypeUnknown:
|
case config.VariableTypeUnknown:
|
||||||
continue
|
continue
|
||||||
case config.VariableTypeMap:
|
case config.VariableTypeMap:
|
||||||
continue
|
// OK
|
||||||
case config.VariableTypeList:
|
case config.VariableTypeList:
|
||||||
continue
|
// OK
|
||||||
case config.VariableTypeString:
|
case config.VariableTypeString:
|
||||||
// Good!
|
// OK
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("Unknown variable type: %#v", v.Type()))
|
panic(fmt.Sprintf("Unknown variable type: %#v", v.Type()))
|
||||||
}
|
}
|
||||||
|
@ -340,6 +342,12 @@ func (c *Context) Input(mode InputMode) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this should only happen during tests
|
||||||
|
if c.uiInput == nil {
|
||||||
|
log.Println("[WARN] Content.uiInput is nil")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Ask the user for a value for this variable
|
// Ask the user for a value for this variable
|
||||||
var value string
|
var value string
|
||||||
retry := 0
|
retry := 0
|
||||||
|
@ -365,17 +373,21 @@ func (c *Context) Input(mode InputMode) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if value == "" {
|
|
||||||
// No value, just exit the loop. With no value, we just
|
|
||||||
// use whatever is currently set in variables.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if value != "" {
|
// no value provided, so don't set the variable at all
|
||||||
c.variables[n] = value
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := parseVariableAsHCL(n, value, valueType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if decoded != nil {
|
||||||
|
c.variables[n] = decoded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -656,9 +668,20 @@ func (c *Context) walk(
|
||||||
// the name of the variable. In order to get around the restriction of HCL requiring a
|
// the name of the variable. In order to get around the restriction of HCL requiring a
|
||||||
// top level object, we prepend a sentinel key, decode the user-specified value as its
|
// top level object, we prepend a sentinel key, decode the user-specified value as its
|
||||||
// value and pull the value back out of the resulting map.
|
// value and pull the value back out of the resulting map.
|
||||||
func parseVariableAsHCL(name string, input interface{}, targetType config.VariableType) (interface{}, error) {
|
func parseVariableAsHCL(name string, input string, targetType config.VariableType) (interface{}, error) {
|
||||||
|
// expecting a string so don't decode anything, just strip quotes
|
||||||
if targetType == config.VariableTypeString {
|
if targetType == config.VariableTypeString {
|
||||||
return input, nil
|
return strings.Trim(input, `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// return empty types
|
||||||
|
if strings.TrimSpace(input) == "" {
|
||||||
|
switch targetType {
|
||||||
|
case config.VariableTypeList:
|
||||||
|
return []interface{}{}, nil
|
||||||
|
case config.VariableTypeMap:
|
||||||
|
return make(map[string]interface{}), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sentinelValue = "SENTINEL_TERRAFORM_VAR_OVERRIDE_KEY"
|
const sentinelValue = "SENTINEL_TERRAFORM_VAR_OVERRIDE_KEY"
|
||||||
|
|
|
@ -617,3 +617,44 @@ func TestContext2Input_interpolateVar(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Input_hcl(t *testing.T) {
|
||||||
|
input := new(MockUIInput)
|
||||||
|
m := testModule(t, "input-hcl")
|
||||||
|
p := testProvider("hcl")
|
||||||
|
p.ApplyFn = testApplyFn
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"hcl": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
Variables: map[string]interface{}{},
|
||||||
|
UIInput: input,
|
||||||
|
})
|
||||||
|
|
||||||
|
input.InputReturnMap = map[string]string{
|
||||||
|
"var.listed": `["a", "b"]`,
|
||||||
|
"var.mapped": `{x = "y", w = "z"}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Input(InputModeVar | InputModeVarUnset); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ctx.Plan(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := ctx.Apply()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualStr := strings.TrimSpace(state.String())
|
||||||
|
expectedStr := strings.TrimSpace(testTerraformInputHCL)
|
||||||
|
if actualStr != expectedStr {
|
||||||
|
t.Logf("expected: \n%s", expectedStr)
|
||||||
|
t.Fatalf("bad: \n%s", actualStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1223,6 +1223,7 @@ func TestContext2Plan_countZero(t *testing.T) {
|
||||||
actual := strings.TrimSpace(plan.String())
|
actual := strings.TrimSpace(plan.String())
|
||||||
expected := strings.TrimSpace(testTerraformPlanCountZeroStr)
|
expected := strings.TrimSpace(testTerraformPlanCountZeroStr)
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
|
t.Logf("expected:\n%s", expected)
|
||||||
t.Fatalf("bad:\n%s", actual)
|
t.Fatalf("bad:\n%s", actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/flatmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewContextState(t *testing.T) {
|
func TestNewContextState(t *testing.T) {
|
||||||
|
@ -165,23 +166,15 @@ func testDiffFn(
|
||||||
v = c.Config[k]
|
v = c.Config[k]
|
||||||
}
|
}
|
||||||
|
|
||||||
attrDiff := &ResourceAttrDiff{
|
for k, attrDiff := range testFlatAttrDiffs(k, v) {
|
||||||
Old: "",
|
if k == "require_new" {
|
||||||
|
attrDiff.RequiresNew = true
|
||||||
|
}
|
||||||
|
if _, ok := c.Raw["__"+k+"_requires_new"]; ok {
|
||||||
|
attrDiff.RequiresNew = true
|
||||||
|
}
|
||||||
|
diff.Attributes[k] = attrDiff
|
||||||
}
|
}
|
||||||
|
|
||||||
if reflect.DeepEqual(v, []interface{}{}) {
|
|
||||||
attrDiff.New = ""
|
|
||||||
} else {
|
|
||||||
attrDiff.New = v.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
if k == "require_new" {
|
|
||||||
attrDiff.RequiresNew = true
|
|
||||||
}
|
|
||||||
if _, ok := c.Raw["__"+k+"_requires_new"]; ok {
|
|
||||||
attrDiff.RequiresNew = true
|
|
||||||
}
|
|
||||||
diff.Attributes[k] = attrDiff
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, k := range c.ComputedKeys {
|
for _, k := range c.ComputedKeys {
|
||||||
|
@ -219,6 +212,39 @@ func testDiffFn(
|
||||||
return diff, nil
|
return diff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate ResourceAttrDiffs for nested data structures in tests
|
||||||
|
func testFlatAttrDiffs(k string, i interface{}) map[string]*ResourceAttrDiff {
|
||||||
|
diffs := make(map[string]*ResourceAttrDiff)
|
||||||
|
// check for strings and empty containers first
|
||||||
|
switch t := i.(type) {
|
||||||
|
case string:
|
||||||
|
diffs[k] = &ResourceAttrDiff{New: t}
|
||||||
|
return diffs
|
||||||
|
case map[string]interface{}:
|
||||||
|
if len(t) == 0 {
|
||||||
|
diffs[k] = &ResourceAttrDiff{New: ""}
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if len(t) == 0 {
|
||||||
|
diffs[k] = &ResourceAttrDiff{New: ""}
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flat := flatmap.Flatten(map[string]interface{}{k: i})
|
||||||
|
|
||||||
|
for k, v := range flat {
|
||||||
|
attrDiff := &ResourceAttrDiff{
|
||||||
|
Old: "",
|
||||||
|
New: v,
|
||||||
|
}
|
||||||
|
diffs[k] = attrDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
|
||||||
func testProvider(prefix string) *MockResourceProvider {
|
func testProvider(prefix string) *MockResourceProvider {
|
||||||
p := new(MockResourceProvider)
|
p := new(MockResourceProvider)
|
||||||
p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) {
|
p.RefreshFn = func(info *InstanceInfo, s *InstanceState) (*InstanceState, error) {
|
||||||
|
|
|
@ -1413,3 +1413,14 @@ module.mod2:
|
||||||
STATE:
|
STATE:
|
||||||
|
|
||||||
<no state>`
|
<no state>`
|
||||||
|
|
||||||
|
const testTerraformInputHCL = `
|
||||||
|
hcl_instance.hcltest:
|
||||||
|
ID = foo
|
||||||
|
bar.w = z
|
||||||
|
bar.x = y
|
||||||
|
foo.# = 2
|
||||||
|
foo.0 = a
|
||||||
|
foo.1 = b
|
||||||
|
type = hcl_instance
|
||||||
|
`
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
variable "mapped" {
|
||||||
|
type = "map"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "listed" {
|
||||||
|
type = "list"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcl_instance" "hcltest" {
|
||||||
|
foo = "${var.listed}"
|
||||||
|
bar = "${var.mapped}"
|
||||||
|
}
|
Loading…
Reference in New Issue