2023-05-02 11:33:06 -04:00
|
|
|
// Copyright IBM Corp. 2014, 2026
|
2023-08-10 18:43:27 -04:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-05-02 11:33:06 -04:00
|
|
|
|
2016-11-14 01:18:18 -05:00
|
|
|
package command
|
|
|
|
|
|
2016-11-30 14:36:54 -05:00
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
2023-12-20 06:04:10 -05:00
|
|
|
"github.com/hashicorp/cli"
|
2021-05-17 15:17:09 -04:00
|
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
2021-05-17 13:40:40 -04:00
|
|
|
"github.com/hashicorp/terraform/internal/providers"
|
2020-08-07 14:04:40 -04:00
|
|
|
"github.com/zclconf/go-cty/cty"
|
2016-11-30 14:36:54 -05:00
|
|
|
)
|
|
|
|
|
|
2016-11-14 01:18:18 -05:00
|
|
|
// ConsoleCommand is tested primarily with tests in the "repl" package.
|
|
|
|
|
// It is not tested here because the Console uses a readline-like library
|
|
|
|
|
// that takes over stdin/stdout. It is difficult to test directly. The
|
|
|
|
|
// core logic is tested in "repl"
|
2016-11-30 14:36:54 -05:00
|
|
|
//
|
|
|
|
|
// This file still contains some tests using the stdin-based input.
|
|
|
|
|
|
|
|
|
|
func TestConsole_basic(t *testing.T) {
|
2025-07-16 06:41:51 -04:00
|
|
|
tmp := t.TempDir()
|
|
|
|
|
t.Chdir(tmp)
|
2016-11-30 14:36:54 -05:00
|
|
|
|
|
|
|
|
p := testProvider()
|
2020-09-09 14:13:53 -04:00
|
|
|
ui := cli.NewMockUi()
|
2021-02-16 07:19:22 -05:00
|
|
|
view, _ := testView(t)
|
2016-11-30 14:36:54 -05:00
|
|
|
c := &ConsoleCommand{
|
|
|
|
|
Meta: Meta{
|
2017-04-13 21:05:58 -04:00
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
|
Ui: ui,
|
2021-02-16 07:19:22 -05:00
|
|
|
View: view,
|
2016-11-30 14:36:54 -05:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
defer testStdinPipe(t, strings.NewReader("1+5\n"))()
|
|
|
|
|
outCloser := testStdoutCapture(t, &output)
|
|
|
|
|
|
|
|
|
|
args := []string{}
|
|
|
|
|
code := c.Run(args)
|
|
|
|
|
outCloser()
|
|
|
|
|
if code != 0 {
|
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual := output.String()
|
|
|
|
|
if actual != "6\n" {
|
|
|
|
|
t.Fatalf("bad: %q", actual)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestConsole_tfvars(t *testing.T) {
|
2022-04-08 12:34:16 -04:00
|
|
|
td := t.TempDir()
|
2021-02-02 10:35:45 -05:00
|
|
|
testCopyDir(t, testFixturePath("apply-vars"), td)
|
2025-07-16 11:04:10 -04:00
|
|
|
t.Chdir(td)
|
2016-11-30 14:36:54 -05:00
|
|
|
|
|
|
|
|
// Write a terraform.tvars
|
2021-02-02 10:35:45 -05:00
|
|
|
varFilePath := filepath.Join(td, "terraform.tfvars")
|
2016-11-30 14:36:54 -05:00
|
|
|
if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil {
|
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p := testProvider()
|
2021-02-18 10:13:43 -05:00
|
|
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
2021-01-12 16:13:10 -05:00
|
|
|
ResourceTypes: map[string]providers.Schema{
|
2020-08-07 14:04:40 -04:00
|
|
|
"test_instance": {
|
2025-03-04 10:33:43 -05:00
|
|
|
Body: &configschema.Block{
|
2021-01-12 16:13:10 -05:00
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
|
"value": {Type: cty.String, Optional: true},
|
|
|
|
|
},
|
2020-08-07 14:04:40 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
2020-09-09 14:13:53 -04:00
|
|
|
ui := cli.NewMockUi()
|
2021-02-16 07:19:22 -05:00
|
|
|
view, _ := testView(t)
|
2016-11-30 14:36:54 -05:00
|
|
|
c := &ConsoleCommand{
|
|
|
|
|
Meta: Meta{
|
2017-04-13 21:05:58 -04:00
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
|
Ui: ui,
|
2021-02-16 07:19:22 -05:00
|
|
|
View: view,
|
2016-11-30 14:36:54 -05:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
defer testStdinPipe(t, strings.NewReader("var.foo\n"))()
|
|
|
|
|
outCloser := testStdoutCapture(t, &output)
|
|
|
|
|
|
2021-02-02 10:35:45 -05:00
|
|
|
args := []string{}
|
2016-11-30 14:36:54 -05:00
|
|
|
code := c.Run(args)
|
|
|
|
|
outCloser()
|
|
|
|
|
if code != 0 {
|
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual := output.String()
|
2020-09-09 14:13:53 -04:00
|
|
|
if actual != "\"bar\"\n" {
|
2016-11-30 14:36:54 -05:00
|
|
|
t.Fatalf("bad: %q", actual)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-09 17:29:40 -04:00
|
|
|
|
|
|
|
|
func TestConsole_unsetRequiredVars(t *testing.T) {
|
|
|
|
|
// This test is verifying that it's possible to run "terraform console"
|
|
|
|
|
// without providing values for all required variables, without
|
|
|
|
|
// "terraform console" producing an interactive prompt for those variables
|
|
|
|
|
// or producing errors. Instead, it should allow evaluation in that
|
|
|
|
|
// partial context but see the unset variables values as being unknown.
|
2021-02-02 10:35:45 -05:00
|
|
|
//
|
|
|
|
|
// This test fixture includes variable "foo" {}, which we are
|
|
|
|
|
// intentionally not setting here.
|
2022-04-08 12:34:16 -04:00
|
|
|
td := t.TempDir()
|
2021-02-02 10:35:45 -05:00
|
|
|
testCopyDir(t, testFixturePath("apply-vars"), td)
|
2025-07-16 11:04:10 -04:00
|
|
|
t.Chdir(td)
|
2019-10-09 17:29:40 -04:00
|
|
|
|
|
|
|
|
p := testProvider()
|
2021-02-18 10:13:43 -05:00
|
|
|
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
2021-01-12 16:13:10 -05:00
|
|
|
ResourceTypes: map[string]providers.Schema{
|
2020-08-07 14:04:40 -04:00
|
|
|
"test_instance": {
|
2025-03-04 10:33:43 -05:00
|
|
|
Body: &configschema.Block{
|
2021-01-12 16:13:10 -05:00
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
|
"value": {Type: cty.String, Optional: true},
|
|
|
|
|
},
|
2020-08-07 14:04:40 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
2020-09-09 14:13:53 -04:00
|
|
|
ui := cli.NewMockUi()
|
2021-02-16 07:19:22 -05:00
|
|
|
view, _ := testView(t)
|
2019-10-09 17:29:40 -04:00
|
|
|
c := &ConsoleCommand{
|
|
|
|
|
Meta: Meta{
|
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
|
Ui: ui,
|
2021-02-16 07:19:22 -05:00
|
|
|
View: view,
|
2019-10-09 17:29:40 -04:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
defer testStdinPipe(t, strings.NewReader("var.foo\n"))()
|
|
|
|
|
outCloser := testStdoutCapture(t, &output)
|
|
|
|
|
|
2021-02-02 10:35:45 -05:00
|
|
|
args := []string{}
|
2019-10-09 17:29:40 -04:00
|
|
|
code := c.Run(args)
|
|
|
|
|
outCloser()
|
|
|
|
|
|
2020-09-09 14:13:53 -04:00
|
|
|
if code != 0 {
|
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
2019-10-09 17:29:40 -04:00
|
|
|
}
|
|
|
|
|
|
2020-09-09 14:13:53 -04:00
|
|
|
if got, want := output.String(), "(known after apply)\n"; got != want {
|
|
|
|
|
t.Fatalf("unexpected output\n got: %q\nwant: %q", got, want)
|
2019-10-09 17:29:40 -04:00
|
|
|
}
|
|
|
|
|
}
|
2020-04-30 09:21:42 -04:00
|
|
|
|
2020-10-05 12:37:36 -04:00
|
|
|
func TestConsole_variables(t *testing.T) {
|
2022-04-08 12:34:16 -04:00
|
|
|
td := t.TempDir()
|
2021-02-02 10:35:45 -05:00
|
|
|
testCopyDir(t, testFixturePath("variables"), td)
|
2025-07-16 11:04:10 -04:00
|
|
|
t.Chdir(td)
|
2020-10-05 12:37:36 -04:00
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
|
ui := cli.NewMockUi()
|
2021-02-16 07:19:22 -05:00
|
|
|
view, _ := testView(t)
|
2020-10-05 12:37:36 -04:00
|
|
|
c := &ConsoleCommand{
|
|
|
|
|
Meta: Meta{
|
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
|
Ui: ui,
|
2021-02-16 07:19:22 -05:00
|
|
|
View: view,
|
2020-10-05 12:37:36 -04:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commands := map[string]string{
|
|
|
|
|
"var.foo\n": "\"bar\"\n",
|
|
|
|
|
"var.snack\n": "\"popcorn\"\n",
|
2022-10-20 12:09:21 -04:00
|
|
|
"var.secret_snack\n": "(sensitive value)\n",
|
|
|
|
|
"local.snack_bar\n": "[\n \"popcorn\",\n (sensitive value),\n]\n",
|
2020-10-05 12:37:36 -04:00
|
|
|
}
|
|
|
|
|
|
2021-02-02 10:35:45 -05:00
|
|
|
args := []string{}
|
2020-10-05 12:37:36 -04:00
|
|
|
|
|
|
|
|
for cmd, val := range commands {
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
defer testStdinPipe(t, strings.NewReader(cmd))()
|
|
|
|
|
outCloser := testStdoutCapture(t, &output)
|
|
|
|
|
code := c.Run(args)
|
|
|
|
|
outCloser()
|
|
|
|
|
if code != 0 {
|
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual := output.String()
|
|
|
|
|
if output.String() != val {
|
|
|
|
|
t.Fatalf("bad: %q, expected %q", actual, val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-30 09:21:42 -04:00
|
|
|
func TestConsole_modules(t *testing.T) {
|
2022-04-08 12:34:16 -04:00
|
|
|
td := t.TempDir()
|
2020-10-07 12:48:25 -04:00
|
|
|
testCopyDir(t, testFixturePath("modules"), td)
|
2025-07-16 11:04:10 -04:00
|
|
|
t.Chdir(td)
|
2020-04-30 09:21:42 -04:00
|
|
|
|
2020-08-07 14:04:40 -04:00
|
|
|
p := applyFixtureProvider()
|
2020-09-09 14:13:53 -04:00
|
|
|
ui := cli.NewMockUi()
|
2021-02-16 07:19:22 -05:00
|
|
|
view, _ := testView(t)
|
2020-04-30 09:21:42 -04:00
|
|
|
|
|
|
|
|
c := &ConsoleCommand{
|
|
|
|
|
Meta: Meta{
|
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
|
Ui: ui,
|
2021-02-16 07:19:22 -05:00
|
|
|
View: view,
|
2020-04-30 09:21:42 -04:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commands := map[string]string{
|
2020-09-09 14:13:53 -04:00
|
|
|
"module.child.myoutput\n": "\"bar\"\n",
|
|
|
|
|
"module.count_child[0].myoutput\n": "\"bar\"\n",
|
2020-04-30 09:21:42 -04:00
|
|
|
"local.foo\n": "3\n",
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 10:35:45 -05:00
|
|
|
args := []string{}
|
2020-04-30 09:21:42 -04:00
|
|
|
|
|
|
|
|
for cmd, val := range commands {
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
defer testStdinPipe(t, strings.NewReader(cmd))()
|
|
|
|
|
outCloser := testStdoutCapture(t, &output)
|
|
|
|
|
code := c.Run(args)
|
|
|
|
|
outCloser()
|
|
|
|
|
if code != 0 {
|
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual := output.String()
|
|
|
|
|
if output.String() != val {
|
|
|
|
|
t.Fatalf("bad: %q, expected %q", actual, val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-11-29 20:13:05 -05:00
|
|
|
|
|
|
|
|
func TestConsole_modulesPlan(t *testing.T) {
|
|
|
|
|
td := t.TempDir()
|
|
|
|
|
testCopyDir(t, testFixturePath("apply"), td)
|
2025-07-16 11:04:10 -04:00
|
|
|
t.Chdir(td)
|
2023-11-29 20:13:05 -05:00
|
|
|
|
|
|
|
|
p := applyFixtureProvider()
|
|
|
|
|
ui := cli.NewMockUi()
|
|
|
|
|
view, _ := testView(t)
|
|
|
|
|
|
|
|
|
|
c := &ConsoleCommand{
|
|
|
|
|
Meta: Meta{
|
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
|
Ui: ui,
|
|
|
|
|
View: view,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commands := map[string]string{
|
|
|
|
|
"test_instance.foo.ami\n": "\"bar\"\n",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The -plan option means that we'll be evaluating expressions against
|
|
|
|
|
// a plan constructed from this configuration, instead of against its
|
|
|
|
|
// (non-existent) prior state.
|
|
|
|
|
args := []string{"-plan"}
|
|
|
|
|
|
|
|
|
|
for cmd, val := range commands {
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
defer testStdinPipe(t, strings.NewReader(cmd))()
|
|
|
|
|
outCloser := testStdoutCapture(t, &output)
|
|
|
|
|
code := c.Run(args)
|
|
|
|
|
outCloser()
|
|
|
|
|
if code != 0 {
|
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actual := output.String()
|
|
|
|
|
if output.String() != val {
|
|
|
|
|
t.Fatalf("bad: %q, expected %q", actual, val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|