diff --git a/backend/local/backend_apply.go b/backend/local/backend_apply.go index d665172ec..8263a2089 100644 --- a/backend/local/backend_apply.go +++ b/backend/local/backend_apply.go @@ -112,7 +112,7 @@ func (b *Local) opApply( b.CLI.Output("") } - v, err := op.UIIn.Input(&terraform.InputOpts{ + v, err := op.UIIn.Input(stopCtx, &terraform.InputOpts{ Id: "approve", Query: query, Description: desc, diff --git a/backend/remote/backend.go b/backend/remote/backend.go index 461b9048a..9e7f04d56 100644 --- a/backend/remote/backend.go +++ b/backend/remote/backend.go @@ -15,7 +15,6 @@ import ( version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state/remote" "github.com/hashicorp/terraform/svchost" @@ -69,9 +68,6 @@ type Remote struct { // configuration. prefix string - // schema defines the configuration for the backend. - schema *schema.Backend - // services is used for service discovery services *disco.Disco @@ -726,13 +722,13 @@ func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend } func (b *Remote) cancel(cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error { - if r.Status == tfe.RunPending && r.Actions.IsCancelable { + if r.Actions.IsCancelable { // Only ask if the remote operation should be canceled // if the auto approve flag is not set. if !op.AutoApprove { - v, err := op.UIIn.Input(&terraform.InputOpts{ + v, err := op.UIIn.Input(cancelCtx, &terraform.InputOpts{ Id: "cancel", - Query: "\nDo you want to cancel the pending remote operation?", + Query: "\nDo you want to cancel the remote operation?", Description: "Only 'yes' will be accepted to cancel.", }) if err != nil { diff --git a/backend/remote/backend_common.go b/backend/remote/backend_common.go index 5da116067..662915239 100644 --- a/backend/remote/backend_common.go +++ b/backend/remote/backend_common.go @@ -313,7 +313,7 @@ func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Ope } func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *terraform.InputOpts, r *tfe.Run, keyword string) error { - v, err := op.UIIn.Input(opts) + v, err := op.UIIn.Input(stopCtx, opts) if err != nil { return fmt.Errorf("Error asking %s: %v", opts.Id, err) } diff --git a/backend/remote/backend_mock.go b/backend/remote/backend_mock.go index 4fe547477..1f0e1fd33 100644 --- a/backend/remote/backend_mock.go +++ b/backend/remote/backend_mock.go @@ -217,7 +217,7 @@ type mockInput struct { answers map[string]string } -func (m *mockInput) Input(opts *terraform.InputOpts) (string, error) { +func (m *mockInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { v, ok := m.answers[opts.Id] if !ok { return "", fmt.Errorf("unexpected input request in test: %s", opts.Id) diff --git a/command/012_config_upgrade.go b/command/012_config_upgrade.go index 28a57ff73..856c8349a 100644 --- a/command/012_config_upgrade.go +++ b/command/012_config_upgrade.go @@ -1,6 +1,7 @@ package command import ( + "context" "fmt" "io/ioutil" "os" @@ -127,7 +128,7 @@ command and dealing with them before running this command again. if dir != "." { query = fmt.Sprintf("Would you like to upgrade the module in %s?", dir) } - v, err := c.UIInput().Input(&terraform.InputOpts{ + v, err := c.UIInput().Input(context.Background(), &terraform.InputOpts{ Id: "approve", Query: query, Description: `Only 'yes' will be accepted to confirm.`, diff --git a/command/meta.go b/command/meta.go index 7658520e6..bb5f6d220 100644 --- a/command/meta.go +++ b/command/meta.go @@ -128,7 +128,7 @@ type Meta struct { // // stateOutPath is used to override the output path for the state. // If not provided, the StatePath is used causing the old state to - // be overriden. + // be overridden. // // backupPath is used to backup the state file before writing a modified // version. It defaults to stateOutPath + DefaultBackupExtension @@ -470,7 +470,7 @@ func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) { } for i := 0; i < 2; i++ { - v, err := m.UIInput().Input(opts) + v, err := m.UIInput().Input(context.Background(), opts) if err != nil { return false, fmt.Errorf( "Error asking for confirmation: %s", err) diff --git a/command/meta_backend.go b/command/meta_backend.go index de242932d..bfaa9b44e 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -773,7 +773,7 @@ func (m *Meta) selectWorkspace(b backend.Backend) error { // If the selected workspace is not migrated, ask the user to select // a workspace from the list of migrated workspaces. - v, err := m.UIInput().Input(&terraform.InputOpts{ + v, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{ Id: "select-workspace", Query: fmt.Sprintf( "\n[reset][bold][yellow]The currently selected workspace (%s) is not migrated.[reset]", diff --git a/command/meta_backend_migrate.go b/command/meta_backend_migrate.go index 79ba0c8fe..9227844a1 100644 --- a/command/meta_backend_migrate.go +++ b/command/meta_backend_migrate.go @@ -242,7 +242,7 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error { // for a new name and migrate the default state to the given named state. stateTwo, err = func() (statemgr.Full, error) { log.Print("[TRACE] backendMigrateState: target doesn't support a default workspace, so we must prompt for a new name") - name, err := m.UIInput().Input(&terraform.InputOpts{ + name, err := m.UIInput().Input(context.Background(), &terraform.InputOpts{ Id: "new-state-name", Query: fmt.Sprintf( "[reset][bold][yellow]The %q backend configuration only allows "+ diff --git a/command/meta_config.go b/command/meta_config.go index e791c3c36..33264498f 100644 --- a/command/meta_config.go +++ b/command/meta_config.go @@ -1,8 +1,8 @@ package command import ( + "context" "fmt" - "github.com/hashicorp/terraform/internal/earlyconfig" "os" "path/filepath" "sort" @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs/configload" "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/internal/earlyconfig" "github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/registry" "github.com/hashicorp/terraform/terraform" @@ -285,7 +286,7 @@ func (m *Meta) inputForSchema(given cty.Value, schema *configschema.Block) (cty. attrS := schema.Attributes[name] for { - strVal, err := input.Input(&terraform.InputOpts{ + strVal, err := input.Input(context.Background(), &terraform.InputOpts{ Id: name, Query: name, Description: attrS.Description, diff --git a/command/ui_input.go b/command/ui_input.go index 9c8873d46..0e392f3b1 100644 --- a/command/ui_input.go +++ b/command/ui_input.go @@ -3,6 +3,7 @@ package command import ( "bufio" "bytes" + "context" "errors" "fmt" "io" @@ -38,7 +39,7 @@ type UIInput struct { once sync.Once } -func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) { +func (i *UIInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { i.once.Do(i.init) r := i.Reader @@ -137,6 +138,12 @@ func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) { } return line, nil + case <-ctx.Done(): + // Print a newline so that any further output starts properly + // on a new line. + fmt.Fprintln(w) + + return "", ctx.Err() case <-sigCh: // Print a newline so that any further output starts properly // on a new line. diff --git a/command/ui_input_test.go b/command/ui_input_test.go index 0da64a99c..4d12a4d6d 100644 --- a/command/ui_input_test.go +++ b/command/ui_input_test.go @@ -2,6 +2,7 @@ package command import ( "bytes" + "context" "testing" "github.com/hashicorp/terraform/terraform" @@ -17,7 +18,7 @@ func TestUIInputInput(t *testing.T) { Writer: bytes.NewBuffer(nil), } - v, err := i.Input(&terraform.InputOpts{}) + v, err := i.Input(context.Background(), &terraform.InputOpts{}) if err != nil { t.Fatalf("err: %s", err) } @@ -33,7 +34,7 @@ func TestUIInputInput_spaces(t *testing.T) { Writer: bytes.NewBuffer(nil), } - v, err := i.Input(&terraform.InputOpts{}) + v, err := i.Input(context.Background(), &terraform.InputOpts{}) if err != nil { t.Fatalf("err: %s", err) } diff --git a/command/unlock.go b/command/unlock.go index 8d8ec3034..8dc7cafcc 100644 --- a/command/unlock.go +++ b/command/unlock.go @@ -1,6 +1,7 @@ package command import ( + "context" "fmt" "strings" @@ -88,7 +89,7 @@ func (c *UnlockCommand) Run(args []string) int { "This will allow local Terraform commands to modify this state, even though it\n" + "may be still be in use. Only 'yes' will be accepted to confirm." - v, err := c.UIInput().Input(&terraform.InputOpts{ + v, err := c.UIInput().Input(context.Background(), &terraform.InputOpts{ Id: "force-unlock", Query: "Do you really want to force-unlock?", Description: desc, diff --git a/helper/schema/schema.go b/helper/schema/schema.go index 31b236072..eb056e771 100644 --- a/helper/schema/schema.go +++ b/helper/schema/schema.go @@ -12,6 +12,7 @@ package schema import ( + "context" "fmt" "os" "reflect" @@ -1255,7 +1256,7 @@ func (m schemaMap) inputString( input terraform.UIInput, k string, schema *Schema) (interface{}, error) { - result, err := input.Input(&terraform.InputOpts{ + result, err := input.Input(context.Background(), &terraform.InputOpts{ Id: k, Query: k, Description: schema.Description, diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go index c2974af18..870595dd4 100644 --- a/plugin/discovery/get_test.go +++ b/plugin/discovery/get_test.go @@ -170,12 +170,12 @@ func TestVersionListing(t *testing.T) { } if len(versions) != len(expected) { - t.Fatalf("Received wrong number of versions. expected: %q, got: %q", expected, versions) + t.Fatalf("Received wrong number of versions. expected: %#v, got: %#v", expected, versions) } for i, v := range versions { if v.Version != expected[i].Version { - t.Fatalf("incorrect version: %q, expected %q", v, expected[i]) + t.Fatalf("incorrect version: %#v, expected %#v", v, expected[i]) } } } diff --git a/plugin/ui_input.go b/plugin/ui_input.go index 493efc0a9..3469e6a96 100644 --- a/plugin/ui_input.go +++ b/plugin/ui_input.go @@ -1,19 +1,20 @@ package plugin import ( + "context" "net/rpc" "github.com/hashicorp/go-plugin" "github.com/hashicorp/terraform/terraform" ) -// UIInput is an implementatin of terraform.UIInput that communicates +// UIInput is an implementation of terraform.UIInput that communicates // over RPC. type UIInput struct { Client *rpc.Client } -func (i *UIInput) Input(opts *terraform.InputOpts) (string, error) { +func (i *UIInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { var resp UIInputInputResponse err := i.Client.Call("Plugin.Input", opts, &resp) if err != nil { @@ -41,7 +42,7 @@ type UIInputServer struct { func (s *UIInputServer) Input( opts *terraform.InputOpts, reply *UIInputInputResponse) error { - value, err := s.UIInput.Input(opts) + value, err := s.UIInput.Input(context.Background(), opts) *reply = UIInputInputResponse{ Value: value, Error: plugin.NewBasicError(err), diff --git a/plugin/ui_input_test.go b/plugin/ui_input_test.go index a13dc0ee1..c6d7036d1 100644 --- a/plugin/ui_input_test.go +++ b/plugin/ui_input_test.go @@ -1,6 +1,7 @@ package plugin import ( + "context" "reflect" "testing" @@ -32,7 +33,7 @@ func TestUIInput_input(t *testing.T) { Id: "foo", } - v, err := input.Input(opts) + v, err := input.Input(context.Background(), opts) if !i.InputCalled { t.Fatal("input should be called") } diff --git a/registry/client_test.go b/registry/client_test.go index 0796f5f33..fd39f9f75 100644 --- a/registry/client_test.go +++ b/registry/client_test.go @@ -232,7 +232,7 @@ func TestLookupProviderVersions(t *testing.T) { for _, v := range resp.Versions { _, err := version.NewVersion(v.Version) if err != nil { - t.Fatalf("invalid version %q: %s", v, err) + t.Fatalf("invalid version %#v: %v", v, err) } } } diff --git a/terraform/context_input.go b/terraform/context_input.go index 7c5b7ecb7..6c7be8817 100644 --- a/terraform/context_input.go +++ b/terraform/context_input.go @@ -1,6 +1,7 @@ package terraform import ( + "context" "fmt" "log" "sort" @@ -26,6 +27,8 @@ func (c *Context) Input(mode InputMode) tfdiags.Diagnostics { return diags } + ctx := context.Background() + if mode&InputModeVar != 0 { log.Printf("[TRACE] Context.Input: Prompting for variables") @@ -60,7 +63,7 @@ func (c *Context) Input(mode InputMode) tfdiags.Diagnostics { retry := 0 for { var err error - rawValue, err = c.uiInput.Input(&InputOpts{ + rawValue, err = c.uiInput.Input(ctx, &InputOpts{ Id: fmt.Sprintf("var.%s", n), Query: fmt.Sprintf("var.%s", n), Description: v.Description, @@ -208,7 +211,7 @@ func (c *Context) Input(mode InputMode) tfdiags.Diagnostics { } log.Printf("[TRACE] Context.Input: Prompting for %s argument %s", pa, key) - rawVal, err := input.Input(&InputOpts{ + rawVal, err := input.Input(ctx, &InputOpts{ Id: key, Query: key, Description: attrS.Description, diff --git a/terraform/ui_input.go b/terraform/ui_input.go index 7c8745922..f6790d9e5 100644 --- a/terraform/ui_input.go +++ b/terraform/ui_input.go @@ -1,10 +1,12 @@ package terraform +import "context" + // UIInput is the interface that must be implemented to ask for input // from this user. This should forward the request to wherever the user // inputs things to ask for values. type UIInput interface { - Input(*InputOpts) (string, error) + Input(context.Context, *InputOpts) (string, error) } // InputOpts are options for asking for input. diff --git a/terraform/ui_input_mock.go b/terraform/ui_input_mock.go index e3a07efa3..e2d9c3848 100644 --- a/terraform/ui_input_mock.go +++ b/terraform/ui_input_mock.go @@ -1,5 +1,7 @@ package terraform +import "context" + // MockUIInput is an implementation of UIInput that can be used for tests. type MockUIInput struct { InputCalled bool @@ -10,7 +12,7 @@ type MockUIInput struct { InputFn func(*InputOpts) (string, error) } -func (i *MockUIInput) Input(opts *InputOpts) (string, error) { +func (i *MockUIInput) Input(ctx context.Context, opts *InputOpts) (string, error) { i.InputCalled = true i.InputOpts = opts if i.InputFn != nil { diff --git a/terraform/ui_input_prefix.go b/terraform/ui_input_prefix.go index 2207d1d0f..b5d32b1e8 100644 --- a/terraform/ui_input_prefix.go +++ b/terraform/ui_input_prefix.go @@ -1,6 +1,7 @@ package terraform import ( + "context" "fmt" ) @@ -12,8 +13,8 @@ type PrefixUIInput struct { UIInput UIInput } -func (i *PrefixUIInput) Input(opts *InputOpts) (string, error) { +func (i *PrefixUIInput) Input(ctx context.Context, opts *InputOpts) (string, error) { opts.Id = fmt.Sprintf("%s.%s", i.IdPrefix, opts.Id) opts.Query = fmt.Sprintf("%s%s", i.QueryPrefix, opts.Query) - return i.UIInput.Input(opts) + return i.UIInput.Input(ctx, opts) } diff --git a/terraform/ui_input_prefix_test.go b/terraform/ui_input_prefix_test.go index 39e552156..c4b294773 100644 --- a/terraform/ui_input_prefix_test.go +++ b/terraform/ui_input_prefix_test.go @@ -1,6 +1,7 @@ package terraform import ( + "context" "testing" ) @@ -15,7 +16,7 @@ func testPrefixUIInput(t *testing.T) { UIInput: input, } - _, err := prefix.Input(&InputOpts{Id: "bar"}) + _, err := prefix.Input(context.Background(), &InputOpts{Id: "bar"}) if err != nil { t.Fatalf("err: %s", err) }