terraform/internal/command/jsonstate/state_test.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1216 lines
31 KiB
Go
Raw Permalink Normal View History

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package jsonstate
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
2021-06-24 17:53:43 -04:00
"github.com/hashicorp/terraform/internal/lang/marks"
2023-07-06 10:22:57 -04:00
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/terraform"
)
func ptrOf[T any](v T) *T {
return &v
}
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"`),
Type: json.RawMessage(`"string"`),
},
},
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"`),
Type: json.RawMessage(`"string"`),
},
},
false,
},
{
map[string]*states.OutputValue{
"mapstring": {
Sensitive: false,
Value: cty.MapVal(map[string]cty.Value{
"beep": cty.StringVal("boop"),
}),
},
"setnumber": {
Sensitive: false,
Value: cty.SetVal([]cty.Value{
cty.NumberIntVal(3),
cty.NumberIntVal(5),
cty.NumberIntVal(7),
cty.NumberIntVal(11),
}),
},
},
map[string]Output{
"mapstring": {
Sensitive: false,
Value: json.RawMessage(`{"beep":"boop"}`),
Type: json.RawMessage(`["map","string"]`),
},
"setnumber": {
Sensitive: false,
Value: json.RawMessage(`[3,5,7,11]`),
Type: json.RawMessage(`["set","number"]`),
},
},
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)
}
if !cmp.Equal(test.Want, got) {
t.Fatalf("wrong result:\n%s", cmp.Diff(test.Want, got))
}
}
}
func TestMarshalAttributeValues(t *testing.T) {
tests := []struct {
Handle marks a little more consistently In the very first implementation of "sensitive values" we were unfortunately not disciplined about separating the idea of "marked value" from the idea of "sensitive value" (where the latter is a subset of the former). The first implementation just assumed that any marking whatsoever meant "sensitive". We later improved that by adding the marks package and the marks.Sensitive value to standardize on the representation of "sensitive value" as being a value marked with _that specific mark_. However, we did not perform a thorough review of all of the mark-handling codepaths to make sure they all agreed on that definition. In particular, the state and plan models were both designed as if they supported arbitrary marks but then in practice marks other than marks.Sensitive would be handled in various inconsistent ways: dropped entirely, or interpreted as if marks.Sensitive, and possibly do so inconsistently when a value is used only in memory vs. round-tripped through a wire/file format. The goal of this commit is to resolve those oddities so that there are now two possible situations: - General mark handling: some codepaths genuinely handle marks generically, by transporting them from input value to output value in a way consistent with how cty itself deals with marks. This is the ideal case because it means we can add new marks in future and assume these codepaths will handle them correctly without any further modifications. - Sensitive-only mark preservation: the codepaths that interact with our wire protocols and file formats typically have only specialized support for sensitive values in particular, and lack support for any other marks. Those codepaths are now subject to a new rule where they must return an error if asked to deal with any other mark, so that if we introduce new marks in future we'll be forced either to define how we'll avoid those markings reaching the file/wire formats or extend the file/wire formats to support the new marks. Some new helper functions in package marks are intended to standardize how we deal with the "sensitive values only" situations, in the hope that this will make it easier to keep things consistent as the codebase evolves in future. In practice the modules runtime only ever uses marks.Sensitive as a mark today, so all of these checks are effectively covering "should never happen" cases. The only other mark Terraform uses is an implementation detail of "terraform console" and does not interact with any of the codepaths that only support sensitive values in particular.
2024-04-16 20:20:33 -04:00
Attr cty.Value
Want AttributeValues
WantSensitivePaths []cty.Path
}{
{
cty.NilVal,
nil,
nil,
},
{
cty.NullVal(cty.String),
nil,
nil,
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bar"),
}),
AttributeValues{"foo": json.RawMessage(`"bar"`)},
nil,
},
{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.NullVal(cty.String),
}),
AttributeValues{"foo": json.RawMessage(`null`)},
nil,
},
{
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"),
}),
}),
AttributeValues{
"bar": json.RawMessage(`{"hello":"world"}`),
"baz": json.RawMessage(`["goodnight","moon"]`),
},
nil,
},
// Sensitive values
{
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"),
2021-06-24 17:53:43 -04:00
cty.StringVal("moon").Mark(marks.Sensitive),
}),
}),
AttributeValues{
mildwonkey/b-show-state (#20032) * command/show: properly marshal attribute values to json marshalAttributeValues in jsonstate and jsonplan packages was returning a cty.Value, which json/encoding could not marshal. These functions now convert those cty.Values into json.RawMessages. * command/jsonplan: planned values should include resources that are not changing * command/jsonplan: return a filtered list of proposed 'after' attributes Previously, proposed 'after' attributes were not being shown if the attributes were not WhollyKnown. jsonplan now iterates through all the `after` attributes, omitting those which are not wholly known. The same was roughly true for after_unknown, and that structure is now correctly populated. In the future we may choose to filter the after_unknown structure to _only_ display unknown attributes, instead of all attributes. * command/jsonconfig: use a unique key for providers so that aliased providers don't get munged together This now uses the same "provider" key from configs.Module, e.g. `providername.provideralias`. * command/jsonplan: unknownAsBool needs to iterate through objects that are not wholly known * command/jsonplan: properly display actions as strings according to the RFC, instead of a plans.Action string. For example: a plans.Action string DeleteThenCreate should be displayed as ["delete", "create"] Tests have been updated to reflect this. * command/jsonplan: return "null" for unknown list items. The length of a list could be meaningful on its own, so we will turn unknowns into "null". The same is less likely true for maps and objects, so we will continue to omit unknown values from those.
2019-01-23 14:46:53 -05:00
"bar": json.RawMessage(`{"hello":"world"}`),
"baz": json.RawMessage(`["goodnight","moon"]`),
},
Handle marks a little more consistently In the very first implementation of "sensitive values" we were unfortunately not disciplined about separating the idea of "marked value" from the idea of "sensitive value" (where the latter is a subset of the former). The first implementation just assumed that any marking whatsoever meant "sensitive". We later improved that by adding the marks package and the marks.Sensitive value to standardize on the representation of "sensitive value" as being a value marked with _that specific mark_. However, we did not perform a thorough review of all of the mark-handling codepaths to make sure they all agreed on that definition. In particular, the state and plan models were both designed as if they supported arbitrary marks but then in practice marks other than marks.Sensitive would be handled in various inconsistent ways: dropped entirely, or interpreted as if marks.Sensitive, and possibly do so inconsistently when a value is used only in memory vs. round-tripped through a wire/file format. The goal of this commit is to resolve those oddities so that there are now two possible situations: - General mark handling: some codepaths genuinely handle marks generically, by transporting them from input value to output value in a way consistent with how cty itself deals with marks. This is the ideal case because it means we can add new marks in future and assume these codepaths will handle them correctly without any further modifications. - Sensitive-only mark preservation: the codepaths that interact with our wire protocols and file formats typically have only specialized support for sensitive values in particular, and lack support for any other marks. Those codepaths are now subject to a new rule where they must return an error if asked to deal with any other mark, so that if we introduce new marks in future we'll be forced either to define how we'll avoid those markings reaching the file/wire formats or extend the file/wire formats to support the new marks. Some new helper functions in package marks are intended to standardize how we deal with the "sensitive values only" situations, in the hope that this will make it easier to keep things consistent as the codebase evolves in future. In practice the modules runtime only ever uses marks.Sensitive as a mark today, so all of these checks are effectively covering "should never happen" cases. The only other mark Terraform uses is an implementation detail of "terraform console" and does not interact with any of the codepaths that only support sensitive values in particular.
2024-04-16 20:20:33 -04:00
[]cty.Path{
cty.GetAttrPath("baz").IndexInt(1),
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%#v", test.Attr), func(t *testing.T) {
Handle marks a little more consistently In the very first implementation of "sensitive values" we were unfortunately not disciplined about separating the idea of "marked value" from the idea of "sensitive value" (where the latter is a subset of the former). The first implementation just assumed that any marking whatsoever meant "sensitive". We later improved that by adding the marks package and the marks.Sensitive value to standardize on the representation of "sensitive value" as being a value marked with _that specific mark_. However, we did not perform a thorough review of all of the mark-handling codepaths to make sure they all agreed on that definition. In particular, the state and plan models were both designed as if they supported arbitrary marks but then in practice marks other than marks.Sensitive would be handled in various inconsistent ways: dropped entirely, or interpreted as if marks.Sensitive, and possibly do so inconsistently when a value is used only in memory vs. round-tripped through a wire/file format. The goal of this commit is to resolve those oddities so that there are now two possible situations: - General mark handling: some codepaths genuinely handle marks generically, by transporting them from input value to output value in a way consistent with how cty itself deals with marks. This is the ideal case because it means we can add new marks in future and assume these codepaths will handle them correctly without any further modifications. - Sensitive-only mark preservation: the codepaths that interact with our wire protocols and file formats typically have only specialized support for sensitive values in particular, and lack support for any other marks. Those codepaths are now subject to a new rule where they must return an error if asked to deal with any other mark, so that if we introduce new marks in future we'll be forced either to define how we'll avoid those markings reaching the file/wire formats or extend the file/wire formats to support the new marks. Some new helper functions in package marks are intended to standardize how we deal with the "sensitive values only" situations, in the hope that this will make it easier to keep things consistent as the codebase evolves in future. In practice the modules runtime only ever uses marks.Sensitive as a mark today, so all of these checks are effectively covering "should never happen" cases. The only other mark Terraform uses is an implementation detail of "terraform console" and does not interact with any of the codepaths that only support sensitive values in particular.
2024-04-16 20:20:33 -04:00
val, got, sensitivePaths, err := marshalAttributeValues(test.Attr)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(got, test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v\n", got, test.Want)
}
Handle marks a little more consistently In the very first implementation of "sensitive values" we were unfortunately not disciplined about separating the idea of "marked value" from the idea of "sensitive value" (where the latter is a subset of the former). The first implementation just assumed that any marking whatsoever meant "sensitive". We later improved that by adding the marks package and the marks.Sensitive value to standardize on the representation of "sensitive value" as being a value marked with _that specific mark_. However, we did not perform a thorough review of all of the mark-handling codepaths to make sure they all agreed on that definition. In particular, the state and plan models were both designed as if they supported arbitrary marks but then in practice marks other than marks.Sensitive would be handled in various inconsistent ways: dropped entirely, or interpreted as if marks.Sensitive, and possibly do so inconsistently when a value is used only in memory vs. round-tripped through a wire/file format. The goal of this commit is to resolve those oddities so that there are now two possible situations: - General mark handling: some codepaths genuinely handle marks generically, by transporting them from input value to output value in a way consistent with how cty itself deals with marks. This is the ideal case because it means we can add new marks in future and assume these codepaths will handle them correctly without any further modifications. - Sensitive-only mark preservation: the codepaths that interact with our wire protocols and file formats typically have only specialized support for sensitive values in particular, and lack support for any other marks. Those codepaths are now subject to a new rule where they must return an error if asked to deal with any other mark, so that if we introduce new marks in future we'll be forced either to define how we'll avoid those markings reaching the file/wire formats or extend the file/wire formats to support the new marks. Some new helper functions in package marks are intended to standardize how we deal with the "sensitive values only" situations, in the hope that this will make it easier to keep things consistent as the codebase evolves in future. In practice the modules runtime only ever uses marks.Sensitive as a mark today, so all of these checks are effectively covering "should never happen" cases. The only other mark Terraform uses is an implementation detail of "terraform console" and does not interact with any of the codepaths that only support sensitive values in particular.
2024-04-16 20:20:33 -04:00
if !reflect.DeepEqual(sensitivePaths, test.WantSensitivePaths) {
t.Errorf("wrong marks\ngot: %#v\nwant: %#v\n", sensitivePaths, test.WantSensitivePaths)
}
if _, marks := val.Unmark(); len(marks) != 0 {
t.Errorf("returned value still has marks; should have been unmarked\n%#v", marks)
}
})
}
t.Run("reject unsupported marks", func(t *testing.T) {
_, _, _, err := marshalAttributeValues(cty.ObjectVal(map[string]cty.Value{
"disallowed": cty.StringVal("a").Mark("unsupported"),
}))
if err == nil {
t.Fatalf("unexpected success; want error")
}
got := err.Error()
Handle marks a little more consistently In the very first implementation of "sensitive values" we were unfortunately not disciplined about separating the idea of "marked value" from the idea of "sensitive value" (where the latter is a subset of the former). The first implementation just assumed that any marking whatsoever meant "sensitive". We later improved that by adding the marks package and the marks.Sensitive value to standardize on the representation of "sensitive value" as being a value marked with _that specific mark_. However, we did not perform a thorough review of all of the mark-handling codepaths to make sure they all agreed on that definition. In particular, the state and plan models were both designed as if they supported arbitrary marks but then in practice marks other than marks.Sensitive would be handled in various inconsistent ways: dropped entirely, or interpreted as if marks.Sensitive, and possibly do so inconsistently when a value is used only in memory vs. round-tripped through a wire/file format. The goal of this commit is to resolve those oddities so that there are now two possible situations: - General mark handling: some codepaths genuinely handle marks generically, by transporting them from input value to output value in a way consistent with how cty itself deals with marks. This is the ideal case because it means we can add new marks in future and assume these codepaths will handle them correctly without any further modifications. - Sensitive-only mark preservation: the codepaths that interact with our wire protocols and file formats typically have only specialized support for sensitive values in particular, and lack support for any other marks. Those codepaths are now subject to a new rule where they must return an error if asked to deal with any other mark, so that if we introduce new marks in future we'll be forced either to define how we'll avoid those markings reaching the file/wire formats or extend the file/wire formats to support the new marks. Some new helper functions in package marks are intended to standardize how we deal with the "sensitive values only" situations, in the hope that this will make it easier to keep things consistent as the codebase evolves in future. In practice the modules runtime only ever uses marks.Sensitive as a mark today, so all of these checks are effectively covering "should never happen" cases. The only other mark Terraform uses is an implementation detail of "terraform console" and does not interact with any of the codepaths that only support sensitive values in particular.
2024-04-16 20:20:33 -04:00
want := `.disallowed: cannot serialize value marked as cty.NewValueMarks("unsupported") for inclusion in a state snapshot (this is a bug in Terraform)`
if got != want {
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
}
})
}
func TestMarshalResources(t *testing.T) {
deposedKey := states.NewDeposedKey()
tests := map[string]struct {
Resources map[string]*states.Resource
Schemas *terraform.Schemas
Want []Resource
Err bool
}{
"nil": {
nil,
nil,
nil,
false,
},
"single resource": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
},
},
false,
},
"single resource_with_sensitive": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
"foozles": json.RawMessage(`"sensuzles"`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
},
},
false,
},
"resource with marks": {
map[string]*states.Resource{
"test_thing.bar": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"foozles":"confuzles"}`),
Handle marks a little more consistently In the very first implementation of "sensitive values" we were unfortunately not disciplined about separating the idea of "marked value" from the idea of "sensitive value" (where the latter is a subset of the former). The first implementation just assumed that any marking whatsoever meant "sensitive". We later improved that by adding the marks package and the marks.Sensitive value to standardize on the representation of "sensitive value" as being a value marked with _that specific mark_. However, we did not perform a thorough review of all of the mark-handling codepaths to make sure they all agreed on that definition. In particular, the state and plan models were both designed as if they supported arbitrary marks but then in practice marks other than marks.Sensitive would be handled in various inconsistent ways: dropped entirely, or interpreted as if marks.Sensitive, and possibly do so inconsistently when a value is used only in memory vs. round-tripped through a wire/file format. The goal of this commit is to resolve those oddities so that there are now two possible situations: - General mark handling: some codepaths genuinely handle marks generically, by transporting them from input value to output value in a way consistent with how cty itself deals with marks. This is the ideal case because it means we can add new marks in future and assume these codepaths will handle them correctly without any further modifications. - Sensitive-only mark preservation: the codepaths that interact with our wire protocols and file formats typically have only specialized support for sensitive values in particular, and lack support for any other marks. Those codepaths are now subject to a new rule where they must return an error if asked to deal with any other mark, so that if we introduce new marks in future we'll be forced either to define how we'll avoid those markings reaching the file/wire formats or extend the file/wire formats to support the new marks. Some new helper functions in package marks are intended to standardize how we deal with the "sensitive values only" situations, in the hope that this will make it easier to keep things consistent as the codebase evolves in future. In practice the modules runtime only ever uses marks.Sensitive as a mark today, so all of these checks are effectively covering "should never happen" cases. The only other mark Terraform uses is an implementation detail of "terraform console" and does not interact with any of the codepaths that only support sensitive values in particular.
2024-04-16 20:20:33 -04:00
AttrSensitivePaths: []cty.Path{
cty.GetAttrPath("foozles"),
},
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
"foozles": json.RawMessage(`"confuzles"`),
"woozles": json.RawMessage(`null`),
},
SensitiveValues: json.RawMessage(`{"foozles":true}`),
},
},
false,
},
"single resource wrong schema": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
SchemaVersion: 1,
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":["confuzles"]}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
nil,
true,
},
"resource with count": {
map[string]*states.Resource{
"test_thing.bar": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.IntKey(0): {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_thing.bar[0]",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: json.RawMessage(`0`),
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
},
},
false,
},
"resource with for_each": {
map[string]*states.Resource{
"test_thing.bar": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.StringKey("rockhopper"): {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_thing.bar[\"rockhopper\"]",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: json.RawMessage(`"rockhopper"`),
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
},
},
false,
},
"deposed resource": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{
states.DeposedKey(deposedKey): {
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
DeposedKey: deposedKey.String(),
AttributeValues: AttributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
},
},
false,
},
"deposed and current resource": {
map[string]*states.Resource{
"test_thing.baz": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Deposed: map[states.DeposedKey]*states.ResourceInstanceObjectSrc{
states.DeposedKey(deposedKey): {
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
mildwonkey/b-show-state (#20032) * command/show: properly marshal attribute values to json marshalAttributeValues in jsonstate and jsonplan packages was returning a cty.Value, which json/encoding could not marshal. These functions now convert those cty.Values into json.RawMessages. * command/jsonplan: planned values should include resources that are not changing * command/jsonplan: return a filtered list of proposed 'after' attributes Previously, proposed 'after' attributes were not being shown if the attributes were not WhollyKnown. jsonplan now iterates through all the `after` attributes, omitting those which are not wholly known. The same was roughly true for after_unknown, and that structure is now correctly populated. In the future we may choose to filter the after_unknown structure to _only_ display unknown attributes, instead of all attributes. * command/jsonconfig: use a unique key for providers so that aliased providers don't get munged together This now uses the same "provider" key from configs.Module, e.g. `providername.provideralias`. * command/jsonplan: unknownAsBool needs to iterate through objects that are not wholly known * command/jsonplan: properly display actions as strings according to the RFC, instead of a plans.Action string. For example: a plans.Action string DeleteThenCreate should be displayed as ["delete", "create"] Tests have been updated to reflect this. * command/jsonplan: return "null" for unknown list items. The length of a list could be meaningful on its own, so we will turn unknowns into "null". The same is less likely true for maps and objects, so we will continue to omit unknown values from those.
2019-01-23 14:46:53 -05:00
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
},
{
Address: "test_thing.bar",
Mode: "managed",
Type: "test_thing",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
DeposedKey: deposedKey.String(),
AttributeValues: AttributeValues{
"foozles": json.RawMessage(`null`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
},
},
false,
},
"resource with marked map attr": {
map[string]*states.Resource{
"test_map_attr.bar": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_map_attr",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"data":{"woozles":"confuzles"}}`),
Handle marks a little more consistently In the very first implementation of "sensitive values" we were unfortunately not disciplined about separating the idea of "marked value" from the idea of "sensitive value" (where the latter is a subset of the former). The first implementation just assumed that any marking whatsoever meant "sensitive". We later improved that by adding the marks package and the marks.Sensitive value to standardize on the representation of "sensitive value" as being a value marked with _that specific mark_. However, we did not perform a thorough review of all of the mark-handling codepaths to make sure they all agreed on that definition. In particular, the state and plan models were both designed as if they supported arbitrary marks but then in practice marks other than marks.Sensitive would be handled in various inconsistent ways: dropped entirely, or interpreted as if marks.Sensitive, and possibly do so inconsistently when a value is used only in memory vs. round-tripped through a wire/file format. The goal of this commit is to resolve those oddities so that there are now two possible situations: - General mark handling: some codepaths genuinely handle marks generically, by transporting them from input value to output value in a way consistent with how cty itself deals with marks. This is the ideal case because it means we can add new marks in future and assume these codepaths will handle them correctly without any further modifications. - Sensitive-only mark preservation: the codepaths that interact with our wire protocols and file formats typically have only specialized support for sensitive values in particular, and lack support for any other marks. Those codepaths are now subject to a new rule where they must return an error if asked to deal with any other mark, so that if we introduce new marks in future we'll be forced either to define how we'll avoid those markings reaching the file/wire formats or extend the file/wire formats to support the new marks. Some new helper functions in package marks are intended to standardize how we deal with the "sensitive values only" situations, in the hope that this will make it easier to keep things consistent as the codebase evolves in future. In practice the modules runtime only ever uses marks.Sensitive as a mark today, so all of these checks are effectively covering "should never happen" cases. The only other mark Terraform uses is an implementation detail of "terraform console" and does not interact with any of the codepaths that only support sensitive values in particular.
2024-04-16 20:20:33 -04:00
AttrSensitivePaths: []cty.Path{
cty.GetAttrPath("data"),
},
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_map_attr.bar",
Mode: "managed",
Type: "test_map_attr",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
"data": json.RawMessage(`{"woozles":"confuzles"}`),
},
SensitiveValues: json.RawMessage(`{"data":true}`),
},
},
false,
},
"single resource with identity": {
map[string]*states.Resource{
"test_identity.bar": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_identity",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles","name":"bar"}`),
IdentityJSON: []byte(`{"foozles":"sensuzles","name":"bar"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
[]Resource{
{
Address: "test_identity.bar",
Mode: "managed",
Type: "test_identity",
Name: "bar",
Index: nil,
ProviderName: "registry.terraform.io/hashicorp/test",
AttributeValues: AttributeValues{
"name": json.RawMessage(`"bar"`),
"foozles": json.RawMessage(`"sensuzles"`),
"woozles": json.RawMessage(`"confuzles"`),
},
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
IdentityValues: IdentityValues{
"name": json.RawMessage(`"bar"`),
"foozles": json.RawMessage(`"sensuzles"`),
},
IdentitySchemaVersion: ptrOf[uint64](0),
},
},
false,
},
"single resource wrong identity schema": {
map[string]*states.Resource{
"test_identity.bar": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_identity",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles","name":"bar"}`),
IdentitySchemaVersion: 1,
IdentityJSON: []byte(`{"foozles":"sensuzles","name":"bar"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
nil,
true,
},
"single resource missing identity schema": {
map[string]*states.Resource{
"test_thing.bar": {
Addr: addrs.AbsResource{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "bar",
},
},
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
addrs.NoKey: {
Current: &states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles"}`),
IdentitySchemaVersion: 0,
IdentityJSON: []byte(`{"foozles":"sensuzles","name":"bar"}`),
},
},
},
ProviderConfig: addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
},
},
testSchemas(),
nil,
true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, err := marshalResources(test.Resources, addrs.RootModuleInstance, test.Schemas)
if test.Err {
if err == nil {
t.Fatal("succeeded; want error")
}
return
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
diff := cmp.Diff(got, test.Want)
if diff != "" {
t.Fatalf("wrong result: %s\n", diff)
}
})
}
}
func TestMarshalModules_basic(t *testing.T) {
childModule, _ := addrs.ParseModuleInstanceStr("module.child")
subModule, _ := addrs.ParseModuleInstanceStr("module.submodule")
testState := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(childModule),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: childModule.Module(),
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(subModule),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: subModule.Module(),
},
)
})
moduleMap := make(map[string][]addrs.ModuleInstance)
moduleMap[""] = []addrs.ModuleInstance{childModule, subModule}
got, err := marshalModules(testState, testSchemas(), moduleMap[""], moduleMap)
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if len(got) != 2 {
t.Fatalf("wrong result! got %d modules, expected 2", len(got))
}
if got[0].Address != "module.child" || got[1].Address != "module.submodule" {
t.Fatalf("wrong result! got %#v\n", got)
}
}
func TestMarshalModules_nested(t *testing.T) {
childModule, _ := addrs.ParseModuleInstanceStr("module.child")
subModule, _ := addrs.ParseModuleInstanceStr("module.child.module.submodule")
testState := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(childModule),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: childModule.Module(),
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(subModule),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: subModule.Module(),
},
)
})
moduleMap := make(map[string][]addrs.ModuleInstance)
moduleMap[""] = []addrs.ModuleInstance{childModule}
moduleMap[childModule.String()] = []addrs.ModuleInstance{subModule}
got, err := marshalModules(testState, testSchemas(), moduleMap[""], moduleMap)
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if len(got) != 1 {
t.Fatalf("wrong result! got %d modules, expected 1", len(got))
}
if got[0].Address != "module.child" {
t.Fatalf("wrong result! got %#v\n", got)
}
if got[0].ChildModules[0].Address != "module.child.module.submodule" {
t.Fatalf("wrong result! got %#v\n", got)
}
}
func TestMarshalModules_parent_no_resources(t *testing.T) {
subModule, _ := addrs.ParseModuleInstanceStr("module.child.module.submodule")
testState := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(subModule),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"foo","foo":"value","bar":"value"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: subModule.Module(),
},
)
})
got, err := marshalRootModule(testState, testSchemas())
if err != nil {
t.Fatalf("unexpected error: %s", err.Error())
}
if len(got.ChildModules) != 1 {
t.Fatalf("wrong result! got %d modules, expected 1", len(got.ChildModules))
}
if got.ChildModules[0].Address != "module.child" {
t.Fatalf("wrong result! got %#v\n", got)
}
if got.ChildModules[0].ChildModules[0].Address != "module.child.module.submodule" {
t.Fatalf("wrong result! got %#v\n", got)
}
}
func testSchemas() *terraform.Schemas {
return &terraform.Schemas{
2023-07-06 10:35:33 -04:00
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.NewDefaultProvider("test"): {
2023-07-06 10:22:57 -04:00
ResourceTypes: map[string]providers.Schema{
"test_thing": {
2025-03-04 10:33:43 -05:00
Body: &configschema.Block{
2023-07-06 10:22:57 -04:00
Attributes: map[string]*configschema.Attribute{
"woozles": {Type: cty.String, Optional: true, Computed: true},
"foozles": {Type: cty.String, Optional: true, Sensitive: true},
},
},
},
"test_instance": {
2025-03-04 10:33:43 -05:00
Body: &configschema.Block{
2023-07-06 10:22:57 -04:00
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"foo": {Type: cty.String, Optional: true},
"bar": {Type: cty.String, Optional: true},
},
},
},
"test_map_attr": {
2025-03-04 10:33:43 -05:00
Body: &configschema.Block{
2023-07-06 10:22:57 -04:00
Attributes: map[string]*configschema.Attribute{
"data": {Type: cty.Map(cty.String), Optional: true, Computed: true, Sensitive: true},
},
},
},
"test_identity": {
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"name": {Type: cty.String, Required: true},
"woozles": {Type: cty.String, Optional: true, Computed: true},
"foozles": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Identity: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"name": {Type: cty.String, Required: true},
"foozles": {Type: cty.String, Optional: true},
},
Nesting: configschema.NestingSingle,
},
},
},
},
},
}
}
func TestSensitiveAsBool(t *testing.T) {
tests := []struct {
Input cty.Value
Want cty.Value
}{
{
cty.StringVal("hello"),
cty.False,
},
{
cty.NullVal(cty.String),
cty.False,
},
{
2021-06-24 17:53:43 -04:00
cty.StringVal("hello").Mark(marks.Sensitive),
cty.True,
},
{
2021-06-24 17:53:43 -04:00
cty.NullVal(cty.String).Mark(marks.Sensitive),
cty.True,
},
{
2021-06-24 17:53:43 -04:00
cty.NullVal(cty.DynamicPseudoType).Mark(marks.Sensitive),
cty.True,
},
{
cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})),
cty.False,
},
{
2021-06-24 17:53:43 -04:00
cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})).Mark(marks.Sensitive),
cty.True,
},
{
cty.DynamicVal,
cty.False,
},
{
2021-06-24 17:53:43 -04:00
cty.DynamicVal.Mark(marks.Sensitive),
cty.True,
},
{
cty.ListValEmpty(cty.String),
cty.EmptyTupleVal,
},
{
2021-06-24 17:53:43 -04:00
cty.ListValEmpty(cty.String).Mark(marks.Sensitive),
cty.True,
},
{
cty.ListVal([]cty.Value{
cty.StringVal("hello"),
2021-06-24 17:53:43 -04:00
cty.StringVal("friend").Mark(marks.Sensitive),
}),
cty.TupleVal([]cty.Value{
cty.False,
cty.True,
}),
},
{
cty.SetValEmpty(cty.String),
cty.EmptyTupleVal,
},
{
2021-06-24 17:53:43 -04:00
cty.SetValEmpty(cty.String).Mark(marks.Sensitive),
cty.True,
},
{
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.False}),
},
{
2021-06-24 17:53:43 -04:00
cty.SetVal([]cty.Value{cty.StringVal("hello").Mark(marks.Sensitive)}),
cty.True,
},
{
2021-06-24 17:53:43 -04:00
cty.EmptyTupleVal.Mark(marks.Sensitive),
cty.True,
},
{
cty.TupleVal([]cty.Value{
cty.StringVal("hello"),
2021-06-24 17:53:43 -04:00
cty.StringVal("friend").Mark(marks.Sensitive),
}),
cty.TupleVal([]cty.Value{
cty.False,
cty.True,
}),
},
{
cty.MapValEmpty(cty.String),
cty.EmptyObjectVal,
},
{
2021-06-24 17:53:43 -04:00
cty.MapValEmpty(cty.String).Mark(marks.Sensitive),
cty.True,
},
{
cty.MapVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
"animal": cty.StringVal("horse"),
}),
cty.EmptyObjectVal,
},
{
cty.MapVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
2021-06-24 17:53:43 -04:00
"animal": cty.StringVal("horse").Mark(marks.Sensitive),
}),
cty.ObjectVal(map[string]cty.Value{
"animal": cty.True,
}),
},
{
cty.MapVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
2021-06-24 17:53:43 -04:00
"animal": cty.StringVal("horse").Mark(marks.Sensitive),
}).Mark(marks.Sensitive),
cty.True,
},
{
cty.EmptyObjectVal,
cty.EmptyObjectVal,
},
{
cty.ObjectVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
"animal": cty.StringVal("horse"),
}),
cty.EmptyObjectVal,
},
{
cty.ObjectVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
2021-06-24 17:53:43 -04:00
"animal": cty.StringVal("horse").Mark(marks.Sensitive),
}),
cty.ObjectVal(map[string]cty.Value{
"animal": cty.True,
}),
},
{
cty.ObjectVal(map[string]cty.Value{
"greeting": cty.StringVal("hello"),
2021-06-24 17:53:43 -04:00
"animal": cty.StringVal("horse").Mark(marks.Sensitive),
}).Mark(marks.Sensitive),
cty.True,
},
{
cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
2021-06-24 17:53:43 -04:00
"a": cty.StringVal("known").Mark(marks.Sensitive),
}),
}),
cty.TupleVal([]cty.Value{
cty.EmptyObjectVal,
cty.ObjectVal(map[string]cty.Value{
"a": cty.True,
}),
}),
},
{
cty.ListVal([]cty.Value{
cty.MapValEmpty(cty.String),
cty.MapVal(map[string]cty.Value{
2021-06-24 17:53:43 -04:00
"a": cty.StringVal("known").Mark(marks.Sensitive),
}),
cty.MapVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
}),
cty.TupleVal([]cty.Value{
cty.EmptyObjectVal,
cty.ObjectVal(map[string]cty.Value{
"a": cty.True,
}),
cty.EmptyObjectVal,
}),
},
{
cty.ObjectVal(map[string]cty.Value{
"list": cty.UnknownVal(cty.List(cty.String)),
"set": cty.UnknownVal(cty.Set(cty.Bool)),
"tuple": cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Number})),
"map": cty.UnknownVal(cty.Map(cty.String)),
"object": cty.UnknownVal(cty.Object(map[string]cty.Type{"a": cty.String})),
}),
cty.ObjectVal(map[string]cty.Value{
"list": cty.EmptyTupleVal,
"set": cty.EmptyTupleVal,
"tuple": cty.EmptyTupleVal,
"map": cty.EmptyObjectVal,
"object": cty.EmptyObjectVal,
}),
},
}
for _, test := range tests {
got := SensitiveAsBool(test.Input)
if !reflect.DeepEqual(got, test.Want) {
t.Errorf(
"wrong result\ninput: %#v\ngot: %#v\nwant: %#v",
test.Input, got, test.Want,
)
}
}
}