command/json*: updating documentation and adding tests (#19944)

A few minor fixes and cleanups as a result of said tests. Hooray for
eventual consistency!
This commit is contained in:
Kristin Laemmert 2019-01-09 08:59:11 -08:00 committed by GitHub
parent e997373f44
commit cdf7cc2449
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 586 additions and 6 deletions

View File

@ -67,7 +67,7 @@ type change struct {
}
type output struct {
Sensitive bool `json:"sensitive,omitempty"`
Sensitive bool `json:"sensitive"`
Value json.RawMessage `json:"value,omitempty"`
}

View File

@ -25,6 +25,9 @@ type stateValues struct {
type attributeValues map[string]interface{}
func marshalAttributeValues(value cty.Value, schema *configschema.Block) attributeValues {
if value == cty.NilVal {
return nil
}
ret := make(attributeValues)
it := value.ElementIterator()
@ -118,9 +121,7 @@ func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (m
return ret, nil
}
// marshalPlannedValues returns two resource slices:
// The former has attribute values populated and the latter has true/false in
// place of values indicating whether the values are known at plan time.
// marshalPlanResources
func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstance, schemas *terraform.Schemas) ([]resource, error) {
var ret []resource

View File

@ -0,0 +1,313 @@
package jsonplan
import (
"encoding/json"
"reflect"
"testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/terraform"
"github.com/zclconf/go-cty/cty"
)
func TestMarshalAttributeValues(t *testing.T) {
tests := []struct {
Attr cty.Value
Schema *configschema.Block
Want attributeValues
}{
{
cty.NilVal,
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
},
},
nil,
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
},
},
attributeValues{"foo": cty.StringVal("bar")},
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
}),
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
},
},
attributeValues{"foo": cty.NullVal(cty.String)},
},
{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.MapVal(map[string]cty.Value{
"hello": cty.StringVal("world"),
}),
"baz": cty.ListVal([]cty.Value{
cty.StringVal("goodnight"),
cty.StringVal("moon"),
}),
}),
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.Map(cty.String),
Required: true,
},
"baz": {
Type: cty.List(cty.String),
Optional: true,
},
},
},
attributeValues{
"bar": cty.MapVal(map[string]cty.Value{
"hello": cty.StringVal("world"),
}),
"baz": cty.ListVal([]cty.Value{
cty.StringVal("goodnight"),
cty.StringVal("moon"),
}),
},
},
}
for _, test := range tests {
got := marshalAttributeValues(test.Attr, test.Schema)
eq := reflect.DeepEqual(got, test.Want)
if !eq {
t.Fatalf("wrong result:\nGot: %v\nWant: %#v\n", got, test.Want)
}
}
}
func TestMarshalPlannedOutputs(t *testing.T) {
after, _ := plans.NewDynamicValue(cty.StringVal("after"), cty.DynamicPseudoType)
tests := []struct {
Changes *plans.Changes
Want map[string]output
Err bool
}{
{
&plans.Changes{},
nil,
false,
},
{
&plans.Changes{
Outputs: []*plans.OutputChangeSrc{
{
Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
After: after,
},
Sensitive: false,
},
},
},
map[string]output{
"bar": {
Sensitive: false,
Value: json.RawMessage(`"after"`),
},
},
false,
},
{ // Delete action
&plans.Changes{
Outputs: []*plans.OutputChangeSrc{
{
Addr: addrs.OutputValue{Name: "bar"}.Absolute(addrs.RootModuleInstance),
ChangeSrc: plans.ChangeSrc{
Action: plans.Delete,
},
Sensitive: false,
},
},
},
map[string]output{},
false,
},
}
for _, test := range tests {
got, err := marshalPlannedOutputs(test.Changes)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
eq := reflect.DeepEqual(got, test.Want)
if !eq {
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
}
}
}
func TestMarshalPlanResources(t *testing.T) {
tests := []struct {
Action plans.Action
Before cty.Value
After cty.Value
Want []resource
Err bool
}{
{
Action: plans.Create,
Before: cty.NullVal(cty.EmptyObject),
After: cty.ObjectVal(map[string]cty.Value{
"woozles": cty.UnknownVal(cty.String),
"foozles": cty.UnknownVal(cty.String),
}),
Want: []resource{resource{
Address: "test_thing.example",
Mode: "managed",
Type: "test_thing",
Name: "example",
Index: addrs.InstanceKey(nil),
ProviderName: "test",
SchemaVersion: 1,
AttributeValues: attributeValues(nil),
}},
Err: false,
},
{
Action: plans.Delete,
Before: cty.NullVal(cty.EmptyObject),
After: cty.NilVal,
Want: nil,
Err: false,
},
{
Action: plans.Update,
Before: cty.ObjectVal(map[string]cty.Value{
"woozles": cty.StringVal("foo"),
"foozles": cty.StringVal("bar"),
}),
After: cty.ObjectVal(map[string]cty.Value{
"woozles": cty.StringVal("baz"),
"foozles": cty.StringVal("bat"),
}),
Want: []resource{resource{
Address: "test_thing.example",
Mode: "managed",
Type: "test_thing",
Name: "example",
Index: addrs.InstanceKey(nil),
ProviderName: "test",
SchemaVersion: 1,
AttributeValues: attributeValues{
"woozles": cty.StringVal("baz"),
"foozles": cty.StringVal("bat"),
},
}},
Err: false,
},
}
for _, test := range tests {
before, err := plans.NewDynamicValue(test.Before, test.Before.Type())
if err != nil {
t.Fatal(err)
}
after, err := plans.NewDynamicValue(test.After, test.After.Type())
if err != nil {
t.Fatal(err)
}
testChange := &plans.Changes{
Resources: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "example",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
ProviderAddr: addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance),
ChangeSrc: plans.ChangeSrc{
Action: test.Action,
Before: before,
After: after,
},
},
},
}
ris := testResourceAddrs()
got, err := marshalPlanResources(testChange, ris, testSchemas())
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
eq := reflect.DeepEqual(got, test.Want)
if !eq {
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
}
}
}
func testSchemas() *terraform.Schemas {
return &terraform.Schemas{
Providers: map[string]*terraform.ProviderSchema{
"test": &terraform.ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_thing": {
Attributes: map[string]*configschema.Attribute{
"woozles": {Type: cty.String, Optional: true, Computed: true},
"foozles": {Type: cty.String, Optional: true},
},
},
},
ResourceTypeSchemaVersions: map[string]uint64{
"test_thing": 1,
},
},
},
}
}
func testResourceAddrs() []addrs.AbsResourceInstance {
return []addrs.AbsResourceInstance{
mustAddr("test_thing.example"),
}
}
func mustAddr(str string) addrs.AbsResourceInstance {
addr, diags := addrs.ParseAbsResourceInstanceStr(str)
if diags.HasErrors() {
panic(diags.Err())
}
return addr
}

