cli: Migrate Terraform UI hook to command views
Move the code which renders Terraform hook callbacks as UI into the views package, backed by a views.View instead of a cli.Ui. Update test setup accordingly. To allow commands to control this hook, we add a hooks member on the backend Operation struct. This supersedes the hooks in the Terraform context, which is not directly controlled by the command logic. This commit should not change how Terraform works, and is refactoring in preparation for more changes which move UI code out of the backend.
This commit is contained in:
parent
fa774dafc0
commit
a7b7cd29fc
|
@ -183,6 +183,10 @@ type Operation struct {
|
|||
// configuration from ConfigDir.
|
||||
ConfigLoader *configload.Loader
|
||||
|
||||
// Hooks can be used to perform actions triggered by various events during
|
||||
// the operation's lifecycle.
|
||||
Hooks []terraform.Hook
|
||||
|
||||
// Plan is a plan that was passed as an argument. This is valid for
|
||||
// plan and apply arguments but may not work for all backends.
|
||||
PlanFile *planfile.Reader
|
||||
|
|
|
@ -40,12 +40,7 @@ func (b *Local) opApply(
|
|||
}
|
||||
|
||||
stateHook := new(StateHook)
|
||||
if b.ContextOpts == nil {
|
||||
b.ContextOpts = new(terraform.ContextOpts)
|
||||
}
|
||||
old := b.ContextOpts.Hooks
|
||||
defer func() { b.ContextOpts.Hooks = old }()
|
||||
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, stateHook)
|
||||
op.Hooks = append(op.Hooks, stateHook)
|
||||
|
||||
// Get our context
|
||||
tfCtx, _, opState, contextDiags := b.context(op)
|
||||
|
|
|
@ -77,6 +77,7 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, *configload.
|
|||
opts.Destroy = op.Destroy
|
||||
opts.Targets = op.Targets
|
||||
opts.UIInput = op.UIIn
|
||||
opts.Hooks = op.Hooks
|
||||
|
||||
opts.SkipRefresh = op.Type != backend.OperationTypeRefresh && !op.PlanRefresh
|
||||
if opts.SkipRefresh {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/hashicorp/terraform/command/arguments"
|
||||
"github.com/hashicorp/terraform/command/views"
|
||||
"github.com/hashicorp/terraform/plans/planfile"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
|
@ -110,7 +111,6 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
|
||||
// Set up our count hook that keeps track of resource changes
|
||||
countHook := new(CountHook)
|
||||
c.ExtraHooks = append(c.ExtraHooks, countHook)
|
||||
|
||||
// Load the backend
|
||||
var be backend.Enhanced
|
||||
|
@ -171,6 +171,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
opReq.AutoApprove = autoApprove
|
||||
opReq.ConfigDir = configPath
|
||||
opReq.Destroy = c.Destroy
|
||||
opReq.Hooks = []terraform.Hook{countHook, c.uiHook()}
|
||||
opReq.PlanFile = planFile
|
||||
opReq.PlanRefresh = refresh
|
||||
opReq.ShowDiagnostics = c.showDiagnostics
|
||||
|
|
|
@ -941,10 +941,12 @@ func TestApply_planNoModuleFiles(t *testing.T) {
|
|||
p := applyFixtureProvider()
|
||||
planPath := applyFixturePlanFile(t)
|
||||
|
||||
view, _ := testView(t)
|
||||
apply := &ApplyCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: new(cli.MockUi),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
args := []string{
|
||||
|
|
|
@ -189,6 +189,7 @@ func (c *ImportCommand) Run(args []string) int {
|
|||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
opReq.Hooks = []terraform.Hook{c.uiHook()}
|
||||
{
|
||||
var moreDiags tfdiags.Diagnostics
|
||||
opReq.Variables, moreDiags = c.collectVariableValues()
|
||||
|
|
|
@ -24,10 +24,12 @@ func TestImport(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -77,10 +79,12 @@ func TestImport_providerConfig(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -170,9 +174,11 @@ func TestImport_remoteState(t *testing.T) {
|
|||
|
||||
// init our backend
|
||||
ui := cli.NewMockUi()
|
||||
view, _ := testView(t)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
|
@ -192,6 +198,7 @@ func TestImport_remoteState(t *testing.T) {
|
|||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -280,9 +287,11 @@ func TestImport_initializationErrorShouldUnlock(t *testing.T) {
|
|||
|
||||
// init our backend
|
||||
ui := cli.NewMockUi()
|
||||
view, _ := testView(t)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
|
@ -305,6 +314,7 @@ func TestImport_initializationErrorShouldUnlock(t *testing.T) {
|
|||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -339,10 +349,12 @@ func TestImport_providerConfigWithVar(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -417,10 +429,12 @@ func TestImport_providerConfigWithDataSource(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -480,10 +494,12 @@ func TestImport_providerConfigWithVarDefault(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -557,10 +573,12 @@ func TestImport_providerConfigWithVarFile(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -635,10 +653,12 @@ func TestImport_allowMissingResourceConfig(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -690,10 +710,12 @@ func TestImport_emptyConfig(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -720,10 +742,12 @@ func TestImport_missingResourceConfig(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -750,10 +774,12 @@ func TestImport_missingModuleConfig(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -801,9 +827,11 @@ func TestImportModuleVarFile(t *testing.T) {
|
|||
|
||||
// init to install the module
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
|
@ -820,6 +848,7 @@ func TestImportModuleVarFile(t *testing.T) {
|
|||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
args := []string{
|
||||
|
@ -873,9 +902,11 @@ func TestImportModuleInputVariableEvaluation(t *testing.T) {
|
|||
|
||||
// init to install the module
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
|
@ -892,6 +923,7 @@ func TestImportModuleInputVariableEvaluation(t *testing.T) {
|
|||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
args := []string{
|
||||
|
@ -912,10 +944,12 @@ func TestImport_dataResource(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -942,10 +976,12 @@ func TestImport_invalidResourceAddr(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -972,10 +1008,12 @@ func TestImport_targetIsModule(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/backend/local"
|
||||
"github.com/hashicorp/terraform/command/arguments"
|
||||
"github.com/hashicorp/terraform/command/format"
|
||||
"github.com/hashicorp/terraform/command/views"
|
||||
"github.com/hashicorp/terraform/command/webbrowser"
|
||||
|
@ -69,9 +70,6 @@ type Meta struct {
|
|||
GlobalPluginDirs []string // Additional paths to search for plugins
|
||||
Ui cli.Ui // Ui for output
|
||||
|
||||
// ExtraHooks are extra hooks to add to the context.
|
||||
ExtraHooks []terraform.Hook
|
||||
|
||||
// Services provides access to remote endpoint information for
|
||||
// "terraform-native' services running at a specific user-facing hostname.
|
||||
Services *disco.Disco
|
||||
|
@ -434,8 +432,6 @@ func (m *Meta) contextOpts() (*terraform.ContextOpts, error) {
|
|||
}
|
||||
|
||||
var opts terraform.ContextOpts
|
||||
opts.Hooks = []terraform.Hook{m.uiHook()}
|
||||
opts.Hooks = append(opts.Hooks, m.ExtraHooks...)
|
||||
|
||||
opts.Targets = m.targets
|
||||
opts.UIInput = m.UIInput()
|
||||
|
@ -621,15 +617,21 @@ func (m *Meta) process(args []string) []string {
|
|||
},
|
||||
}
|
||||
|
||||
// Reconfigure the view. This is necessary for commands which use both
|
||||
// views.View and cli.Ui during the migration phase.
|
||||
if m.View != nil {
|
||||
m.View.Configure(&arguments.View{
|
||||
CompactWarnings: m.compactWarnings,
|
||||
NoColor: !m.Color,
|
||||
})
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// uiHook returns the UiHook to use with the context.
|
||||
func (m *Meta) uiHook() *UiHook {
|
||||
return &UiHook{
|
||||
Colorize: m.Colorize(),
|
||||
Ui: m.Ui,
|
||||
}
|
||||
func (m *Meta) uiHook() *views.UiHook {
|
||||
return views.NewUiHook(m.View)
|
||||
}
|
||||
|
||||
// confirm asks a yes/no confirmation.
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/plans"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
|
@ -82,6 +83,7 @@ func (c *PlanCommand) Run(args []string) int {
|
|||
opReq := c.Operation(b)
|
||||
opReq.ConfigDir = configPath
|
||||
opReq.Destroy = destroy
|
||||
opReq.Hooks = []terraform.Hook{c.uiHook()}
|
||||
opReq.PlanOutPath = outPath
|
||||
opReq.PlanRefresh = refresh
|
||||
opReq.ShowDiagnostics = c.showDiagnostics
|
||||
|
|
|
@ -32,10 +32,12 @@ func TestPlan(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -59,10 +61,12 @@ func TestPlan_lockedState(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -85,10 +89,12 @@ func TestPlan_plan(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -126,10 +132,12 @@ func TestPlan_destroy(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -158,10 +166,12 @@ func TestPlan_noState(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -193,10 +203,12 @@ func TestPlan_outPath(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -246,10 +258,12 @@ func TestPlan_outPathNoChange(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -324,10 +338,12 @@ func TestPlan_outBackend(t *testing.T) {
|
|||
}
|
||||
}
|
||||
ui := cli.NewMockUi()
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -376,10 +392,12 @@ func TestPlan_refreshFalse(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -407,10 +425,12 @@ func TestPlan_state(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -450,10 +470,12 @@ func TestPlan_stateDefault(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -505,10 +527,12 @@ func TestPlan_validate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -532,10 +556,12 @@ func TestPlan_vars(t *testing.T) {
|
|||
|
||||
p := planVarsFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -577,10 +603,12 @@ func TestPlan_varsUnset(t *testing.T) {
|
|||
|
||||
p := planVarsFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -640,10 +668,12 @@ func TestPlan_providerArgumentUnset(t *testing.T) {
|
|||
},
|
||||
}
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -667,10 +697,12 @@ func TestPlan_varFile(t *testing.T) {
|
|||
|
||||
p := planVarsFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -707,10 +739,12 @@ func TestPlan_varFileDefault(t *testing.T) {
|
|||
|
||||
p := planVarsFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -745,10 +779,12 @@ func TestPlan_varFileWithDecls(t *testing.T) {
|
|||
|
||||
p := planVarsFixtureProvider()
|
||||
ui := cli.NewMockUi()
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -773,10 +809,12 @@ func TestPlan_detailedExitcode(t *testing.T) {
|
|||
|
||||
p := planFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -794,10 +832,12 @@ func TestPlan_detailedExitcode_emptyDiff(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -819,10 +859,12 @@ func TestPlan_shutdown(t *testing.T) {
|
|||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
ShutdownCh: shutdownCh,
|
||||
},
|
||||
}
|
||||
|
@ -884,10 +926,12 @@ func TestPlan_init_required(t *testing.T) {
|
|||
defer testChdir(t, td)()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
// Running plan without setting testingOverrides is similar to plan without init
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -927,10 +971,12 @@ func TestPlan_targeted(t *testing.T) {
|
|||
}
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -961,9 +1007,11 @@ func TestPlan_targetFlagsDiags(t *testing.T) {
|
|||
defer testChdir(t, td)()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/arguments"
|
||||
"github.com/hashicorp/terraform/command/views"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
|
@ -75,6 +76,7 @@ func (c *RefreshCommand) Run(args []string) int {
|
|||
// Build the operation
|
||||
opReq := c.Operation(b)
|
||||
opReq.ConfigDir = configPath
|
||||
opReq.Hooks = []terraform.Hook{c.uiHook()}
|
||||
opReq.ShowDiagnostics = c.showDiagnostics
|
||||
opReq.Type = backend.OperationTypeRefresh
|
||||
|
||||
|
|
|
@ -791,7 +791,7 @@ func TestRefresh_targeted(t *testing.T) {
|
|||
}
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
view, done := testView(t)
|
||||
c := &RefreshCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
|
@ -808,7 +808,7 @@ func TestRefresh_targeted(t *testing.T) {
|
|||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
got := ui.OutputWriter.String()
|
||||
got := done(t).Stdout()
|
||||
if want := "test_instance.foo: Refreshing"; !strings.Contains(got, want) {
|
||||
t.Fatalf("expected output to contain %q, got:\n%s", want, got)
|
||||
}
|
||||
|
|
|
@ -255,9 +255,11 @@ func TestShow_json_output(t *testing.T) {
|
|||
|
||||
p := showFixtureProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
ProviderSource: providerSource,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package command
|
||||
package views
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
@ -9,8 +9,6 @@ import (
|
|||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/colorstring"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
|
@ -24,17 +22,24 @@ import (
|
|||
const defaultPeriodicUiTimer = 10 * time.Second
|
||||
const maxIdLen = 80
|
||||
|
||||
func NewUiHook(view *View) *UiHook {
|
||||
return &UiHook{
|
||||
view: view,
|
||||
periodicUiTimer: defaultPeriodicUiTimer,
|
||||
resources: make(map[string]uiResourceState),
|
||||
}
|
||||
}
|
||||
|
||||
type UiHook struct {
|
||||
terraform.NilHook
|
||||
|
||||
Colorize *colorstring.Colorize
|
||||
Ui cli.Ui
|
||||
PeriodicUiTimer time.Duration
|
||||
view *View
|
||||
viewLock sync.Mutex
|
||||
|
||||
periodicUiTimer time.Duration
|
||||
|
||||
l sync.Mutex
|
||||
once sync.Once
|
||||
resources map[string]uiResourceState
|
||||
ui cli.Ui
|
||||
resourcesLock sync.Mutex
|
||||
}
|
||||
|
||||
var _ terraform.Hook = (*UiHook)(nil)
|
||||
|
@ -63,8 +68,6 @@ const (
|
|||
)
|
||||
|
||||
func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
|
||||
h.once.Do(h.init)
|
||||
|
||||
dispAddr := addr.String()
|
||||
if gen != states.CurrentGen {
|
||||
dispAddr = fmt.Sprintf("%s (%s)", dispAddr, gen)
|
||||
|
@ -89,7 +92,7 @@ func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation,
|
|||
default:
|
||||
// We don't expect any other actions in here, so anything else is a
|
||||
// bug in the caller but we'll ignore it in order to be robust.
|
||||
h.ui.Output(fmt.Sprintf("(Unknown action %s for %s)", action, dispAddr))
|
||||
h.println(fmt.Sprintf("(Unknown action %s for %s)", action, dispAddr))
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
|
@ -103,7 +106,7 @@ func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation,
|
|||
idValue = ""
|
||||
}
|
||||
|
||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||
h.println(h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][bold]%s: %s%s[reset]",
|
||||
dispAddr,
|
||||
operation,
|
||||
|
@ -121,9 +124,9 @@ func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation,
|
|||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
h.l.Lock()
|
||||
h.resourcesLock.Lock()
|
||||
h.resources[key] = uiState
|
||||
h.l.Unlock()
|
||||
h.resourcesLock.Unlock()
|
||||
|
||||
// Start goroutine that shows progress
|
||||
go h.stillApplying(uiState)
|
||||
|
@ -138,7 +141,7 @@ func (h *UiHook) stillApplying(state uiResourceState) {
|
|||
case <-state.DoneCh:
|
||||
return
|
||||
|
||||
case <-time.After(h.PeriodicUiTimer):
|
||||
case <-time.After(h.periodicUiTimer):
|
||||
// Timer up, show status
|
||||
}
|
||||
|
||||
|
@ -161,7 +164,7 @@ func (h *UiHook) stillApplying(state uiResourceState) {
|
|||
idSuffix = fmt.Sprintf("%s=%s, ", state.IDKey, truncateId(state.IDValue, maxIdLen))
|
||||
}
|
||||
|
||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||
h.println(h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][bold]%s: %s [%s%s elapsed][reset]",
|
||||
state.DispAddr,
|
||||
msg,
|
||||
|
@ -172,17 +175,16 @@ func (h *UiHook) stillApplying(state uiResourceState) {
|
|||
}
|
||||
|
||||
func (h *UiHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation, newState cty.Value, applyerr error) (terraform.HookAction, error) {
|
||||
|
||||
id := addr.String()
|
||||
|
||||
h.l.Lock()
|
||||
h.resourcesLock.Lock()
|
||||
state := h.resources[id]
|
||||
if state.DoneCh != nil {
|
||||
close(state.DoneCh)
|
||||
}
|
||||
|
||||
delete(h.resources, id)
|
||||
h.l.Unlock()
|
||||
h.resourcesLock.Unlock()
|
||||
|
||||
var stateIdSuffix string
|
||||
if k, v := format.ObjectValueID(newState); k != "" && v != "" {
|
||||
|
@ -208,21 +210,17 @@ func (h *UiHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation
|
|||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
colorized := h.Colorize.Color(fmt.Sprintf(
|
||||
colorized := h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][bold]%s: %s after %s%s[reset]",
|
||||
addr, msg, time.Now().Round(time.Second).Sub(state.Start), stateIdSuffix))
|
||||
|
||||
h.ui.Output(colorized)
|
||||
h.println(colorized)
|
||||
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
func (h *UiHook) PreDiff(addr addrs.AbsResourceInstance, gen states.Generation, priorState, proposedNewState cty.Value) (terraform.HookAction, error) {
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
func (h *UiHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeName string) (terraform.HookAction, error) {
|
||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||
h.println(h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][bold]%s: Provisioning with '%s'...[reset]",
|
||||
addr, typeName,
|
||||
)))
|
||||
|
@ -231,7 +229,7 @@ func (h *UiHook) PreProvisionInstanceStep(addr addrs.AbsResourceInstance, typeNa
|
|||
|
||||
func (h *UiHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string, msg string) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(h.Colorize.Color("[reset]"))
|
||||
buf.WriteString(h.view.colorize.Color("[reset]"))
|
||||
|
||||
prefix := fmt.Sprintf("%s (%s): ", addr, typeName)
|
||||
s := bufio.NewScanner(strings.NewReader(msg))
|
||||
|
@ -243,27 +241,23 @@ func (h *UiHook) ProvisionOutput(addr addrs.AbsResourceInstance, typeName string
|
|||
}
|
||||
}
|
||||
|
||||
h.ui.Output(strings.TrimSpace(buf.String()))
|
||||
h.println(strings.TrimSpace(buf.String()))
|
||||
}
|
||||
|
||||
func (h *UiHook) PreRefresh(addr addrs.AbsResourceInstance, gen states.Generation, priorState cty.Value) (terraform.HookAction, error) {
|
||||
h.once.Do(h.init)
|
||||
|
||||
var stateIdSuffix string
|
||||
if k, v := format.ObjectValueID(priorState); k != "" && v != "" {
|
||||
stateIdSuffix = fmt.Sprintf(" [%s=%s]", k, v)
|
||||
}
|
||||
|
||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||
h.println(h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][bold]%s: Refreshing state...%s",
|
||||
addr, stateIdSuffix)))
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
func (h *UiHook) PreImportState(addr addrs.AbsResourceInstance, importID string) (terraform.HookAction, error) {
|
||||
h.once.Do(h.init)
|
||||
|
||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||
h.println(h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][bold]%s: Importing from ID %q...",
|
||||
addr, importID,
|
||||
)))
|
||||
|
@ -271,12 +265,10 @@ func (h *UiHook) PreImportState(addr addrs.AbsResourceInstance, importID string)
|
|||
}
|
||||
|
||||
func (h *UiHook) PostImportState(addr addrs.AbsResourceInstance, imported []providers.ImportedResource) (terraform.HookAction, error) {
|
||||
h.once.Do(h.init)
|
||||
|
||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||
h.println(h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][bold][green]%s: Import prepared!", addr)))
|
||||
for _, s := range imported {
|
||||
h.ui.Output(h.Colorize.Color(fmt.Sprintf(
|
||||
h.println(h.view.colorize.Color(fmt.Sprintf(
|
||||
"[reset][green] Prepared %s for import",
|
||||
s.TypeName,
|
||||
)))
|
||||
|
@ -285,19 +277,11 @@ func (h *UiHook) PostImportState(addr addrs.AbsResourceInstance, imported []prov
|
|||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
func (h *UiHook) init() {
|
||||
if h.Colorize == nil {
|
||||
panic("colorize not given")
|
||||
}
|
||||
if h.PeriodicUiTimer == 0 {
|
||||
h.PeriodicUiTimer = defaultPeriodicUiTimer
|
||||
}
|
||||
|
||||
h.resources = make(map[string]uiResourceState)
|
||||
|
||||
// Wrap the ui so that it is safe for concurrency regardless of the
|
||||
// underlying reader/writer that is in place.
|
||||
h.ui = &cli.ConcurrentUi{Ui: h.Ui}
|
||||
// Wrap calls to the view so that concurrent calls do not interleave println.
|
||||
func (h *UiHook) println(s string) {
|
||||
h.viewLock.Lock()
|
||||
defer h.viewLock.Unlock()
|
||||
h.view.streams.Println(s)
|
||||
}
|
||||
|
||||
// scanLines is basically copied from the Go standard library except
|
|
@ -1,4 +1,4 @@
|
|||
package command
|
||||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -6,28 +6,20 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/colorstring"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/plans"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestUiHookPreApply_periodicTimer(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
h := &UiHook{
|
||||
Colorize: &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
Reset: true,
|
||||
},
|
||||
Ui: ui,
|
||||
PeriodicUiTimer: 1 * time.Second,
|
||||
}
|
||||
h.init()
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := NewView(streams)
|
||||
h := NewUiHook(view)
|
||||
h.periodicUiTimer = 1 * time.Second
|
||||
h.resources = map[string]uiResourceState{
|
||||
"data.aws_availability_zones.available": uiResourceState{
|
||||
Op: uiResourceDestroy,
|
||||
|
@ -75,29 +67,23 @@ data.aws_availability_zones.available: Still destroying... [id=2017-03-05 10:56:
|
|||
data.aws_availability_zones.available: Still destroying... [id=2017-03-05 10:56:59.298784526 +0000 UTC, 2s elapsed]
|
||||
data.aws_availability_zones.available: Still destroying... [id=2017-03-05 10:56:59.298784526 +0000 UTC, 3s elapsed]
|
||||
`
|
||||
output := ui.OutputWriter.String()
|
||||
result := done(t)
|
||||
output := result.Stdout()
|
||||
if output != expectedOutput {
|
||||
t.Fatalf("Output didn't match.\nExpected: %q\nGiven: %q", expectedOutput, output)
|
||||
}
|
||||
|
||||
expectedErrOutput := ""
|
||||
errOutput := ui.ErrorWriter.String()
|
||||
errOutput := result.Stderr()
|
||||
if errOutput != expectedErrOutput {
|
||||
t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUiHookPreApply_destroy(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
h := &UiHook{
|
||||
Colorize: &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
Reset: true,
|
||||
},
|
||||
Ui: ui,
|
||||
}
|
||||
h.init()
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := NewView(streams)
|
||||
h := NewUiHook(view)
|
||||
h.resources = map[string]uiResourceState{
|
||||
"data.aws_availability_zones.available": uiResourceState{
|
||||
Op: uiResourceDestroy,
|
||||
|
@ -138,30 +124,24 @@ func TestUiHookPreApply_destroy(t *testing.T) {
|
|||
close(uiState.DoneCh)
|
||||
<-uiState.done
|
||||
|
||||
result := done(t)
|
||||
expectedOutput := "data.aws_availability_zones.available: Destroying... [id=2017-03-05 10:56:59.298784526 +0000 UTC]\n"
|
||||
output := ui.OutputWriter.String()
|
||||
output := result.Stdout()
|
||||
if output != expectedOutput {
|
||||
t.Fatalf("Output didn't match.\nExpected: %q\nGiven: %q", expectedOutput, output)
|
||||
}
|
||||
|
||||
expectedErrOutput := ""
|
||||
errOutput := ui.ErrorWriter.String()
|
||||
errOutput := result.Stderr()
|
||||
if errOutput != expectedErrOutput {
|
||||
t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUiHookPostApply_emptyState(t *testing.T) {
|
||||
ui := cli.NewMockUi()
|
||||
h := &UiHook{
|
||||
Colorize: &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
Reset: true,
|
||||
},
|
||||
Ui: ui,
|
||||
}
|
||||
h.init()
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
view := NewView(streams)
|
||||
h := NewUiHook(view)
|
||||
h.resources = map[string]uiResourceState{
|
||||
"data.google_compute_zones.available": uiResourceState{
|
||||
Op: uiResourceDestroy,
|
||||
|
@ -187,15 +167,16 @@ func TestUiHookPostApply_emptyState(t *testing.T) {
|
|||
if action != terraform.HookActionContinue {
|
||||
t.Fatalf("Expected hook to continue, given: %#v", action)
|
||||
}
|
||||
result := done(t)
|
||||
|
||||
expectedRegexp := "^data.google_compute_zones.available: Destruction complete after -?[a-z0-9µ.]+\n$"
|
||||
output := ui.OutputWriter.String()
|
||||
output := result.Stdout()
|
||||
if matched, _ := regexp.MatchString(expectedRegexp, output); !matched {
|
||||
t.Fatalf("Output didn't match regexp.\nExpected: %q\nGiven: %q", expectedRegexp, output)
|
||||
}
|
||||
|
||||
expectedErrOutput := ""
|
||||
errOutput := ui.ErrorWriter.String()
|
||||
errOutput := result.Stderr()
|
||||
if errOutput != expectedErrOutput {
|
||||
t.Fatalf("Error output didn't match.\nExpected: %q\nGiven: %q", expectedErrOutput, errOutput)
|
||||
}
|
Loading…
Reference in New Issue