command: plan shows module-level, can control depth

This commit is contained in:
Mitchell Hashimoto 2014-09-24 22:54:51 -07:00
parent bc71d6adca
commit 8c17062638
4 changed files with 111 additions and 14 deletions

View File

@ -10,49 +10,96 @@ import (
"github.com/mitchellh/colorstring" "github.com/mitchellh/colorstring"
) )
// FormatPlanOpts are the options for formatting a plan.
type FormatPlanOpts struct {
// Plan is the plan to format. This is required.
Plan *terraform.Plan
// Color is the colorizer. This is optional.
Color *colorstring.Colorize
// ModuleDepth is the depth of the modules to expand. By default this
// is zero which will not expand modules at all.
ModuleDepth int
}
// FormatPlan takes a plan and returns a // FormatPlan takes a plan and returns a
func FormatPlan(p *terraform.Plan, c *colorstring.Colorize) string { func FormatPlan(opts *FormatPlanOpts) string {
p := opts.Plan
if p.Diff == nil || p.Diff.Empty() { if p.Diff == nil || p.Diff.Empty() {
return "This plan does nothing." return "This plan does nothing."
} }
if c == nil { if opts.Color == nil {
c = &colorstring.Colorize{ opts.Color = &colorstring.Colorize{
Colors: colorstring.DefaultColors, Colors: colorstring.DefaultColors,
Reset: false, Reset: false,
} }
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
for _, m := range p.Diff.Modules {
if len(m.Path)-1 <= opts.ModuleDepth || opts.ModuleDepth == -1 {
formatPlanModuleExpand(buf, m, opts)
} else {
formatPlanModuleSingle(buf, m, opts)
}
}
return strings.TrimSpace(buf.String())
}
// formatPlanModuleExpand will output the given module and all of its
// resources.
func formatPlanModuleExpand(
buf *bytes.Buffer, m *terraform.ModuleDiff, opts *FormatPlanOpts) {
// Ignore empty diffs
if m.Empty() {
return
}
var moduleName string
if !m.IsRoot() {
moduleName = fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
}
// We want to output the resources in sorted order to make things // We want to output the resources in sorted order to make things
// easier to scan through, so get all the resource names and sort them. // easier to scan through, so get all the resource names and sort them.
names := make([]string, 0, len(p.Diff.RootModule().Resources)) names := make([]string, 0, len(m.Resources))
for name, _ := range p.Diff.RootModule().Resources { for name, _ := range m.Resources {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
// Go through each sorted name and start building the output // Go through each sorted name and start building the output
for _, name := range names { for _, name := range names {
rdiff := p.Diff.RootModule().Resources[name] rdiff := m.Resources[name]
if rdiff.Empty() {
continue
}
if moduleName != "" {
name = moduleName + "." + name
}
// Determine the color for the text (green for adding, yellow // Determine the color for the text (green for adding, yellow
// for change, red for delete), and symbol, and output the // for change, red for delete), and symbol, and output the
// resource header. // resource header.
color := "yellow" color := "yellow"
symbol := "~" symbol := "~"
if rdiff.RequiresNew() && rdiff.Destroy { switch rdiff.ChangeType() {
case terraform.DiffDestroyCreate:
color = "green" color = "green"
symbol = "-/+" symbol = "-/+"
} else if rdiff.RequiresNew() { case terraform.DiffCreate:
color = "green" color = "green"
symbol = "+" symbol = "+"
} else if rdiff.Destroy { case terraform.DiffDestroy:
color = "red" color = "red"
symbol = "-" symbol = "-"
} }
buf.WriteString(c.Color(fmt.Sprintf(
buf.WriteString(opts.Color.Color(fmt.Sprintf(
"[%s]%s %s\n", "[%s]%s %s\n",
color, symbol, name))) color, symbol, name)))
@ -97,8 +144,40 @@ func FormatPlan(p *terraform.Plan, c *colorstring.Colorize) string {
} }
// Write the reset color so we don't overload the user's terminal // Write the reset color so we don't overload the user's terminal
buf.WriteString(c.Color("[reset]\n")) buf.WriteString(opts.Color.Color("[reset]\n"))
}
} }
return strings.TrimSpace(buf.String()) // formatPlanModuleSingle will output the given module and all of its
// resources.
func formatPlanModuleSingle(
buf *bytes.Buffer, m *terraform.ModuleDiff, opts *FormatPlanOpts) {
// Ignore empty diffs
if m.Empty() {
return
}
moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
// Determine the color for the text (green for adding, yellow
// for change, red for delete), and symbol, and output the
// resource header.
color := "yellow"
symbol := "~"
switch m.ChangeType() {
case terraform.DiffCreate:
color = "green"
symbol = "+"
case terraform.DiffDestroy:
color = "red"
symbol = "-"
}
buf.WriteString(opts.Color.Color(fmt.Sprintf(
"[%s]%s %s\n",
color, symbol, moduleName)))
buf.WriteString(fmt.Sprintf(
" %d resource(s)",
len(m.Resources)))
buf.WriteString(opts.Color.Color("[reset]\n"))
} }

View File

@ -18,12 +18,14 @@ type PlanCommand struct {
func (c *PlanCommand) Run(args []string) int { func (c *PlanCommand) Run(args []string) int {
var destroy, refresh bool var destroy, refresh bool
var outPath, statePath, backupPath string var outPath, statePath, backupPath string
var moduleDepth int
args = c.Meta.process(args, true) args = c.Meta.process(args, true)
cmdFlags := c.Meta.flagSet("plan") cmdFlags := c.Meta.flagSet("plan")
cmdFlags.BoolVar(&destroy, "destroy", false, "destroy") cmdFlags.BoolVar(&destroy, "destroy", false, "destroy")
cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
cmdFlags.IntVar(&moduleDepth, "module-depth", 0, "module-depth")
cmdFlags.StringVar(&outPath, "out", "", "path") cmdFlags.StringVar(&outPath, "out", "", "path")
cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&backupPath, "backup", "", "path") cmdFlags.StringVar(&backupPath, "backup", "", "path")
@ -136,7 +138,11 @@ func (c *PlanCommand) Run(args []string) int {
outPath)) outPath))
} }
c.Ui.Output(FormatPlan(plan, c.Colorize())) c.Ui.Output(FormatPlan(&FormatPlanOpts{
Plan: plan,
Color: c.Colorize(),
ModuleDepth: moduleDepth,
}))
return 0 return 0
} }
@ -161,6 +167,10 @@ Options:
-destroy If set, a plan will be generated to destroy all resources -destroy If set, a plan will be generated to destroy all resources
managed by the given configuration and state. managed by the given configuration and state.
-module-depth=n Specifies the depth of modules to show in the output.
This does not affect the plan itself, only the output
shown. By default, this is zero. -1 will expand all.
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
-out=path Write a plan file to the given path. This can be used as -out=path Write a plan file to the given path. This can be used as

View File

@ -72,7 +72,10 @@ func (c *ShowCommand) Run(args []string) int {
} }
if plan != nil { if plan != nil {
c.Ui.Output(FormatPlan(plan, c.Colorize())) c.Ui.Output(FormatPlan(&FormatPlanOpts{
Plan: plan,
Color: c.Colorize(),
}))
return 0 return 0
} }

View File

@ -167,6 +167,11 @@ func (d *ModuleDiff) Empty() bool {
return true return true
} }
// IsRoot says whether or not this module diff is for the root module.
func (d *ModuleDiff) IsRoot() bool {
return reflect.DeepEqual(d.Path, rootModulePath)
}
// String outputs the diff in a long but command-line friendly output // String outputs the diff in a long but command-line friendly output
// format that users can read to quickly inspect a diff. // format that users can read to quickly inspect a diff.
func (d *ModuleDiff) String() string { func (d *ModuleDiff) String() string {