View File

@ -33,7 +33,7 @@ type stateValues struct {
}
type output struct {
Sensitive bool `json:"sensitive,omitempty"`
Sensitive bool `json:"sensitive"`
Value json.RawMessage `json:"value,omitempty"`
}
@ -86,6 +86,9 @@ type resource struct {
type attributeValues map[string]interface{}
func marshalAttributeValues(value cty.Value, schema *configschema.Block) attributeValues {
if value == cty.NilVal {
return nil
}
ret := make(attributeValues)
it := value.ElementIterator()
@ -229,7 +232,7 @@ func marshalResources(resources map[string]*states.Resource, schemas *terraform.
Address: r.Addr.String(),
Type: r.Addr.Type,
Name: r.Addr.Name,
ProviderName: r.ProviderConfig.ProviderConfig.String(),
ProviderName: r.ProviderConfig.ProviderConfig.StringCompact(),
}
switch r.Addr.Mode {

View File

@ -0,0 +1,253 @@
package jsonstate
import (
"encoding/json"
"reflect"
"testing"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
"github.com/zclconf/go-cty/cty"
)
func TestMarshalOutputs(t *testing.T) {
tests := []struct {
Outputs map[string]*states.OutputValue
Want map[string]output
Err bool
}{
{
nil,
nil,
false,
},
{
map[string]*states.OutputValue{
"test": {
Sensitive: true,
Value: cty.StringVal("sekret"),
},
},
map[string]output{
"test": {
Sensitive: true,
Value: json.RawMessage(`"sekret"`),
},
},
false,
},
{
map[string]*states.OutputValue{
"test": {
Sensitive: false,
Value: cty.StringVal("not_so_sekret"),
},
},
map[string]output{
"test": {
Sensitive: false,
Value: json.RawMessage(`"not_so_sekret"`),
},
},
false,
},
}
for _, test := range tests {
got, err := marshalOutputs(test.Outputs)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
eq := reflect.DeepEqual(got, test.Want)
if !eq {
// printing the output isn't terribly useful, but it does help indicate which test case failed
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
}
}
}
func TestMarshalAttributeValues(t *testing.T) {
tests := []struct {
Attr cty.Value
Schema *configschema.Block
Want attributeValues
}{
{
cty.NilVal,
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
},
},
nil,
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
},
},
attributeValues{"foo": cty.StringVal("bar")},
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
}),
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.String,
Optional: true,
},
},
},
attributeValues{"foo": cty.NullVal(cty.String)},
},
{
cty.ObjectVal(map[string]cty.Value{
"bar": cty.MapVal(map[string]cty.Value{
"hello": cty.StringVal("world"),
}),
"baz": cty.ListVal([]cty.Value{
cty.StringVal("goodnight"),
cty.StringVal("moon"),
}),
}),
&configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": {
Type: cty.Map(cty.String),
Required: true,
},
"baz": {
Type: cty.List(cty.String),
Optional: true,
},
},
},
attributeValues{
"bar": cty.MapVal(map[string]cty.Value{
"hello": cty.StringVal("world"),
}),
"baz": cty.ListVal([]cty.Value{
cty.StringVal("goodnight"),
cty.StringVal("moon"),
}),
},
},
}
for _, test := range tests {
got := marshalAttributeValues(test.Attr, test.Schema)
eq := reflect.DeepEqual(got, test.Want)
if !eq {
t.Fatalf("wrong result:\nGot: %v\nWant: %#v\n", got, test.Want)
}
}
}
func TestMarshalResources(t *testing.T) {
tests := []struct {
Resources map[string]*states.Resource
Schemas *terraform.Schemas
Want []resource
Err bool
}{
{
nil,
nil,
nil,
false,
},
{
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
EachMode: states.EachList,
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.IntKey(0): {
Current: &states.ResourceInstanceObjectSrc{
SchemaVersion: 1,
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
ProviderConfig: addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
},
},
testSchemas(),
[]resource{
resource{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: addrs.IntKey(0),
ProviderName: "test",
SchemaVersion: 1,
AttributeValues: attributeValues{
"foozles": cty.NullVal(cty.String),
"woozles": cty.StringVal("confuzles"),
},
},
},
false,
},
}
for _, test := range tests {
got, err := marshalResources(test.Resources, test.Schemas)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
eq := reflect.DeepEqual(got, test.Want)
if !eq {
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
}
}
}
func testSchemas() *terraform.Schemas {
return &terraform.Schemas{
Providers: map[string]*terraform.ProviderSchema{
"test": &terraform.ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_thing": {
Attributes: map[string]*configschema.Attribute{
"woozles": {Type: cty.String, Optional: true, Computed: true},
"foozles": {Type: cty.String, Optional: true},
},
},
},
},
},
}
}

View File

@ -13,6 +13,15 @@ from a state or plan file. This can be used to inspect a plan to ensure
that the planned operations are expected, or to inspect the current state
as Terraform sees it.
Machine-readable output is generated by adding the `-json` command-line
flag. This is only available when using `show` with a path to a Terraform
plan file.
**NOTE**
When using the `-json` command-line flag, any sensitive values in
terraform state will be displayed in plain text. For more information, see
[_Sensitive Data in State_](/docs/state/sensitive-data.html).
## Usage
Usage: `terraform show [options] [path]`
@ -24,3 +33,4 @@ The command-line flags are all optional. The list of available flags are:
* `-no-color` - Disables output with coloring
* `-json` - Displays machine-readable output from a plan file