mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-26 17:01:04 -06:00
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:
parent
e997373f44
commit
cdf7cc2449
@ -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"`
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
313
command/jsonplan/values_test.go
Normal file
313
command/jsonplan/values_test.go
Normal 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
|
||||
}
|
@ -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 {
|
||||
|
253
command/jsonstate/state_test.go
Normal file
253
command/jsonstate/state_test.go
Normal 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},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user