core: add a context to the UIInput interface

This commit is contained in:
Sander van Harmelen 2019-03-07 21:07:13 +01:00
parent a95d97f066
commit 973e2a7cf9
22 changed files with 55 additions and 36 deletions

View File

@ -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,

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)

View File

@ -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.`,

View File

@ -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)

View File

@ -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]",

View File

@ -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 "+

View File

@ -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,

View File

@ -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.

View File

@ -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)
}

View File

@ -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,

View File

@ -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,

View File

@ -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])
}
}
}

View File

@ -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),

View File

@ -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")
}

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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.

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}