From 6445e1f16adbee95d0d57334736567b0420b29c7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 09:00:07 -0700 Subject: [PATCH 01/13] terraform: UIOutput interface --- terraform/ui_output.go | 7 +++++++ terraform/ui_output_mock.go | 16 ++++++++++++++++ terraform/ui_output_mock_test.go | 9 +++++++++ 3 files changed, 32 insertions(+) create mode 100644 terraform/ui_output.go create mode 100644 terraform/ui_output_mock.go create mode 100644 terraform/ui_output_mock_test.go diff --git a/terraform/ui_output.go b/terraform/ui_output.go new file mode 100644 index 000000000..84427c63d --- /dev/null +++ b/terraform/ui_output.go @@ -0,0 +1,7 @@ +package terraform + +// UIOutput is the interface that must be implemented to output +// data to the end user. +type UIOutput interface { + Output(string) +} diff --git a/terraform/ui_output_mock.go b/terraform/ui_output_mock.go new file mode 100644 index 000000000..8e6ff82d7 --- /dev/null +++ b/terraform/ui_output_mock.go @@ -0,0 +1,16 @@ +package terraform + +// MockUIOutput is an implementation of UIOutput that can be used for tests. +type MockUIOutput struct { + OutputCalled bool + OutputMessage string + OutputFn func(string) +} + +func (o *MockUIOutput) Output(v string) { + o.OutputCalled = true + o.OutputMessage= v + if o.OutputFn == nil { + o.OutputFn(v) + } +} diff --git a/terraform/ui_output_mock_test.go b/terraform/ui_output_mock_test.go new file mode 100644 index 000000000..0a23c2e23 --- /dev/null +++ b/terraform/ui_output_mock_test.go @@ -0,0 +1,9 @@ +package terraform + +import ( + "testing" +) + +func TestMockUIOutput(t *testing.T) { + var _ UIOutput = new(MockUIOutput) +} From 509f293bea185c001827db0c8da5a6e34faf96d2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 09:11:51 -0700 Subject: [PATCH 02/13] rpc: UIOutput --- rpc/ui_output.go | 30 ++++++++++++++++++++++++++++++ rpc/ui_output_test.go | 34 ++++++++++++++++++++++++++++++++++ terraform/ui_output_mock.go | 2 +- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 rpc/ui_output.go create mode 100644 rpc/ui_output_test.go diff --git a/rpc/ui_output.go b/rpc/ui_output.go new file mode 100644 index 000000000..a997b943b --- /dev/null +++ b/rpc/ui_output.go @@ -0,0 +1,30 @@ +package rpc + +import ( + "net/rpc" + + "github.com/hashicorp/terraform/terraform" +) + +// UIOutput is an implementatin of terraform.UIOutput that communicates +// over RPC. +type UIOutput struct { + Client *rpc.Client + Name string +} + +func (o *UIOutput) Output(v string) { + o.Client.Call(o.Name+".Output", v, new(interface{})) +} + +// UIOutputServer is the RPC server for serving UIOutput. +type UIOutputServer struct { + UIOutput terraform.UIOutput +} + +func (s *UIOutputServer) Output( + v string, + reply *interface{}) error { + s.UIOutput.Output(v) + return nil +} diff --git a/rpc/ui_output_test.go b/rpc/ui_output_test.go new file mode 100644 index 000000000..0113a0903 --- /dev/null +++ b/rpc/ui_output_test.go @@ -0,0 +1,34 @@ +package rpc + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestUIOutput_impl(t *testing.T) { + var _ terraform.UIOutput = new(UIOutput) +} + +func TestUIOutput_input(t *testing.T) { + client, server := testClientServer(t) + defer client.Close() + + o := new(terraform.MockUIOutput) + + err := server.RegisterName("UIOutput", &UIOutputServer{ + UIOutput: o, + }) + if err != nil { + t.Fatalf("err: %s", err) + } + + output := &UIOutput{Client: client, Name: "UIOutput"} + output.Output("foo") + if !o.OutputCalled { + t.Fatal("output should be called") + } + if o.OutputMessage != "foo" { + t.Fatalf("bad: %#v", o.OutputMessage) + } +} diff --git a/terraform/ui_output_mock.go b/terraform/ui_output_mock.go index 8e6ff82d7..8e16ac9af 100644 --- a/terraform/ui_output_mock.go +++ b/terraform/ui_output_mock.go @@ -10,7 +10,7 @@ type MockUIOutput struct { func (o *MockUIOutput) Output(v string) { o.OutputCalled = true o.OutputMessage= v - if o.OutputFn == nil { + if o.OutputFn != nil { o.OutputFn(v) } } From 8b129babe50e819a0383629373c61efc57038b50 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 09:20:05 -0700 Subject: [PATCH 03/13] terraform: change provisioners to take UIOutput --- terraform/context.go | 8 ++++++-- terraform/resource_provisioner.go | 2 +- terraform/resource_provisioner_mock.go | 7 ++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index 132a9f48d..f7ab9145d 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -34,6 +34,7 @@ type Context struct { provisioners map[string]ResourceProvisionerFactory variables map[string]string uiInput UIInput + uiOutput UIOutput l sync.Mutex // Lock acquired during any task parCh chan struct{} // Semaphore used to limit parallelism @@ -54,7 +55,8 @@ type ContextOpts struct { Provisioners map[string]ResourceProvisionerFactory Variables map[string]string - UIInput UIInput + UIInput UIInput + UIOutput UIOutput } // NewContext creates a new context. @@ -88,6 +90,7 @@ func NewContext(opts *ContextOpts) *Context { provisioners: opts.Provisioners, variables: opts.Variables, uiInput: opts.UIInput, + uiOutput: opts.UIOutput, parCh: parCh, sh: sh, @@ -1310,7 +1313,8 @@ func (c *walkContext) applyProvisioners(r *Resource, is *InstanceState) error { handleHook(h.PreProvision(r.Info, prov.Type)) } - if err := prov.Provisioner.Apply(is, prov.Config); err != nil { + err := prov.Provisioner.Apply(c.Context.uiOutput, is, prov.Config) + if err != nil { return err } diff --git a/terraform/resource_provisioner.go b/terraform/resource_provisioner.go index 6a749e001..acc80d054 100644 --- a/terraform/resource_provisioner.go +++ b/terraform/resource_provisioner.go @@ -20,7 +20,7 @@ type ResourceProvisioner interface { // resource state along with an error. Instead of a diff, the ResourceConfig // is provided since provisioners only run after a resource has been // newly created. - Apply(*InstanceState, *ResourceConfig) error + Apply(UIOutput, *InstanceState, *ResourceConfig) error } // ResourceProvisionerFactory is a function type that creates a new instance diff --git a/terraform/resource_provisioner_mock.go b/terraform/resource_provisioner_mock.go index f1600784c..2ba7220cd 100644 --- a/terraform/resource_provisioner_mock.go +++ b/terraform/resource_provisioner_mock.go @@ -7,6 +7,7 @@ type MockResourceProvisioner struct { Meta interface{} ApplyCalled bool + ApplyOutput UIOutput ApplyState *InstanceState ApplyConfig *ResourceConfig ApplyFn func(*InstanceState, *ResourceConfig) error @@ -28,8 +29,12 @@ func (p *MockResourceProvisioner) Validate(c *ResourceConfig) ([]string, []error return p.ValidateReturnWarns, p.ValidateReturnErrors } -func (p *MockResourceProvisioner) Apply(state *InstanceState, c *ResourceConfig) error { +func (p *MockResourceProvisioner) Apply( + output UIOutput, + state *InstanceState, + c *ResourceConfig) error { p.ApplyCalled = true + p.ApplyOutput = output p.ApplyState = state p.ApplyConfig = c if p.ApplyFn != nil { From 02d01141ca25c07c9f07098175e8d671fd98c325 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 09:54:03 -0700 Subject: [PATCH 04/13] rpc: implement provisioner update --- rpc/client.go | 1 + rpc/client_test.go | 3 ++- rpc/resource_provisioner.go | 35 +++++++++++++++++++++++++++----- rpc/resource_provisioner_test.go | 13 +++++++----- rpc/server.go | 1 + 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/rpc/client.go b/rpc/client.go index f6d20e3a6..0c80385ee 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -101,6 +101,7 @@ func (c *Client) ResourceProvisioner() (terraform.ResourceProvisioner, error) { } return &ResourceProvisioner{ + Broker: c.broker, Client: rpc.NewClient(conn), Name: "ResourceProvisioner", }, nil diff --git a/rpc/client_test.go b/rpc/client_test.go index c4479cfd1..f8c286fe8 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -60,9 +60,10 @@ func TestClient_ResourceProvisioner(t *testing.T) { } // Apply + output := &terraform.MockUIOutput{} state := &terraform.InstanceState{} conf := &terraform.ResourceConfig{} - err = provisioner.Apply(state, conf) + err = provisioner.Apply(output, state, conf) if !p.ApplyCalled { t.Fatal("apply should be called") } diff --git a/rpc/resource_provisioner.go b/rpc/resource_provisioner.go index 5fc45a98c..cf8c00812 100644 --- a/rpc/resource_provisioner.go +++ b/rpc/resource_provisioner.go @@ -9,6 +9,7 @@ import ( // ResourceProvisioner is an implementation of terraform.ResourceProvisioner // that communicates over RPC. type ResourceProvisioner struct { + Broker *muxBroker Client *rpc.Client Name string } @@ -36,12 +37,19 @@ func (p *ResourceProvisioner) Validate(c *terraform.ResourceConfig) ([]string, [ } func (p *ResourceProvisioner) Apply( + output terraform.UIOutput, s *terraform.InstanceState, c *terraform.ResourceConfig) error { + id := p.Broker.NextId() + go acceptAndServe(p.Broker, id, "UIOutput", &UIOutputServer{ + UIOutput: output, + }) + var resp ResourceProvisionerApplyResponse args := &ResourceProvisionerApplyArgs{ - State: s, - Config: c, + OutputId: id, + State: s, + Config: c, } err := p.Client.Call(p.Name+".Apply", args, &resp) @@ -65,8 +73,9 @@ type ResourceProvisionerValidateResponse struct { } type ResourceProvisionerApplyArgs struct { - State *terraform.InstanceState - Config *terraform.ResourceConfig + OutputId uint32 + State *terraform.InstanceState + Config *terraform.ResourceConfig } type ResourceProvisionerApplyResponse struct { @@ -76,13 +85,29 @@ type ResourceProvisionerApplyResponse struct { // ResourceProvisionerServer is a net/rpc compatible structure for serving // a ResourceProvisioner. This should not be used directly. type ResourceProvisionerServer struct { + Broker *muxBroker Provisioner terraform.ResourceProvisioner } func (s *ResourceProvisionerServer) Apply( args *ResourceProvisionerApplyArgs, result *ResourceProvisionerApplyResponse) error { - err := s.Provisioner.Apply(args.State, args.Config) + conn, err := s.Broker.Dial(args.OutputId) + if err != nil { + *result = ResourceProvisionerApplyResponse{ + Error: NewBasicError(err), + } + return nil + } + client := rpc.NewClient(conn) + defer client.Close() + + output := &UIOutput{ + Client: client, + Name: "UIOutput", + } + + err = s.Provisioner.Apply(output, args.State, args.Config) *result = ResourceProvisionerApplyResponse{ Error: NewBasicError(err), } diff --git a/rpc/resource_provisioner_test.go b/rpc/resource_provisioner_test.go index b91252648..32b6c06f3 100644 --- a/rpc/resource_provisioner_test.go +++ b/rpc/resource_provisioner_test.go @@ -13,18 +13,21 @@ func TestResourceProvisioner_impl(t *testing.T) { } func TestResourceProvisioner_apply(t *testing.T) { - p := new(terraform.MockResourceProvisioner) - client, server := testClientServer(t) - name, err := Register(server, p) + client, server := testNewClientServer(t) + defer client.Close() + + p := server.ProvisionerFunc().(*terraform.MockResourceProvisioner) + + provisioner, err := client.ResourceProvisioner() if err != nil { t.Fatalf("err: %s", err) } - provisioner := &ResourceProvisioner{Client: client, Name: name} // Apply + output := &terraform.MockUIOutput{} state := &terraform.InstanceState{} conf := &terraform.ResourceConfig{} - err = provisioner.Apply(state, conf) + err = provisioner.Apply(output, state, conf) if !p.ApplyCalled { t.Fatal("apply should be called") } diff --git a/rpc/server.go b/rpc/server.go index 5ca6c6746..dd1e9b7b0 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -118,6 +118,7 @@ func (d *dispenseServer) ResourceProvisioner( } serve(conn, "ResourceProvisioner", &ResourceProvisionerServer{ + Broker: d.broker, Provisioner: d.ProvisionerFunc(), }) }() From 6d9463bfa8ccd116ce77ac9f051e5120929cd186 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 10:20:35 -0700 Subject: [PATCH 05/13] command: set UIOutput --- command/meta.go | 4 ++++ command/ui_output.go | 15 +++++++++++++++ command/ui_output_test.go | 11 +++++++++++ 3 files changed, 30 insertions(+) create mode 100644 command/ui_output.go create mode 100644 command/ui_output_test.go diff --git a/command/meta.go b/command/meta.go index a6bdded50..a58a858a0 100644 --- a/command/meta.go +++ b/command/meta.go @@ -143,6 +143,10 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { } opts.Variables = vs opts.UIInput = m.UIInput() + opts.UIOutput = &UIOutput{ + Colorize: m.Colorize(), + Ui: m.Ui, + } return &opts } diff --git a/command/ui_output.go b/command/ui_output.go new file mode 100644 index 000000000..7e8b574a3 --- /dev/null +++ b/command/ui_output.go @@ -0,0 +1,15 @@ +package command + +import ( + "github.com/mitchellh/cli" + "github.com/mitchellh/colorstring" +) + +// UIOutput is an implementation of terraform.UIOutput. +type UIOutput struct { + Colorize *colorstring.Colorize + Ui cli.Ui +} + +func (u *UIOutput) Output(v string) { +} diff --git a/command/ui_output_test.go b/command/ui_output_test.go new file mode 100644 index 000000000..bff073e86 --- /dev/null +++ b/command/ui_output_test.go @@ -0,0 +1,11 @@ +package command + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestUIOutput_impl(t *testing.T) { + var _ terraform.UIOutput = new(UIOutput) +} From b2342866c9835485c667216b309b089da6d73ca0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 10:25:54 -0700 Subject: [PATCH 06/13] command: UIOutput is functinal --- command/ui_output.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/command/ui_output.go b/command/ui_output.go index 7e8b574a3..32cde2ef9 100644 --- a/command/ui_output.go +++ b/command/ui_output.go @@ -1,6 +1,8 @@ package command import ( + "sync" + "github.com/mitchellh/cli" "github.com/mitchellh/colorstring" ) @@ -9,7 +11,18 @@ import ( type UIOutput struct { Colorize *colorstring.Colorize Ui cli.Ui + + once sync.Once + ui cli.Ui } func (u *UIOutput) Output(v string) { + u.once.Do(u.init) + u.ui.Output(v) +} + +func (u *UIOutput) init() { + // Wrap the ui so that it is safe for concurrency regardless of the + // underlying reader/writer that is in place. + u.ui = &cli.ConcurrentUi{Ui: u.Ui} } From 24dd078beea437625800119fa92d82aa9231e287 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 10:33:01 -0700 Subject: [PATCH 07/13] terraform: UIOutputPrefix --- terraform/ui_output_prefix.go | 17 +++++++++++++++++ terraform/ui_output_prefix_test.go | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 terraform/ui_output_prefix.go create mode 100644 terraform/ui_output_prefix_test.go diff --git a/terraform/ui_output_prefix.go b/terraform/ui_output_prefix.go new file mode 100644 index 000000000..e814f6e8e --- /dev/null +++ b/terraform/ui_output_prefix.go @@ -0,0 +1,17 @@ +package terraform + +import ( + "fmt" +) + +// PrefixUIOutput is an implementation of UIOutput that prefixes the output +// with a string. +type PrefixUIOutput struct { + Prefix string + UIOutput UIOutput +} + +func (i *PrefixUIOutput) Output(v string) { + v = fmt.Sprintf("%s%s", i.Prefix, v) + i.UIOutput.Output(v) +} diff --git a/terraform/ui_output_prefix_test.go b/terraform/ui_output_prefix_test.go new file mode 100644 index 000000000..093500f99 --- /dev/null +++ b/terraform/ui_output_prefix_test.go @@ -0,0 +1,22 @@ +package terraform + +import ( + "testing" +) + +func TestPrefixUIOutput_impl(t *testing.T) { + var _ UIOutput = new(PrefixUIOutput) +} + +func testPrefixUIOutput(t *testing.T) { + output := new(MockUIOutput) + prefix := &PrefixUIOutput{ + Prefix: "foo", + UIOutput: output, + } + + prefix.Output("foo") + if output.OutputMessage != "foofoo" { + t.Fatalf("bad: %#v", output) + } +} From d7a1f3dc0e89e63c01488a4ca25077235d2608f4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 10:38:46 -0700 Subject: [PATCH 08/13] terraform: prefix the provisioner output --- terraform/context.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/terraform/context.go b/terraform/context.go index f7ab9145d..7e6480562 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -1313,7 +1313,11 @@ func (c *walkContext) applyProvisioners(r *Resource, is *InstanceState) error { handleHook(h.PreProvision(r.Info, prov.Type)) } - err := prov.Provisioner.Apply(c.Context.uiOutput, is, prov.Config) + output := PrefixUIOutput{ + Prefix: r.Id + ": ", + UIOutput: c.Context.uiOutput, + } + err := prov.Provisioner.Apply(&output, is, prov.Config) if err != nil { return err } From 76f5f1057e3aa1c615c90fac8d718f2ff1a0806c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 16:24:07 -0700 Subject: [PATCH 09/13] terraform: no longer require uiOutput, do it auto in Hook --- terraform/context.go | 10 ++++----- terraform/hook.go | 5 +++++ terraform/hook_mock.go | 15 +++++++++++++ terraform/hook_stop.go | 3 +++ terraform/ui_output_callback.go | 5 +++++ terraform/ui_output_prefix.go | 17 -------------- terraform/ui_output_prefix_test.go | 22 ------------------ terraform/ui_output_provisioner.go | 15 +++++++++++++ terraform/ui_output_provisioner_test.go | 30 +++++++++++++++++++++++++ 9 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 terraform/ui_output_callback.go delete mode 100644 terraform/ui_output_prefix.go delete mode 100644 terraform/ui_output_prefix_test.go create mode 100644 terraform/ui_output_provisioner.go create mode 100644 terraform/ui_output_provisioner_test.go diff --git a/terraform/context.go b/terraform/context.go index 7e6480562..b859d67d8 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -34,7 +34,6 @@ type Context struct { provisioners map[string]ResourceProvisionerFactory variables map[string]string uiInput UIInput - uiOutput UIOutput l sync.Mutex // Lock acquired during any task parCh chan struct{} // Semaphore used to limit parallelism @@ -56,7 +55,6 @@ type ContextOpts struct { Variables map[string]string UIInput UIInput - UIOutput UIOutput } // NewContext creates a new context. @@ -90,7 +88,6 @@ func NewContext(opts *ContextOpts) *Context { provisioners: opts.Provisioners, variables: opts.Variables, uiInput: opts.UIInput, - uiOutput: opts.UIOutput, parCh: parCh, sh: sh, @@ -1313,9 +1310,10 @@ func (c *walkContext) applyProvisioners(r *Resource, is *InstanceState) error { handleHook(h.PreProvision(r.Info, prov.Type)) } - output := PrefixUIOutput{ - Prefix: r.Id + ": ", - UIOutput: c.Context.uiOutput, + output := ProvisionerUIOutput{ + Info: r.Info, + Type: prov.Type, + Hooks: c.Context.hooks, } err := prov.Provisioner.Apply(&output, is, prov.Config) if err != nil { diff --git a/terraform/hook.go b/terraform/hook.go index 8d4c83d4c..47f17c495 100644 --- a/terraform/hook.go +++ b/terraform/hook.go @@ -37,6 +37,7 @@ type Hook interface { PostProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error) PreProvision(*InstanceInfo, string) (HookAction, error) PostProvision(*InstanceInfo, string) (HookAction, error) + ProvisionOutput(*InstanceInfo, string, string) // PreRefresh and PostRefresh are called before and after a single // resource state is refreshed, respectively. @@ -81,6 +82,10 @@ func (*NilHook) PostProvision(*InstanceInfo, string) (HookAction, error) { return HookActionContinue, nil } +func (*NilHook) ProvisionOutput( + *InstanceInfo, string, string) { +} + func (*NilHook) PreRefresh(*InstanceInfo, *InstanceState) (HookAction, error) { return HookActionContinue, nil } diff --git a/terraform/hook_mock.go b/terraform/hook_mock.go index 63ae011b7..b2b6a6e6f 100644 --- a/terraform/hook_mock.go +++ b/terraform/hook_mock.go @@ -53,6 +53,11 @@ type MockHook struct { PostProvisionReturn HookAction PostProvisionError error + ProvisionOutputCalled bool + ProvisionOutputInfo *InstanceInfo + ProvisionOutputProvisionerId string + ProvisionOutputMessage string + PostRefreshCalled bool PostRefreshInfo *InstanceInfo PostRefreshState *InstanceState @@ -124,6 +129,16 @@ func (h *MockHook) PostProvision(n *InstanceInfo, provId string) (HookAction, er return h.PostProvisionReturn, h.PostProvisionError } +func (h *MockHook) ProvisionOutput( + n *InstanceInfo, + provId string, + msg string) { + h.ProvisionOutputCalled = true + h.ProvisionOutputInfo = n + h.ProvisionOutputProvisionerId = provId + h.ProvisionOutputMessage = msg +} + func (h *MockHook) PreRefresh(n *InstanceInfo, s *InstanceState) (HookAction, error) { h.PreRefreshCalled = true h.PreRefreshInfo = n diff --git a/terraform/hook_stop.go b/terraform/hook_stop.go index 148f9c63c..0dc1ad7b4 100644 --- a/terraform/hook_stop.go +++ b/terraform/hook_stop.go @@ -42,6 +42,9 @@ func (h *stopHook) PostProvision(*InstanceInfo, string) (HookAction, error) { return h.hook() } +func (h *stopHook) ProvisionOutput(*InstanceInfo, string, string) { +} + func (h *stopHook) PreRefresh(*InstanceInfo, *InstanceState) (HookAction, error) { return h.hook() } diff --git a/terraform/ui_output_callback.go b/terraform/ui_output_callback.go new file mode 100644 index 000000000..147515b95 --- /dev/null +++ b/terraform/ui_output_callback.go @@ -0,0 +1,5 @@ +package terraform + +type CallbackUIOutput struct { + OutputFun func(string) +} diff --git a/terraform/ui_output_prefix.go b/terraform/ui_output_prefix.go deleted file mode 100644 index e814f6e8e..000000000 --- a/terraform/ui_output_prefix.go +++ /dev/null @@ -1,17 +0,0 @@ -package terraform - -import ( - "fmt" -) - -// PrefixUIOutput is an implementation of UIOutput that prefixes the output -// with a string. -type PrefixUIOutput struct { - Prefix string - UIOutput UIOutput -} - -func (i *PrefixUIOutput) Output(v string) { - v = fmt.Sprintf("%s%s", i.Prefix, v) - i.UIOutput.Output(v) -} diff --git a/terraform/ui_output_prefix_test.go b/terraform/ui_output_prefix_test.go deleted file mode 100644 index 093500f99..000000000 --- a/terraform/ui_output_prefix_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package terraform - -import ( - "testing" -) - -func TestPrefixUIOutput_impl(t *testing.T) { - var _ UIOutput = new(PrefixUIOutput) -} - -func testPrefixUIOutput(t *testing.T) { - output := new(MockUIOutput) - prefix := &PrefixUIOutput{ - Prefix: "foo", - UIOutput: output, - } - - prefix.Output("foo") - if output.OutputMessage != "foofoo" { - t.Fatalf("bad: %#v", output) - } -} diff --git a/terraform/ui_output_provisioner.go b/terraform/ui_output_provisioner.go new file mode 100644 index 000000000..878a03122 --- /dev/null +++ b/terraform/ui_output_provisioner.go @@ -0,0 +1,15 @@ +package terraform + +// ProvisionerUIOutput is an implementation of UIOutput that calls a hook +// for the output so that the hooks can handle it. +type ProvisionerUIOutput struct { + Info *InstanceInfo + Type string + Hooks []Hook +} + +func (o *ProvisionerUIOutput) Output(msg string) { + for _, h := range o.Hooks { + h.ProvisionOutput(o.Info, o.Type, msg) + } +} diff --git a/terraform/ui_output_provisioner_test.go b/terraform/ui_output_provisioner_test.go new file mode 100644 index 000000000..dc1d00c21 --- /dev/null +++ b/terraform/ui_output_provisioner_test.go @@ -0,0 +1,30 @@ +package terraform + +import ( + "testing" +) + +func TestProvisionerUIOutput_impl(t *testing.T) { + var _ UIOutput = new(ProvisionerUIOutput) +} + +func TestProvisionerUIOutputOutput(t *testing.T) { + hook := new(MockHook) + output := &ProvisionerUIOutput{ + Info: nil, + Type: "foo", + Hooks: []Hook{hook}, + } + + output.Output("bar") + + if !hook.ProvisionOutputCalled { + t.Fatal("should be called") + } + if hook.ProvisionOutputProvisionerId != "foo" { + t.Fatalf("bad: %#v", hook.ProvisionOutputProvisionerId) + } + if hook.ProvisionOutputMessage != "bar" { + t.Fatalf("bad: %#v", hook.ProvisionOutputMessage) + } +} From 063e727491b5fea94702b782e96f7f59974c5c7f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 16:24:57 -0700 Subject: [PATCH 10/13] command: compilej:wq --- command/meta.go | 4 ---- command/ui_output.go | 28 ---------------------------- command/ui_output_test.go | 11 ----------- 3 files changed, 43 deletions(-) delete mode 100644 command/ui_output.go delete mode 100644 command/ui_output_test.go diff --git a/command/meta.go b/command/meta.go index a58a858a0..a6bdded50 100644 --- a/command/meta.go +++ b/command/meta.go @@ -143,10 +143,6 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { } opts.Variables = vs opts.UIInput = m.UIInput() - opts.UIOutput = &UIOutput{ - Colorize: m.Colorize(), - Ui: m.Ui, - } return &opts } diff --git a/command/ui_output.go b/command/ui_output.go deleted file mode 100644 index 32cde2ef9..000000000 --- a/command/ui_output.go +++ /dev/null @@ -1,28 +0,0 @@ -package command - -import ( - "sync" - - "github.com/mitchellh/cli" - "github.com/mitchellh/colorstring" -) - -// UIOutput is an implementation of terraform.UIOutput. -type UIOutput struct { - Colorize *colorstring.Colorize - Ui cli.Ui - - once sync.Once - ui cli.Ui -} - -func (u *UIOutput) Output(v string) { - u.once.Do(u.init) - u.ui.Output(v) -} - -func (u *UIOutput) init() { - // Wrap the ui so that it is safe for concurrency regardless of the - // underlying reader/writer that is in place. - u.ui = &cli.ConcurrentUi{Ui: u.Ui} -} diff --git a/command/ui_output_test.go b/command/ui_output_test.go deleted file mode 100644 index bff073e86..000000000 --- a/command/ui_output_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package command - -import ( - "testing" - - "github.com/hashicorp/terraform/terraform" -) - -func TestUIOutput_impl(t *testing.T) { - var _ terraform.UIOutput = new(UIOutput) -} From e5868ebdd9628a72886aa2d3c20c21e7d316553c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 16:29:33 -0700 Subject: [PATCH 11/13] provisioners/*: new interface --- builtin/provisioners/file/resource_provisioner.go | 4 +++- builtin/provisioners/local-exec/resource_provisioner.go | 1 + builtin/provisioners/local-exec/resource_provisioner_test.go | 3 ++- builtin/provisioners/remote-exec/resource_provisioner.go | 4 +++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/builtin/provisioners/file/resource_provisioner.go b/builtin/provisioners/file/resource_provisioner.go index 91970b215..bb95c0860 100644 --- a/builtin/provisioners/file/resource_provisioner.go +++ b/builtin/provisioners/file/resource_provisioner.go @@ -13,7 +13,9 @@ import ( type ResourceProvisioner struct{} -func (p *ResourceProvisioner) Apply(s *terraform.InstanceState, +func (p *ResourceProvisioner) Apply( + o terraform.UIOutput, + s *terraform.InstanceState, c *terraform.ResourceConfig) error { // Ensure the connection type is SSH if err := helper.VerifySSH(s); err != nil { diff --git a/builtin/provisioners/local-exec/resource_provisioner.go b/builtin/provisioners/local-exec/resource_provisioner.go index 234b8ee3e..544dabe86 100644 --- a/builtin/provisioners/local-exec/resource_provisioner.go +++ b/builtin/provisioners/local-exec/resource_provisioner.go @@ -20,6 +20,7 @@ const ( type ResourceProvisioner struct{} func (p *ResourceProvisioner) Apply( + o terraform.UIOutput, s *terraform.InstanceState, c *terraform.ResourceConfig) error { diff --git a/builtin/provisioners/local-exec/resource_provisioner_test.go b/builtin/provisioners/local-exec/resource_provisioner_test.go index e31641561..9158c333e 100644 --- a/builtin/provisioners/local-exec/resource_provisioner_test.go +++ b/builtin/provisioners/local-exec/resource_provisioner_test.go @@ -20,8 +20,9 @@ func TestResourceProvider_Apply(t *testing.T) { "command": "echo foo > test_out", }) + output := new(terraform.MockUIOutput) p := new(ResourceProvisioner) - if err := p.Apply(nil, c); err != nil { + if err := p.Apply(output, nil, c); err != nil { t.Fatalf("err: %v", err) } diff --git a/builtin/provisioners/remote-exec/resource_provisioner.go b/builtin/provisioners/remote-exec/resource_provisioner.go index fc694d8aa..7868f32ac 100644 --- a/builtin/provisioners/remote-exec/resource_provisioner.go +++ b/builtin/provisioners/remote-exec/resource_provisioner.go @@ -22,7 +22,9 @@ const ( type ResourceProvisioner struct{} -func (p *ResourceProvisioner) Apply(s *terraform.InstanceState, +func (p *ResourceProvisioner) Apply( + o terraform.UIOutput, + s *terraform.InstanceState, c *terraform.ResourceConfig) error { // Ensure the connection type is SSH if err := helper.VerifySSH(s); err != nil { From c52a21d680cffa8a09a778aab18e8d46dd77c532 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 16:31:48 -0700 Subject: [PATCH 12/13] terraform: comments --- terraform/hook.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terraform/hook.go b/terraform/hook.go index 47f17c495..e4ad42016 100644 --- a/terraform/hook.go +++ b/terraform/hook.go @@ -33,6 +33,12 @@ type Hook interface { PostDiff(*InstanceInfo, *InstanceDiff) (HookAction, error) // Provisioning hooks + // + // All should be self-explanatory. ProvisionOutput is called with + // output sent back by the provisioners. This will be called multiple + // times as output comes in, but each call should represent a line of + // output. The ProvisionOutput method cannot control whether the + // hook continues running. PreProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error) PostProvisionResource(*InstanceInfo, *InstanceState) (HookAction, error) PreProvision(*InstanceInfo, string) (HookAction, error) From c1fbf46a336781538595fcacc5495bd133b93bc0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 4 Oct 2014 16:33:47 -0700 Subject: [PATCH 13/13] command: send the provisioner output to the prompt --- command/hook_ui.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/command/hook_ui.go b/command/hook_ui.go index b86701f5e..80c7fea28 100644 --- a/command/hook_ui.go +++ b/command/hook_ui.go @@ -164,6 +164,18 @@ func (h *UiHook) PreProvision( return terraform.HookActionContinue, nil } +func (h *UiHook) ProvisionOutput( + n *terraform.InstanceInfo, + provId string, + msg string) { + id := n.HumanId() + var buf bytes.Buffer + buf.WriteString(h.Colorize.Color(fmt.Sprintf( + "[reset]%s (%s): ", id, provId))) + buf.WriteString(msg) + h.ui.Output(buf.String()) +} + func (h *UiHook) PreRefresh( n *terraform.InstanceInfo, s *terraform.InstanceState) (terraform.HookAction, error) {