diff --git a/command/apply.go b/command/apply.go index 0ca22b73f..a6dc368fb 100644 --- a/command/apply.go +++ b/command/apply.go @@ -50,6 +50,10 @@ func (c *ApplyCommand) Run(args []string) int { } } + // Prepare the extra hooks to count resources + countHook := new(CountHook) + c.Meta.extraHooks = []terraform.Hook{countHook} + // If we don't specify an output path, default to out normal state // path. if stateOutPath == "" { @@ -124,13 +128,21 @@ func (c *ApplyCommand) Run(args []string) int { c.Ui.Output(c.Colorize().Color(fmt.Sprintf( "[reset][bold][green]\n"+ - "Apply succeeded! Infrastructure created and/or updated.\n"+ - "The state of your infrastructure has been saved to the path\n"+ - "below. This state is required to modify and destroy your\n"+ - "infrastructure, so keep it safe. To inspect the complete state\n"+ - "use the `terraform show` command.\n\n"+ - "State path: %s", - stateOutPath))) + "Apply complete! Resources: %d added, %d changed, %d destroyed.", + countHook.Added, + countHook.Changed, + countHook.Removed))) + + if countHook.Added > 0 || countHook.Changed > 0 { + c.Ui.Output(c.Colorize().Color(fmt.Sprintf( + "[reset]\n"+ + "The state of your infrastructure has been saved to the path\n"+ + "below. This state is required to modify and destroy your\n"+ + "infrastructure, so keep it safe. To inspect the complete state\n"+ + "use the `terraform show` command.\n\n"+ + "State path: %s", + stateOutPath))) + } // If we have outputs, then output those at the end. if len(state.Outputs) > 0 { diff --git a/command/hook_count.go b/command/hook_count.go new file mode 100644 index 000000000..06f02bc56 --- /dev/null +++ b/command/hook_count.go @@ -0,0 +1,83 @@ +package command + +import ( + "sync" + + "github.com/hashicorp/terraform/terraform" +) + +// CountHook is a hook that counts the number of resources +// added, removed, changed during the course of an apply. +type CountHook struct { + Added int + Changed int + Removed int + + pending map[string]countHookAction + + sync.Mutex + terraform.NilHook +} + +type countHookAction byte + +const ( + countHookActionAdd countHookAction = iota + countHookActionChange + countHookActionRemove +) + +func (h *CountHook) Reset() { + h.Lock() + defer h.Unlock() + + h.pending = nil + h.Added = 0 + h.Changed = 0 + h.Removed = 0 +} + +func (h *CountHook) PreApply( + id string, + s *terraform.ResourceState, + d *terraform.ResourceDiff) (terraform.HookAction, error) { + h.Lock() + defer h.Unlock() + + if h.pending == nil { + h.pending = make(map[string]countHookAction) + } + + action := countHookActionChange + if d.Destroy { + action = countHookActionRemove + } else if s.ID == "" { + action = countHookActionAdd + } + + h.pending[id] = action + + return terraform.HookActionContinue, nil +} + +func (h *CountHook) PostApply( + id string, + s *terraform.ResourceState) (terraform.HookAction, error) { + h.Lock() + defer h.Unlock() + + if h.pending != nil { + if a, ok := h.pending[id]; ok { + switch a { + case countHookActionAdd: + h.Added += 1 + case countHookActionChange: + h.Changed += 1 + case countHookActionRemove: + h.Removed += 1 + } + } + } + + return terraform.HookActionContinue, nil +} diff --git a/command/hook_count_test.go b/command/hook_count_test.go new file mode 100644 index 000000000..37fc72b69 --- /dev/null +++ b/command/hook_count_test.go @@ -0,0 +1,11 @@ +package command + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" +) + +func TestCountHook_impl(t *testing.T) { + var _ terraform.Hook = new(CountHook) +} diff --git a/command/meta.go b/command/meta.go index b0ce5ad7d..2d00627b8 100644 --- a/command/meta.go +++ b/command/meta.go @@ -16,6 +16,9 @@ type Meta struct { ContextOpts *terraform.ContextOpts Ui cli.Ui + // This can be set by the command itself to provide extra hooks. + extraHooks []terraform.Hook + color bool oldUi cli.Ui } @@ -99,9 +102,13 @@ func (m *Meta) Context(path, statePath string, doPlan bool) (*terraform.Context, // context with the settings from this Meta. func (m *Meta) contextOpts() *terraform.ContextOpts { var opts terraform.ContextOpts = *m.ContextOpts - opts.Hooks = make([]terraform.Hook, len(m.ContextOpts.Hooks)+1) + opts.Hooks = make( + []terraform.Hook, + len(m.ContextOpts.Hooks)+len(m.extraHooks)+1) opts.Hooks[0] = m.uiHook() copy(opts.Hooks[1:], m.ContextOpts.Hooks) + copy(opts.Hooks[len(m.ContextOpts.Hooks)+1:], m.extraHooks) + println(fmt.Sprintf("%#v", opts.Hooks)) return &opts }