command/format: a package for formatting plans/state for output
This commit is contained in:
parent
251e5c6f87
commit
0a0842a7d9
|
@ -0,0 +1,8 @@
|
||||||
|
// Package format contains helpers for formatting various Terraform
|
||||||
|
// structures for human-readabout output.
|
||||||
|
//
|
||||||
|
// This package is used by the official Terraform CLI in formatting any
|
||||||
|
// output and is exported to encourage non-official frontends to mimic the
|
||||||
|
// output formatting as much as possible so that text formats of Terraform
|
||||||
|
// structures have a consistent look and feel.
|
||||||
|
package format
|
|
@ -0,0 +1,231 @@
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PlanOpts are the options for formatting a plan.
|
||||||
|
type PlanOpts 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plan takes a plan and returns a
|
||||||
|
func Plan(opts *PlanOpts) string {
|
||||||
|
p := opts.Plan
|
||||||
|
if p.Diff == nil || p.Diff.Empty() {
|
||||||
|
return "This plan does nothing."
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Color == nil {
|
||||||
|
opts.Color = &colorstring.Colorize{
|
||||||
|
Colors: colorstring.DefaultColors,
|
||||||
|
Reset: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *PlanOpts) {
|
||||||
|
// 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
|
||||||
|
// easier to scan through, so get all the resource names and sort them.
|
||||||
|
names := make([]string, 0, len(m.Resources))
|
||||||
|
for name, _ := range m.Resources {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
// Go through each sorted name and start building the output
|
||||||
|
for _, name := range names {
|
||||||
|
rdiff := m.Resources[name]
|
||||||
|
if rdiff.Empty() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource := strings.HasPrefix(name, "data.")
|
||||||
|
|
||||||
|
if moduleName != "" {
|
||||||
|
name = moduleName + "." + name
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := "~"
|
||||||
|
oldValues := true
|
||||||
|
switch rdiff.ChangeType() {
|
||||||
|
case terraform.DiffDestroyCreate:
|
||||||
|
color = "green"
|
||||||
|
symbol = "-/+"
|
||||||
|
case terraform.DiffCreate:
|
||||||
|
color = "green"
|
||||||
|
symbol = "+"
|
||||||
|
oldValues = false
|
||||||
|
|
||||||
|
// If we're "creating" a data resource then we'll present it
|
||||||
|
// to the user as a "read" operation, so it's clear that this
|
||||||
|
// operation won't change anything outside of the Terraform state.
|
||||||
|
// Unfortunately by the time we get here we only have the name
|
||||||
|
// to work with, so we need to cheat and exploit knowledge of the
|
||||||
|
// naming scheme for data resources.
|
||||||
|
if dataSource {
|
||||||
|
symbol = "<="
|
||||||
|
color = "cyan"
|
||||||
|
}
|
||||||
|
case terraform.DiffDestroy:
|
||||||
|
color = "red"
|
||||||
|
symbol = "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
var extraAttr []string
|
||||||
|
if rdiff.DestroyTainted {
|
||||||
|
extraAttr = append(extraAttr, "tainted")
|
||||||
|
}
|
||||||
|
if rdiff.DestroyDeposed {
|
||||||
|
extraAttr = append(extraAttr, "deposed")
|
||||||
|
}
|
||||||
|
var extraStr string
|
||||||
|
if len(extraAttr) > 0 {
|
||||||
|
extraStr = fmt.Sprintf(" (%s)", strings.Join(extraAttr, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(opts.Color.Color(fmt.Sprintf(
|
||||||
|
"[%s]%s %s%s\n",
|
||||||
|
color, symbol, name, extraStr)))
|
||||||
|
|
||||||
|
// Get all the attributes that are changing, and sort them. Also
|
||||||
|
// determine the longest key so that we can align them all.
|
||||||
|
keyLen := 0
|
||||||
|
keys := make([]string, 0, len(rdiff.Attributes))
|
||||||
|
for key, _ := range rdiff.Attributes {
|
||||||
|
// Skip the ID since we do that specially
|
||||||
|
if key == "id" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = append(keys, key)
|
||||||
|
if len(key) > keyLen {
|
||||||
|
keyLen = len(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
// Go through and output each attribute
|
||||||
|
for _, attrK := range keys {
|
||||||
|
attrDiff := rdiff.Attributes[attrK]
|
||||||
|
|
||||||
|
v := attrDiff.New
|
||||||
|
if v == "" && attrDiff.NewComputed {
|
||||||
|
v = "<computed>"
|
||||||
|
}
|
||||||
|
|
||||||
|
if attrDiff.Sensitive {
|
||||||
|
v = "<sensitive>"
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMsg := ""
|
||||||
|
if attrDiff.RequiresNew && rdiff.Destroy {
|
||||||
|
updateMsg = opts.Color.Color(" [red](forces new resource)")
|
||||||
|
} else if attrDiff.Sensitive && oldValues {
|
||||||
|
updateMsg = opts.Color.Color(" [yellow](attribute changed)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldValues {
|
||||||
|
var u string
|
||||||
|
if attrDiff.Sensitive {
|
||||||
|
u = "<sensitive>"
|
||||||
|
} else {
|
||||||
|
u = attrDiff.Old
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf(
|
||||||
|
" %s:%s %#v => %#v%s\n",
|
||||||
|
attrK,
|
||||||
|
strings.Repeat(" ", keyLen-len(attrK)),
|
||||||
|
u,
|
||||||
|
v,
|
||||||
|
updateMsg))
|
||||||
|
} else {
|
||||||
|
buf.WriteString(fmt.Sprintf(
|
||||||
|
" %s:%s %#v%s\n",
|
||||||
|
attrK,
|
||||||
|
strings.Repeat(" ", keyLen-len(attrK)),
|
||||||
|
v,
|
||||||
|
updateMsg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the reset color so we don't overload the user's terminal
|
||||||
|
buf.WriteString(opts.Color.Color("[reset]\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPlanModuleSingle will output the given module and all of its
|
||||||
|
// resources.
|
||||||
|
func formatPlanModuleSingle(
|
||||||
|
buf *bytes.Buffer, m *terraform.ModuleDiff, opts *PlanOpts) {
|
||||||
|
// 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"))
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test that a root level data source gets a special plan output on create
|
||||||
|
func TestPlan_destroyDeposed(t *testing.T) {
|
||||||
|
plan := &terraform.Plan{
|
||||||
|
Diff: &terraform.Diff{
|
||||||
|
Modules: []*terraform.ModuleDiff{
|
||||||
|
&terraform.ModuleDiff{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*terraform.InstanceDiff{
|
||||||
|
"aws_instance.foo": &terraform.InstanceDiff{
|
||||||
|
DestroyDeposed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
opts := &PlanOpts{
|
||||||
|
Plan: plan,
|
||||||
|
Color: &colorstring.Colorize{
|
||||||
|
Colors: colorstring.DefaultColors,
|
||||||
|
Disable: true,
|
||||||
|
},
|
||||||
|
ModuleDepth: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := Plan(opts)
|
||||||
|
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
- aws_instance.foo (deposed)
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that computed fields with an interpolation string get displayed
|
||||||
|
func TestPlan_displayInterpolations(t *testing.T) {
|
||||||
|
plan := &terraform.Plan{
|
||||||
|
Diff: &terraform.Diff{
|
||||||
|
Modules: []*terraform.ModuleDiff{
|
||||||
|
&terraform.ModuleDiff{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*terraform.InstanceDiff{
|
||||||
|
"aws_instance.foo": &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"computed_field": &terraform.ResourceAttrDiff{
|
||||||
|
New: "${aws_instance.other.id}",
|
||||||
|
NewComputed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
opts := &PlanOpts{
|
||||||
|
Plan: plan,
|
||||||
|
Color: &colorstring.Colorize{
|
||||||
|
Colors: colorstring.DefaultColors,
|
||||||
|
Disable: true,
|
||||||
|
},
|
||||||
|
ModuleDepth: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
out := Plan(opts)
|
||||||
|
lines := strings.Split(out, "\n")
|
||||||
|
if len(lines) != 2 {
|
||||||
|
t.Fatal("expected 2 lines of output, got:\n", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(lines[1])
|
||||||
|
expected := `computed_field: "" => "${aws_instance.other.id}"`
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that a root level data source gets a special plan output on create
|
||||||
|
func TestPlan_rootDataSource(t *testing.T) {
|
||||||
|
plan := &terraform.Plan{
|
||||||
|
Diff: &terraform.Diff{
|
||||||
|
Modules: []*terraform.ModuleDiff{
|
||||||
|
&terraform.ModuleDiff{
|
||||||
|
Path: []string{"root"},
|
||||||
|
Resources: map[string]*terraform.InstanceDiff{
|
||||||
|
"data.type.name": &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"A": &terraform.ResourceAttrDiff{
|
||||||
|
New: "B",
|
||||||
|
RequiresNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
opts := &PlanOpts{
|
||||||
|
Plan: plan,
|
||||||
|
Color: &colorstring.Colorize{
|
||||||
|
Colors: colorstring.DefaultColors,
|
||||||
|
Disable: true,
|
||||||
|
},
|
||||||
|
ModuleDepth: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := Plan(opts)
|
||||||
|
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
<= data.type.name
|
||||||
|
A: "B"
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that data sources nested in modules get the same plan output
|
||||||
|
func TestPlan_nestedDataSource(t *testing.T) {
|
||||||
|
plan := &terraform.Plan{
|
||||||
|
Diff: &terraform.Diff{
|
||||||
|
Modules: []*terraform.ModuleDiff{
|
||||||
|
&terraform.ModuleDiff{
|
||||||
|
Path: []string{"root", "nested"},
|
||||||
|
Resources: map[string]*terraform.InstanceDiff{
|
||||||
|
"data.type.name": &terraform.InstanceDiff{
|
||||||
|
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||||
|
"A": &terraform.ResourceAttrDiff{
|
||||||
|
New: "B",
|
||||||
|
RequiresNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
opts := &PlanOpts{
|
||||||
|
Plan: plan,
|
||||||
|
Color: &colorstring.Colorize{
|
||||||
|
Colors: colorstring.DefaultColors,
|
||||||
|
Disable: true,
|
||||||
|
},
|
||||||
|
ModuleDepth: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := Plan(opts)
|
||||||
|
|
||||||
|
expected := strings.TrimSpace(`
|
||||||
|
<= module.nested.data.type.name
|
||||||
|
A: "B"
|
||||||
|
`)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,265 @@
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateOpts are the options for formatting a state.
|
||||||
|
type StateOpts struct {
|
||||||
|
// State is the state to format. This is required.
|
||||||
|
State *terraform.State
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// State takes a state and returns a string
|
||||||
|
func State(opts *StateOpts) string {
|
||||||
|
if opts.Color == nil {
|
||||||
|
panic("colorize not given")
|
||||||
|
}
|
||||||
|
|
||||||
|
s := opts.State
|
||||||
|
if len(s.Modules) == 0 {
|
||||||
|
return "The state file is empty. No resources are represented."
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("[reset]")
|
||||||
|
|
||||||
|
// Format all the modules
|
||||||
|
for _, m := range s.Modules {
|
||||||
|
if len(m.Path)-1 <= opts.ModuleDepth || opts.ModuleDepth == -1 {
|
||||||
|
formatStateModuleExpand(&buf, m, opts)
|
||||||
|
} else {
|
||||||
|
formatStateModuleSingle(&buf, m, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the outputs for the root module
|
||||||
|
m := s.RootModule()
|
||||||
|
if len(m.Outputs) > 0 {
|
||||||
|
buf.WriteString("\nOutputs:\n\n")
|
||||||
|
|
||||||
|
// Sort the outputs
|
||||||
|
ks := make([]string, 0, len(m.Outputs))
|
||||||
|
for k, _ := range m.Outputs {
|
||||||
|
ks = append(ks, k)
|
||||||
|
}
|
||||||
|
sort.Strings(ks)
|
||||||
|
|
||||||
|
// Output each output k/v pair
|
||||||
|
for _, k := range ks {
|
||||||
|
v := m.Outputs[k]
|
||||||
|
switch output := v.Value.(type) {
|
||||||
|
case string:
|
||||||
|
buf.WriteString(fmt.Sprintf("%s = %s", k, output))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
case []interface{}:
|
||||||
|
buf.WriteString(formatListOutput("", k, output))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
case map[string]interface{}:
|
||||||
|
buf.WriteString(formatMapOutput("", k, output))
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts.Color.Color(strings.TrimSpace(buf.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatStateModuleExpand(
|
||||||
|
buf *bytes.Buffer, m *terraform.ModuleState, opts *StateOpts) {
|
||||||
|
var moduleName string
|
||||||
|
if !m.IsRoot() {
|
||||||
|
moduleName = fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
// First get the names of all the resources so we can show them
|
||||||
|
// in alphabetical order.
|
||||||
|
names := make([]string, 0, len(m.Resources))
|
||||||
|
for name, _ := range m.Resources {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
// Go through each resource and begin building up the output.
|
||||||
|
for _, k := range names {
|
||||||
|
name := k
|
||||||
|
if moduleName != "" {
|
||||||
|
name = moduleName + "." + name
|
||||||
|
}
|
||||||
|
|
||||||
|
rs := m.Resources[k]
|
||||||
|
is := rs.Primary
|
||||||
|
var id string
|
||||||
|
if is != nil {
|
||||||
|
id = is.ID
|
||||||
|
}
|
||||||
|
if id == "" {
|
||||||
|
id = "<not created>"
|
||||||
|
}
|
||||||
|
|
||||||
|
taintStr := ""
|
||||||
|
if rs.Primary != nil && rs.Primary.Tainted {
|
||||||
|
taintStr = " (tainted)"
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString(fmt.Sprintf("%s:%s\n", name, taintStr))
|
||||||
|
buf.WriteString(fmt.Sprintf(" id = %s\n", id))
|
||||||
|
|
||||||
|
if is != nil {
|
||||||
|
// Sort the attributes
|
||||||
|
attrKeys := make([]string, 0, len(is.Attributes))
|
||||||
|
for ak, _ := range is.Attributes {
|
||||||
|
// Skip the id attribute since we just show the id directly
|
||||||
|
if ak == "id" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
attrKeys = append(attrKeys, ak)
|
||||||
|
}
|
||||||
|
sort.Strings(attrKeys)
|
||||||
|
|
||||||
|
// Output each attribute
|
||||||
|
for _, ak := range attrKeys {
|
||||||
|
av := is.Attributes[ak]
|
||||||
|
buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("[reset]\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatStateModuleSingle(
|
||||||
|
buf *bytes.Buffer, m *terraform.ModuleState, opts *StateOpts) {
|
||||||
|
// Header with the module name
|
||||||
|
buf.WriteString(fmt.Sprintf("module.%s\n", strings.Join(m.Path[1:], ".")))
|
||||||
|
|
||||||
|
// Now just write how many resources are in here.
|
||||||
|
buf.WriteString(fmt.Sprintf(" %d resource(s)\n", len(m.Resources)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatNestedList(indent string, outputList []interface{}) string {
|
||||||
|
outputBuf := new(bytes.Buffer)
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("%s[", indent))
|
||||||
|
|
||||||
|
lastIdx := len(outputList) - 1
|
||||||
|
|
||||||
|
for i, value := range outputList {
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, " ", value))
|
||||||
|
if i != lastIdx {
|
||||||
|
outputBuf.WriteString(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
|
||||||
|
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatListOutput(indent, outputName string, outputList []interface{}) string {
|
||||||
|
keyIndent := ""
|
||||||
|
|
||||||
|
outputBuf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if outputName != "" {
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("%s%s = [", indent, outputName))
|
||||||
|
keyIndent = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIdx := len(outputList) - 1
|
||||||
|
|
||||||
|
for i, value := range outputList {
|
||||||
|
switch typedValue := value.(type) {
|
||||||
|
case string:
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, keyIndent, value))
|
||||||
|
case []interface{}:
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
|
||||||
|
formatNestedList(indent+keyIndent, typedValue)))
|
||||||
|
case map[string]interface{}:
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
|
||||||
|
formatNestedMap(indent+keyIndent, typedValue)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastIdx != i {
|
||||||
|
outputBuf.WriteString(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputName != "" {
|
||||||
|
if len(outputList) > 0 {
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
|
||||||
|
} else {
|
||||||
|
outputBuf.WriteString("]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatNestedMap(indent string, outputMap map[string]interface{}) string {
|
||||||
|
ks := make([]string, 0, len(outputMap))
|
||||||
|
for k, _ := range outputMap {
|
||||||
|
ks = append(ks, k)
|
||||||
|
}
|
||||||
|
sort.Strings(ks)
|
||||||
|
|
||||||
|
outputBuf := new(bytes.Buffer)
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("%s{", indent))
|
||||||
|
|
||||||
|
lastIdx := len(outputMap) - 1
|
||||||
|
for i, k := range ks {
|
||||||
|
v := outputMap[k]
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s%s = %v", indent+" ", k, v))
|
||||||
|
|
||||||
|
if lastIdx != i {
|
||||||
|
outputBuf.WriteString(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
|
||||||
|
|
||||||
|
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMapOutput(indent, outputName string, outputMap map[string]interface{}) string {
|
||||||
|
ks := make([]string, 0, len(outputMap))
|
||||||
|
for k, _ := range outputMap {
|
||||||
|
ks = append(ks, k)
|
||||||
|
}
|
||||||
|
sort.Strings(ks)
|
||||||
|
|
||||||
|
keyIndent := ""
|
||||||
|
|
||||||
|
outputBuf := new(bytes.Buffer)
|
||||||
|
if outputName != "" {
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("%s%s = {", indent, outputName))
|
||||||
|
keyIndent = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range ks {
|
||||||
|
v := outputMap[k]
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s = %v", indent, keyIndent, k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputName != "" {
|
||||||
|
if len(outputMap) > 0 {
|
||||||
|
outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
|
||||||
|
} else {
|
||||||
|
outputBuf.WriteString("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||||
|
}
|
Loading…
Reference in New Issue