cli: format/state refactor to use blockBodyDiffPrinter

Use functions from format/diff to print values
This commit is contained in:
Kristin Laemmert 2018-09-24 15:00:07 -07:00 committed by Martin Atkins
parent 8063f69e5c
commit 14c28b8de4
3 changed files with 177 additions and 100 deletions

View File

@ -7,12 +7,11 @@ import (
"strings"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans"
"github.com/mitchellh/colorstring"
"github.com/hashicorp/terraform/states"
// "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/colorstring"
)
// StateOpts are the options for formatting a state.
@ -20,6 +19,9 @@ type StateOpts struct {
// State is the state to format. This is required.
State *states.State
// Schemas are used to decode attributes. This is required.
Schemas *terraform.Schemas
// Color is the colorizer. This is optional.
Color *colorstring.Colorize
}
@ -30,6 +32,10 @@ func State(opts *StateOpts) string {
panic("colorize not given")
}
if opts.Schemas == nil {
panic("schemas not given")
}
s := opts.State
if len(s.Modules) == 0 {
return "The state file is empty. No resources are represented."
@ -45,7 +51,7 @@ func State(opts *StateOpts) string {
// Format all the modules
for _, m := range s.Modules {
formatStateModule(&buf, m, opts)
formatStateModule(p, m, opts.Schemas)
}
// Write the outputs for the root module
@ -53,7 +59,7 @@ func State(opts *StateOpts) string {
if m.OutputValues != nil {
if len(m.OutputValues) > 0 {
buf.WriteString("\nOutputs:\n\n")
p.buf.WriteString("\nOutputs:\n\n")
}
// Sort the outputs
@ -66,17 +72,17 @@ func State(opts *StateOpts) string {
// Output each output k/v pair
for _, k := range ks {
v := m.OutputValues[k]
buf.WriteString(fmt.Sprintf("%s = ", k))
p.buf.WriteString(fmt.Sprintf("%s = ", k))
p.writeValue(v.Value, plans.NoOp, 0)
}
}
return opts.Color.Color(strings.TrimSpace(buf.String()))
return opts.Color.Color(strings.TrimSpace(p.buf.String()))
}
func formatStateModule(
buf *bytes.Buffer, m *states.Module, opts *StateOpts) {
p blockBodyDiffPrinter, m *states.Module, schemas *terraform.Schemas) {
var moduleName string
if !m.Addr.IsRoot() {
@ -92,75 +98,64 @@ func formatStateModule(
sort.Strings(names)
// Go through each resource and begin building up the output.
for _, k := range names {
name := k
if moduleName != "" {
name = moduleName + "." + name
for _, key := range names {
taintStr := ""
instances := m.Resources[key].Instances
for k, v := range instances {
name := key
if moduleName != "" {
name = moduleName + "." + name
}
addr := m.Resources[key].Addr
if v.Current.Status == 'T' {
taintStr = "(tainted)"
}
p.buf.WriteString(fmt.Sprintf("# %s: %s\n", addr.Instance(k), taintStr))
taintStr = ""
var schema *configschema.Block
provider := m.Resources[key].ProviderConfig.ProviderConfig.StringCompact()
switch addr.Mode {
case addrs.ManagedResourceMode:
p.buf.WriteString(fmt.Sprintf(
"resource %q %q {\n",
addr.Type,
addr.Name,
))
schema = schemas.Providers[provider].ResourceTypes[addr.Type]
case addrs.DataResourceMode:
p.buf.WriteString(fmt.Sprintf(
"data %q %q {\n",
addr.Type,
addr.Name,
))
schema = schemas.Providers[provider].DataSources[addr.Type]
default:
// should never happen, since the above is exhaustive
p.buf.WriteString(addr.String())
}
val, err := v.Current.Decode(schema.ImpliedType())
if err != nil {
fmt.Println(err.Error())
break
}
for name := range schema.Attributes {
attr := ctyGetAttrMaybeNull(val.Value, name)
if !attr.IsNull() {
p.buf.WriteString(fmt.Sprintf(" %s = ", name))
attr := ctyGetAttrMaybeNull(val.Value, name)
p.writeValue(attr, plans.NoOp, 4)
p.buf.WriteString("\n")
}
}
p.buf.WriteString("}\n\n")
}
addr := m.Resources[k].Addr
switch addr.Mode {
case addrs.ManagedResourceMode:
buf.WriteString(fmt.Sprintf(
"resource %q %q",
addr.Type,
addr.Name,
))
case addrs.DataResourceMode:
buf.WriteString(fmt.Sprintf(
"data %q %q ",
addr.Type,
addr.Name,
))
default:
// should never happen, since the above is exhaustive
buf.WriteString(addr.String())
}
buf.WriteString(" { attrs go here! }\n")
}
// rs := m.Resources[k]
// is := rs.Instance
// var id string
// if is != nil {
// id = is
// }
// 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")
p.buf.WriteString("[reset]\n")
}
func formatNestedList(indent string, outputList []interface{}) string {

View File

@ -1,11 +1,13 @@
package format
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/colorstring"
"github.com/zclconf/go-cty/cty"
)
@ -28,7 +30,7 @@ func TestState(t *testing.T) {
rootModule.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Type: "test_resource",
Name: "baz",
}.Instance(addrs.IntKey(0)),
&states.ResourceInstanceObjectSrc{
@ -47,17 +49,19 @@ func TestState(t *testing.T) {
}{
{
&StateOpts{
State: &states.State{},
Color: disabledColorize,
State: &states.State{},
Color: disabledColorize,
Schemas: &terraform.Schemas{},
},
"The state file is empty. No resources are represented.",
},
{
&StateOpts{
State: state,
Color: disabledColorize,
State: state,
Color: disabledColorize,
Schemas: testSchemas(),
},
"module.test_module.test_resource.foo",
"test_resource.baz",
},
}
@ -72,11 +76,49 @@ func TestState(t *testing.T) {
}
}
func mustParseModuleInstanceStr(s string) addrs.ModuleInstance {
addr, err := addrs.ParseModuleInstanceStr(s)
if err != nil {
fmt.Printf(err.Err().Error())
panic(err)
func testProvider() *terraform.MockProvider {
p := new(terraform.MockProvider)
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
return providers.ReadResourceResponse{NewState: req.PriorState}
}
p.GetSchemaReturn = testProviderSchema()
return p
}
func testProviderSchema() *terraform.ProviderSchema {
return &terraform.ProviderSchema{
Provider: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"region": {Type: cty.String, Optional: true},
},
},
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"foo": {Type: cty.String, Optional: true},
"woozles": {Type: cty.String, Optional: true},
},
},
},
DataSources: map[string]*configschema.Block{
"test_data_source": {
Attributes: map[string]*configschema.Attribute{
"compute": {Type: cty.String, Optional: true},
"value": {Type: cty.String, Computed: true},
},
},
},
}
}
func testSchemas() *terraform.Schemas {
provider := testProvider()
return &terraform.Schemas{
Providers: map[string]*terraform.ProviderSchema{
"test": provider.GetSchemaReturn,
},
}
return addr
}

View File

@ -6,8 +6,10 @@ import (
"os"
"strings"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/plans"
@ -43,6 +45,52 @@ func (c *ShowCommand) Run(args []string) int {
return 1
}
configPath, err := ModulePath(cmdFlags.Args())
if err != nil {
c.Ui.Error(err.Error())
return 1
}
var diags tfdiags.Diagnostics
// Load the backend
b, backendDiags := c.Backend(nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
// We require a local backend
local, ok := b.(backend.Local)
if !ok {
c.showDiagnostics(diags) // in case of any warnings in here
c.Ui.Error(ErrUnsupportedLocalOp)
return 1
}
// Build the operation
opReq := c.Operation(b)
opReq.ConfigDir = configPath
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
c.showDiagnostics(diags)
return 1
}
// Get the context
ctx, _, ctxDiags := local.Context(opReq)
diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
schemas := ctx.Schemas()
env := c.Workspace()
var planErr, stateErr error
var path string
var plan *plans.Plan
@ -72,15 +120,6 @@ func (c *ShowCommand) Run(args []string) int {
}
}
} else {
// Load the backend
b, backendDiags := c.Backend(nil)
if backendDiags.HasErrors() {
c.showDiagnostics(backendDiags)
return 1
}
env := c.Workspace()
// Get the state
stateStore, err := b.StateMgr(env)
if err != nil {
@ -118,8 +157,9 @@ func (c *ShowCommand) Run(args []string) int {
}
c.Ui.Output(format.State(&format.StateOpts{
State: state,
Color: c.Colorize(),
State: state,
Color: c.Colorize(),
Schemas: schemas,
}))
return 0
}