diff --git a/terraform/context.go b/terraform/context.go index c70634a9a..0f2473568 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -58,6 +58,22 @@ type ContextOpts struct { UIInput UIInput } +// InputMode defines what sort of input will be asked for when Input +// is called on Context. +type InputMode byte + +const ( + // InputModeVar asks for variables + InputModeVar InputMode = 1 << iota + + // InputModeProvider asks for provider variables + InputModeProvider + + // InputModeStd is the standard operating mode and asks for both variables + // and providers. + InputModeStd = InputModeVar | InputModeProvider +) + // NewContext creates a new context. // // Once a context is created, the pointer values within ContextOpts should @@ -137,75 +153,81 @@ func (c *Context) Graph() (*depgraph.Graph, error) { // Input asks for input to fill variables and provider configurations. // This modifies the configuration in-place, so asking for Input twice // may result in different UI output showing different current values. -func (c *Context) Input() error { +func (c *Context) Input(mode InputMode) error { v := c.acquireRun() defer c.releaseRun(v) - // Walk the variables first for the root module. We walk them in - // alphabetical order for UX reasons. - rootConf := c.module.Config() - names := make([]string, len(rootConf.Variables)) - m := make(map[string]*config.Variable) - for i, v := range rootConf.Variables { - names[i] = v.Name - m[v.Name] = v - } - sort.Strings(names) - for _, n := range names { - v := m[n] - switch v.Type() { - case config.VariableTypeMap: - continue - case config.VariableTypeString: - // Good! - default: - panic(fmt.Sprintf("Unknown variable type: %s", v.Type())) + if mode&InputModeVar != 0 { + // Walk the variables first for the root module. We walk them in + // alphabetical order for UX reasons. + rootConf := c.module.Config() + names := make([]string, len(rootConf.Variables)) + m := make(map[string]*config.Variable) + for i, v := range rootConf.Variables { + names[i] = v.Name + m[v.Name] = v } - - var defaultString string - if v.Default != nil { - defaultString = v.Default.(string) - } - - // Ask the user for a value for this variable - var value string - for { - var err error - value, err = c.uiInput.Input(&InputOpts{ - Id: fmt.Sprintf("var.%s", n), - Query: fmt.Sprintf("var.%s", n), - Default: defaultString, - Description: v.Description, - }) - if err != nil { - return fmt.Errorf( - "Error asking for %s: %s", n, err) - } - - if value == "" && v.Required() { - // Redo if it is required. + sort.Strings(names) + for _, n := range names { + v := m[n] + switch v.Type() { + case config.VariableTypeMap: continue + case config.VariableTypeString: + // Good! + default: + panic(fmt.Sprintf("Unknown variable type: %s", v.Type())) } - if value == "" { - // No value, just exit the loop. With no value, we just - // use whatever is currently set in variables. + var defaultString string + if v.Default != nil { + defaultString = v.Default.(string) + } + + // Ask the user for a value for this variable + var value string + for { + var err error + value, err = c.uiInput.Input(&InputOpts{ + Id: fmt.Sprintf("var.%s", n), + Query: fmt.Sprintf("var.%s", n), + Default: defaultString, + Description: v.Description, + }) + if err != nil { + return fmt.Errorf( + "Error asking for %s: %s", n, err) + } + + if value == "" && v.Required() { + // Redo if it is required. + 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 != "" { - c.variables[n] = value + if value != "" { + c.variables[n] = value + } } } - // Create the walk context and walk the inputs, which will gather the - // inputs for any resource providers. - wc := c.walkContext(walkInput, rootModulePath) - wc.Meta = new(walkInputMeta) - return wc.Walk() + if mode&InputModeProvider != 0 { + // Create the walk context and walk the inputs, which will gather the + // inputs for any resource providers. + wc := c.walkContext(walkInput, rootModulePath) + wc.Meta = new(walkInputMeta) + return wc.Walk() + } + + return nil } // Plan generates an execution plan for the given context. diff --git a/terraform/context_test.go b/terraform/context_test.go index 8134dc674..11b0e4e7b 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -460,7 +460,7 @@ func TestContextInput(t *testing.T) { "var.foo": "us-east-1", } - if err := ctx.Input(); err != nil { + if err := ctx.Input(InputModeStd); err != nil { t.Fatalf("err: %s", err) } @@ -502,7 +502,7 @@ func TestContextInput_provider(t *testing.T) { return nil } - if err := ctx.Input(); err != nil { + if err := ctx.Input(InputModeStd); err != nil { t.Fatalf("err: %s", err) } @@ -552,7 +552,7 @@ func TestContextInput_providerId(t *testing.T) { "provider.aws.foo": "bar", } - if err := ctx.Input(); err != nil { + if err := ctx.Input(InputModeStd); err != nil { t.Fatalf("err: %s", err) } @@ -569,6 +569,116 @@ func TestContextInput_providerId(t *testing.T) { } } +func TestContextInput_providerOnly(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-provider-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "us-west-2", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "us-east-1", + } + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Raw["foo"] = "bar" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual = c.Raw["foo"] + return nil + } + + if err := ctx.Input(InputModeProvider); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } + + actualStr := strings.TrimSpace(state.String()) + expectedStr := strings.TrimSpace(testTerraformInputProviderOnlyStr) + if actualStr != expectedStr { + t.Fatalf("bad: \n%s", actualStr) + } +} + +func TestContextInput_varOnly(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-provider-vars") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "us-west-2", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "us-east-1", + } + + var actual interface{} + p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { + c.Raw["foo"] = "bar" + return c, nil + } + p.ConfigureFn = func(c *ResourceConfig) error { + actual = c.Raw["foo"] + return nil + } + + if err := ctx.Input(InputModeVar); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + if reflect.DeepEqual(actual, "bar") { + t.Fatalf("bad: %#v", actual) + } + + actualStr := strings.TrimSpace(state.String()) + expectedStr := strings.TrimSpace(testTerraformInputVarOnlyStr) + if actualStr != expectedStr { + t.Fatalf("bad: \n%s", actualStr) + } +} + func TestContextApply(t *testing.T) { m := testModule(t, "apply-good") p := testProvider("aws") diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 6aedf6447..b620d23c5 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -126,6 +126,20 @@ aws_instance.foo: type = aws_instance ` +const testTerraformInputProviderOnlyStr = ` +aws_instance.foo: + ID = foo + foo = us-west-2 + type = aws_instance +` + +const testTerraformInputVarOnlyStr = ` +aws_instance.foo: + ID = foo + foo = us-east-1 + type = aws_instance +` + const testTerraformInputVarsStr = ` aws_instance.bar: ID = foo diff --git a/terraform/test-fixtures/input-provider-vars/main.tf b/terraform/test-fixtures/input-provider-vars/main.tf new file mode 100644 index 000000000..692bfb30f --- /dev/null +++ b/terraform/test-fixtures/input-provider-vars/main.tf @@ -0,0 +1,5 @@ +variable "foo" {} + +resource "aws_instance" "foo" { + foo = "${var.foo}" +}