pulumi/sdk/go/common/resource/plugin/rpc_test.go

1943 lines
56 KiB
Go

// Copyright 2016-2021, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package plugin
import (
"fmt"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/protobuf/types/known/structpb"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/archive"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/asset"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
func setProperty(key resource.PropertyKey, s *structpb.Value, k string, v interface{}) {
marshaled, err := MarshalPropertyValue(key, resource.NewPropertyValue(v), MarshalOptions{})
contract.Assertf(err == nil, "error marshaling property value")
s.GetStructValue().Fields[k] = marshaled
}
func TestAssetSerialize(t *testing.T) {
t.Parallel()
// Ensure that asset and archive serialization round trips.
text := "a test asset"
pk := resource.PropertyKey("a test asset uri")
anAsset, err := asset.FromText(text)
assert.NoError(t, err)
assert.Equal(t, text, anAsset.Text)
assert.Equal(t, "e34c74529110661faae4e121e57165ff4cb4dbdde1ef9770098aa3695e6b6704", anAsset.Hash)
assetProps, err := MarshalPropertyValue(pk, resource.NewAssetProperty(anAsset), MarshalOptions{})
assert.NoError(t, err)
t.Logf("%v", assetProps)
assetValue, err := UnmarshalPropertyValue("", assetProps, MarshalOptions{})
assert.NoError(t, err)
assert.True(t, assetValue.IsAsset())
assetDes := assetValue.AssetValue()
assert.True(t, assetDes.IsText())
assert.Equal(t, text, assetDes.Text)
assert.Equal(t, "e34c74529110661faae4e121e57165ff4cb4dbdde1ef9770098aa3695e6b6704", assetDes.Hash)
// Ensure that an invalid asset produces an error.
setProperty(pk, assetProps, resource.AssetHashProperty, 0)
_, err = UnmarshalPropertyValue("", assetProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected asset hash of type float64")
setProperty(pk, assetProps, resource.AssetHashProperty, anAsset.Hash)
setProperty(pk, assetProps, resource.AssetTextProperty, 0)
_, err = UnmarshalPropertyValue("", assetProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected asset text of type float64")
setProperty(pk, assetProps, resource.AssetTextProperty, "")
setProperty(pk, assetProps, resource.AssetPathProperty, 0)
_, err = UnmarshalPropertyValue("", assetProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected asset path of type float64")
setProperty(pk, assetProps, resource.AssetPathProperty, "")
setProperty(pk, assetProps, resource.AssetURIProperty, 0)
_, err = UnmarshalPropertyValue("", assetProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected asset URI of type float64")
setProperty(pk, assetProps, resource.AssetURIProperty, "")
arch, err := archive.FromAssets(map[string]interface{}{"foo": anAsset})
assert.NoError(t, err)
switch runtime.Version() {
case "go1.9":
assert.Equal(t, "d8ce0142b3b10300c7c76487fad770f794c1e84e1b0c73a4b2e1503d4fbac093", arch.Hash)
default:
// Go 1.10 introduced breaking changes to archive/zip and archive/tar headers
assert.Equal(t, "27ab4a14a617df10cff3e1cf4e30cf510302afe56bf4cc91f84041c9f7b62fd8", arch.Hash)
}
archProps, err := MarshalPropertyValue(pk, resource.NewArchiveProperty(arch), MarshalOptions{})
assert.NoError(t, err)
archValue, err := UnmarshalPropertyValue("", archProps, MarshalOptions{})
assert.NoError(t, err)
assert.True(t, archValue.IsArchive())
archDes := archValue.ArchiveValue()
assert.True(t, archDes.IsAssets())
assert.Equal(t, 1, len(archDes.Assets))
assert.True(t, archDes.Assets["foo"].(*asset.Asset).IsText())
assert.Equal(t, text, archDes.Assets["foo"].(*asset.Asset).Text)
switch runtime.Version() {
case "go1.9":
assert.Equal(t, "d8ce0142b3b10300c7c76487fad770f794c1e84e1b0c73a4b2e1503d4fbac093", archDes.Hash)
default:
// Go 1.10 introduced breaking changes to archive/zip and archive/tar headers
assert.Equal(t, "27ab4a14a617df10cff3e1cf4e30cf510302afe56bf4cc91f84041c9f7b62fd8", archDes.Hash)
}
// Ensure that an invalid archive produces an error.
setProperty(pk, archProps, resource.ArchiveHashProperty, 0)
_, err = UnmarshalPropertyValue("", archProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected archive hash of type float64")
setProperty(pk, archProps, resource.ArchiveHashProperty, arch.Hash)
setProperty(pk, archProps, resource.ArchiveAssetsProperty, 0)
_, err = UnmarshalPropertyValue("", archProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected archive contents of type float64")
setProperty(pk, archProps, resource.ArchiveAssetsProperty, nil)
setProperty(pk, archProps, resource.ArchivePathProperty, 0)
_, err = UnmarshalPropertyValue("", archProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected archive path of type float64")
setProperty(pk, archProps, resource.ArchivePathProperty, "")
setProperty(pk, archProps, resource.ArchiveURIProperty, 0)
_, err = UnmarshalPropertyValue("", archProps, MarshalOptions{})
assert.EqualError(t, err, "unexpected archive URI of type float64")
setProperty(pk, archProps, resource.ArchiveURIProperty, "")
}
func TestComputedSerialize(t *testing.T) {
t.Parallel()
// Ensure that computed properties survive round trips.
opts := MarshalOptions{KeepUnknowns: true}
pk := resource.PropertyKey("pk")
{
cprop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewStringProperty("")}), opts)
assert.NoError(t, err)
cpropU, err := UnmarshalPropertyValue(pk, cprop, opts)
assert.NoError(t, err)
assert.True(t, cpropU.IsComputed())
assert.True(t, cpropU.Input().Element.IsString())
}
{
cprop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewNumberProperty(0)}), opts)
assert.NoError(t, err)
cpropU, err := UnmarshalPropertyValue(pk, cprop, opts)
assert.NoError(t, err)
assert.True(t, cpropU.IsComputed())
assert.True(t, cpropU.Input().Element.IsNumber())
}
}
func TestComputedSkip(t *testing.T) {
t.Parallel()
// Ensure that computed properties are skipped when KeepUnknowns == false.
opts := MarshalOptions{KeepUnknowns: false}
pk := resource.PropertyKey("pk")
{
cprop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewStringProperty("")}), opts)
assert.NoError(t, err)
assert.Nil(t, cprop)
}
{
cprop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewNumberProperty(0)}), opts)
assert.NoError(t, err)
assert.Nil(t, cprop)
}
}
func TestComputedReject(t *testing.T) {
t.Parallel()
// Ensure that computed properties produce errors when RejectUnknowns == true.
opts := MarshalOptions{RejectUnknowns: true}
pk := resource.PropertyKey("pk")
{
cprop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewStringProperty("")}), opts)
assert.EqualError(t, err, "unexpected unknown property value for \"pk\"")
assert.Nil(t, cprop)
}
{
cprop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewStringProperty("")}), MarshalOptions{KeepUnknowns: true})
assert.NoError(t, err)
cpropU, err := UnmarshalPropertyValue(pk, cprop, opts)
assert.EqualError(t, err, "unexpected unknown property value for \"pk\"")
assert.Nil(t, cpropU)
}
}
func TestAssetReject(t *testing.T) {
t.Parallel()
// Ensure that asset and archive properties produce errors when RejectAssets == true.
opts := MarshalOptions{RejectAssets: true}
text := "a test asset"
pk := resource.PropertyKey("an asset URI")
asset, err := asset.FromText(text)
assert.NoError(t, err)
{
assetProps, err := MarshalPropertyValue(pk, resource.NewAssetProperty(asset), opts)
assert.EqualError(t, err, "unexpected Asset property value for \"an asset URI\"")
assert.Nil(t, assetProps)
}
{
assetProps, err := MarshalPropertyValue(pk, resource.NewAssetProperty(asset), MarshalOptions{})
assert.NoError(t, err)
assetPropU, err := UnmarshalPropertyValue(pk, assetProps, opts)
assert.EqualError(t, err, "unexpected Asset property value for \"an asset URI\"")
assert.Nil(t, assetPropU)
}
arch, err := archive.FromAssets(map[string]interface{}{"foo": asset})
assert.NoError(t, err)
{
archProps, err := MarshalPropertyValue(pk, resource.NewArchiveProperty(arch), opts)
assert.EqualError(t, err, "unexpected Asset Archive property value for \"an asset URI\"")
assert.Nil(t, archProps)
}
{
archProps, err := MarshalPropertyValue(pk, resource.NewArchiveProperty(arch), MarshalOptions{})
assert.NoError(t, err)
archValue, err := UnmarshalPropertyValue(pk, archProps, opts)
assert.EqualError(t, err, "unexpected Asset property value for \"foo\"")
assert.Nil(t, archValue)
}
}
func TestUnsupportedSecret(t *testing.T) {
t.Parallel()
rawProp := resource.NewObjectProperty(resource.NewPropertyMapFromMap(map[string]interface{}{
resource.SigKey: resource.SecretSig,
"value": "foo",
}))
pk := resource.PropertyKey("pk")
prop, err := MarshalPropertyValue(pk, rawProp, MarshalOptions{})
assert.NoError(t, err)
val, err := UnmarshalPropertyValue(pk, prop, MarshalOptions{})
assert.NoError(t, err)
assert.True(t, val.IsString())
assert.False(t, val.IsSecret())
assert.Equal(t, "foo", val.StringValue())
}
func TestSupportedSecret(t *testing.T) {
t.Parallel()
rawProp := resource.NewObjectProperty(resource.NewPropertyMapFromMap(map[string]interface{}{
resource.SigKey: resource.SecretSig,
"value": "foo",
}))
pk := resource.PropertyKey("pk")
prop, err := MarshalPropertyValue(pk, rawProp, MarshalOptions{KeepSecrets: true})
assert.NoError(t, err)
val, err := UnmarshalPropertyValue(pk, prop, MarshalOptions{KeepSecrets: true})
assert.NoError(t, err)
assert.False(t, val.IsString())
assert.True(t, val.IsSecret())
assert.Equal(t, "foo", val.SecretValue().Element.StringValue())
}
func TestUnknownSig(t *testing.T) {
t.Parallel()
rawProp := resource.NewObjectProperty(resource.NewPropertyMapFromMap(map[string]interface{}{
resource.SigKey: "foobar",
}))
pk := resource.PropertyKey("pk")
prop, err := MarshalPropertyValue(pk, rawProp, MarshalOptions{})
assert.NoError(t, err)
_, err = UnmarshalPropertyValue(pk, prop, MarshalOptions{})
assert.EqualError(t, err, "unrecognized signature 'foobar' in property map for \"pk\"")
}
func TestSkipInternalKeys(t *testing.T) {
t.Parallel()
opts := MarshalOptions{SkipInternalKeys: true}
expected := &structpb.Struct{
Fields: map[string]*structpb.Value{
"keepers": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
},
},
}
props := resource.NewPropertyMapFromMap(map[string]interface{}{
"__defaults": []string{},
"keepers": map[string]interface{}{
"__defaults": []string{},
},
})
actual, err := MarshalProperties(props, opts)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
}
func TestMarshalProperties(t *testing.T) {
t.Parallel()
tests := []struct {
name string
opts MarshalOptions
props resource.PropertyMap
expected *structpb.Struct
}{
{
name: "empty (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
{
name: "unknown (default)",
props: resource.PropertyMap{
"foo": resource.MakeOutput(resource.NewStringProperty("")),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
{
name: "unknown with deps (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty(""),
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
{
name: "known (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StringValue{
StringValue: "hello",
},
},
},
},
},
{
name: "known with deps (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StringValue{
StringValue: "hello",
},
},
},
},
},
{
name: "secret (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
Secret: true,
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StringValue{
StringValue: "hello",
},
},
},
},
},
{
name: "secret with deps (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StringValue{
StringValue: "hello",
},
},
},
},
},
{
name: "unknown secret (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
{
name: "unknown secret with deps (default)",
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{},
},
},
{
name: "empty (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
},
},
},
},
},
},
},
{
name: "unknown (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.MakeOutput(resource.NewStringProperty("")),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
},
},
},
},
},
},
},
{
name: "unknown with deps (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty(""),
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
},
},
},
{
name: "known (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "hello"},
},
},
},
},
},
},
},
},
{
name: "known with deps (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "hello"},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
},
},
},
{
name: "secret (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
},
},
},
{
name: "secret with deps (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
},
},
},
{
name: "unknown secret (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
},
},
},
{
name: "unknown secret with deps (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
props: resource.PropertyMap{
"foo": resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"foo": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
actual, err := MarshalProperties(tt.props, tt.opts)
assert.NoError(t, err)
assert.Equal(t, tt.expected, actual)
})
}
}
func TestResourceReference(t *testing.T) {
t.Parallel()
// Test round-trip
opts := MarshalOptions{KeepResources: true}
rawProp := resource.MakeCustomResourceReference("fakeURN", "fakeID", "fakeVersion")
pk := resource.PropertyKey("pk")
prop, err := MarshalPropertyValue(pk, rawProp, opts)
assert.NoError(t, err)
actual, err := UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.Equal(t, rawProp, *actual)
// Test unmarshaling as an ID
opts.KeepResources = false
actual, err = UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.Equal(t, resource.NewStringProperty(rawProp.ResourceReferenceValue().ID.StringValue()), *actual)
// Test marshaling as an ID
prop, err = MarshalPropertyValue(pk, rawProp, opts)
assert.NoError(t, err)
opts.KeepResources = true
actual, err = UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.Equal(t, resource.NewStringProperty(rawProp.ResourceReferenceValue().ID.StringValue()), *actual)
// Test unmarshaling as a URN
rawProp = resource.MakeComponentResourceReference("fakeURN", "fakeVersion")
prop, err = MarshalPropertyValue(pk, rawProp, opts)
assert.NoError(t, err)
opts.KeepResources = false
actual, err = UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.Equal(t, resource.NewStringProperty(string(rawProp.ResourceReferenceValue().URN)), *actual)
// Test marshaling as a URN
prop, err = MarshalPropertyValue(pk, rawProp, opts)
assert.NoError(t, err)
opts.KeepResources = true
actual, err = UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.Equal(t, resource.NewStringProperty(string(rawProp.ResourceReferenceValue().URN)), *actual)
}
func TestOutputValueRoundTrip(t *testing.T) {
t.Parallel()
tests := []struct {
name string
raw resource.PropertyValue
}{
{
name: "unknown",
raw: resource.NewOutputProperty(resource.Output{}),
},
{
name: "unknown with deps",
raw: resource.NewOutputProperty(resource.Output{
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
{
name: "known",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
}),
},
{
name: "known with deps",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
{
name: "secret",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("secret"),
Known: true,
Secret: true,
}),
},
{
name: "secret with deps",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("secret"),
Known: true,
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
{
name: "unknown secret",
raw: resource.NewOutputProperty(resource.Output{
Secret: true,
}),
},
{
name: "unknown secret with deps",
raw: resource.NewOutputProperty(resource.Output{
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
opts := MarshalOptions{KeepOutputValues: true}
prop, err := MarshalPropertyValue("", tt.raw, opts)
assert.NoError(t, err)
actual, err := UnmarshalPropertyValue("", prop, opts)
assert.NoError(t, err)
assert.Equal(t, tt.raw, *actual)
})
}
}
func TestOutputValueMarshaling(t *testing.T) {
t.Parallel()
tests := []struct {
name string
opts MarshalOptions
raw resource.PropertyValue
expected *structpb.Value
}{
{
name: "empty (default)",
raw: resource.NewOutputProperty(resource.Output{}),
expected: nil,
},
{
name: "unknown (default)",
raw: resource.MakeOutput(resource.NewStringProperty("")),
expected: nil,
},
{
name: "unknown with deps (default)",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: nil,
},
{
name: "known (default)",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: "hello"},
},
},
{
name: "known with deps (default)",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Known: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: "hello"},
},
},
{
name: "secret (default)",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
},
{
name: "secret with deps (default)",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
},
{
name: "unknown secret (default)",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
}),
expected: nil,
},
{
name: "unknown secret with deps (default)",
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: nil,
},
{
name: "empty (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: resource.NewOutputProperty(resource.Output{}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
{
name: "unknown (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: resource.MakeOutput(resource.NewStringProperty("")),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
{
name: "unknown with deps (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("hello"),
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
{
name: "unknown secret (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
{
name: "unknown secret with deps (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
{
name: "secret (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
},
{
name: "secret with deps (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: &structpb.Value{
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
},
{
name: "secret (KeepSecrets)",
opts: MarshalOptions{KeepSecrets: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
}),
expected: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.SecretSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
},
},
},
},
},
{
name: "secret with deps (KeepSecrets)",
opts: MarshalOptions{KeepSecrets: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Known: true,
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.SecretSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
},
},
},
},
},
{
name: "unknown secret (KeepUnknowns, KeepSecrets)",
opts: MarshalOptions{KeepUnknowns: true, KeepSecrets: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
}),
expected: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.SecretSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
},
},
},
},
{
name: "unknown secret with deps (KeepUnknowns, KeepSecrets)",
opts: MarshalOptions{KeepUnknowns: true, KeepSecrets: true},
raw: resource.NewOutputProperty(resource.Output{
Element: resource.NewStringProperty("shhh"),
Secret: true,
Dependencies: []resource.URN{"fakeURN1", "fakeURN2"},
}),
expected: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.SecretSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
},
},
},
},
{
name: "unknown value (KeepOutputValues)",
opts: MarshalOptions{KeepOutputValues: true},
raw: resource.MakeOutput(resource.NewStringProperty("")),
expected: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
},
},
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
actual, err := MarshalPropertyValue("", tt.raw, tt.opts)
assert.NoError(t, err)
assert.Equal(t, tt.expected, actual)
})
}
}
func TestOutputValueUnmarshaling(t *testing.T) {
t.Parallel()
ptr := func(v resource.PropertyValue) *resource.PropertyValue {
return &v
}
tests := []struct {
name string
opts MarshalOptions
raw *structpb.Value
expected *resource.PropertyValue
}{
{
name: "unknown (default)",
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
},
},
},
},
expected: nil,
},
{
name: "known (default)",
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "hello"},
},
},
},
},
},
expected: ptr(resource.NewStringProperty("hello")),
},
{
name: "known with deps (default)",
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "hello"},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: ptr(resource.NewStringProperty("hello")),
},
{
name: "secret (default)",
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
expected: ptr(resource.NewStringProperty("shhh")),
},
{
name: "secret with deps (default)",
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: ptr(resource.NewStringProperty("shhh")),
},
{
name: "unknown secret (default)",
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
expected: nil,
},
{
name: "unknown secret with deps (default)",
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: nil,
},
{
name: "unknown (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
},
},
},
},
expected: ptr(resource.MakeComputed(resource.NewStringProperty(""))),
},
{
name: "unknown with deps (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: ptr(resource.MakeComputed(resource.NewStringProperty(""))),
},
{
name: "unknown secret (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
expected: ptr(resource.MakeComputed(resource.NewStringProperty(""))),
},
{
name: "unknown secret with deps (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: ptr(resource.MakeComputed(resource.NewStringProperty(""))),
},
{
name: "secret (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
expected: ptr(resource.NewStringProperty("shhh")),
},
{
name: "secret with deps (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: ptr(resource.NewStringProperty("shhh")),
},
{
name: "secret (KeepSecrets)",
opts: MarshalOptions{KeepSecrets: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
expected: ptr(resource.MakeSecret(resource.NewStringProperty("shhh"))),
},
{
name: "secret with deps (KeepSecrets)",
opts: MarshalOptions{KeepSecrets: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"value": {
Kind: &structpb.Value_StringValue{StringValue: "shhh"},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: ptr(resource.MakeSecret(resource.NewStringProperty("shhh"))),
},
{
name: "unknown secret (KeepUnknowns, KeepSecrets)",
opts: MarshalOptions{KeepUnknowns: true, KeepSecrets: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
},
},
},
},
expected: ptr(resource.MakeSecret(resource.MakeComputed(resource.NewStringProperty("")))),
},
{
name: "unknown secret with deps (KeepUnknowns, KeepSecrets)",
opts: MarshalOptions{KeepUnknowns: true, KeepSecrets: true},
raw: &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
"secret": {
Kind: &structpb.Value_BoolValue{BoolValue: true},
},
"dependencies": {
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{
Values: []*structpb.Value{
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN1"}},
{Kind: &structpb.Value_StringValue{StringValue: "fakeURN2"}},
},
},
},
},
},
},
},
},
expected: ptr(resource.MakeSecret(resource.MakeComputed(resource.NewStringProperty("")))),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
prop, err := UnmarshalPropertyValue("", tt.raw, tt.opts)
assert.NoError(t, err)
assert.Equal(t, tt.expected, prop)
})
}
}
func TestMarshalPropertiesDiscardsListNestedUnknowns(t *testing.T) {
t.Parallel()
proto := pbStruct("resources", pbList(pbString(UnknownStringValue)))
propsWithUnknowns, err := UnmarshalProperties(proto, MarshalOptions{KeepUnknowns: true})
if err != nil {
t.Errorf("UnmarshalProperties failed: %v", err)
}
protobufStruct, err := MarshalProperties(propsWithUnknowns, MarshalOptions{KeepUnknowns: false})
if err != nil {
t.Error(err)
return
}
protobufValue := &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: protobufStruct,
},
}
assertValidProtobufValue(t, protobufValue)
}
func pbString(s string) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StringValue{
StringValue: s,
},
}
}
func pbStruct(name string, value *structpb.Value) *structpb.Struct {
return &structpb.Struct{
Fields: map[string]*structpb.Value{
name: value,
},
}
}
func pbList(elems ...*structpb.Value) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_ListValue{
ListValue: &structpb.ListValue{Values: elems},
},
}
}
func assertValidProtobufValue(t *testing.T, value *structpb.Value) {
err := walkValueSelfWithDescendants(value, "", func(path string, v *structpb.Value) error {
return nil
})
if err != nil {
t.Errorf("This is not a valid *structpb.Value: %v\n%v", value, err)
}
}
func walkValueSelfWithDescendants(
v *structpb.Value,
path string,
visit func(path string, v *structpb.Value) error,
) error {
if v == nil {
return fmt.Errorf("bad *structpb.Value nil at %s", path)
}
err := visit(path, v)
if err != nil {
return err
}
switch v.Kind.(type) {
case *structpb.Value_ListValue:
values := v.GetListValue().GetValues()
for i, v := range values {
err = walkValueSelfWithDescendants(v, fmt.Sprintf("%s[%d]", path, i), visit)
if err != nil {
return err
}
}
case *structpb.Value_StructValue:
s := v.GetStructValue()
for fn, fv := range s.Fields {
err = walkValueSelfWithDescendants(fv, fmt.Sprintf(`%s["%s"]`, path, fn), visit)
if err != nil {
return err
}
}
case *structpb.Value_NumberValue:
return nil
case *structpb.Value_StringValue:
return nil
case *structpb.Value_BoolValue:
return nil
case *structpb.Value_NullValue:
return nil
default:
return fmt.Errorf("bad *structpb.Value of unknown type at %s: %v", path, v)
}
return nil
}
func TestMarshalPropertiesDontSkipOutputs(t *testing.T) {
t.Parallel()
tests := []struct {
name string
opts MarshalOptions
props resource.PropertyMap
expected *structpb.Struct
}{
{
name: "Computed (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
props: resource.PropertyMap{
"message": resource.MakeComputed(resource.NewStringProperty("")),
"nested": resource.NewObjectProperty(resource.PropertyMap{
"value": resource.MakeComputed(resource.NewStringProperty("")),
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"message": {
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
"nested": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
"value": {
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
},
},
},
},
},
},
{
name: "Output (KeepUnknowns)",
opts: MarshalOptions{KeepUnknowns: true},
props: resource.PropertyMap{
"message": resource.NewOutputProperty(resource.Output{}),
"nested": resource.NewObjectProperty(resource.PropertyMap{
"value": resource.NewOutputProperty(resource.Output{}),
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"message": {
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
"nested": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
"value": {
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
},
},
},
},
},
},
{
name: "Output (KeepUnknowns, KeepOutputValues)",
opts: MarshalOptions{KeepUnknowns: true, KeepOutputValues: true},
props: resource.PropertyMap{
"message": resource.NewOutputProperty(resource.Output{}),
"nested": resource.NewObjectProperty(resource.PropertyMap{
"value": resource.NewOutputProperty(resource.Output{}),
}),
},
expected: &structpb.Struct{
Fields: map[string]*structpb.Value{
"message": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.OutputValueSig},
},
},
},
},
},
"nested": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
"value": {
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{
StringValue: resource.OutputValueSig,
},
},
},
},
},
},
},
},
},
},
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
actual, err := MarshalProperties(tt.props, tt.opts)
assert.NoError(t, err)
assert.Equal(t, tt.expected, actual)
})
}
}
func TestUpgradeToOutputValues(t *testing.T) {
t.Parallel()
opts := MarshalOptions{
KeepUnknowns: true, KeepSecrets: true, KeepOutputValues: true, KeepResources: true,
}
upgradeOpts := MarshalOptions{
KeepUnknowns: true, KeepSecrets: true, KeepOutputValues: true, KeepResources: true, UpgradeToOutputValues: true,
}
pk := resource.PropertyKey("pk")
// Unknown
{
prop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewStringProperty("")}), upgradeOpts)
assert.NoError(t, err)
propU, err := UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.True(t, propU.IsOutput())
assert.False(t, propU.OutputValue().Known)
assert.False(t, propU.OutputValue().Secret)
}
{
prop, err := MarshalPropertyValue(pk,
resource.NewComputedProperty(
resource.Computed{Element: resource.NewStringProperty("")}), opts)
assert.NoError(t, err)
propU, err := UnmarshalPropertyValue(pk, prop, upgradeOpts)
assert.NoError(t, err)
assert.True(t, propU.IsOutput())
assert.False(t, propU.OutputValue().Known)
assert.False(t, propU.OutputValue().Secret)
}
// Secrets
{
elem := resource.NewStringProperty("hello")
prop, err := MarshalPropertyValue(pk,
resource.NewSecretProperty(
&resource.Secret{Element: elem}), upgradeOpts)
assert.NoError(t, err)
propU, err := UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.True(t, propU.IsOutput())
assert.True(t, propU.OutputValue().Known)
assert.True(t, propU.OutputValue().Secret)
assert.Equal(t, elem, propU.OutputValue().Element)
}
{
elem := resource.NewStringProperty("hello")
prop, err := MarshalPropertyValue(pk,
resource.NewSecretProperty(
&resource.Secret{Element: elem}), opts)
assert.NoError(t, err)
propU, err := UnmarshalPropertyValue(pk, prop, upgradeOpts)
assert.NoError(t, err)
assert.True(t, propU.IsOutput())
assert.True(t, propU.OutputValue().Known)
assert.True(t, propU.OutputValue().Secret)
assert.Equal(t, elem, propU.OutputValue().Element)
}
// Resource reference
{
elem := resource.ResourceReference{
URN: resource.CreateURN("name", "type", "", "project", "stack"),
ID: resource.MakeComputed(resource.NewStringProperty("")),
}
prop, err := MarshalPropertyValue(pk, resource.NewResourceReferenceProperty(elem), upgradeOpts)
assert.NoError(t, err)
propU, err := UnmarshalPropertyValue(pk, prop, opts)
assert.NoError(t, err)
assert.True(t, propU.IsResourceReference())
assert.Equal(t, elem.URN, propU.ResourceReferenceValue().URN)
id := propU.ResourceReferenceValue().ID
// ResourceReferences are always Computed even if output upgrades are turned on.
assert.True(t, id.IsComputed())
}
{
elem := resource.ResourceReference{
URN: resource.CreateURN("name", "type", "", "project", "stack"),
ID: resource.MakeComputed(resource.NewStringProperty("")),
}
prop, err := MarshalPropertyValue(pk, resource.NewResourceReferenceProperty(elem), opts)
assert.NoError(t, err)
propU, err := UnmarshalPropertyValue(pk, prop, upgradeOpts)
assert.NoError(t, err)
assert.True(t, propU.IsResourceReference())
assert.Equal(t, elem.URN, propU.ResourceReferenceValue().URN)
id := propU.ResourceReferenceValue().ID
assert.True(t, id.IsComputed())
}
// Some SDKs send the unknown string value instead of "" for the unknown ID
{
urn := string(resource.CreateURN("name", "type", "", "project", "stack"))
prop := &structpb.Value{
Kind: &structpb.Value_StructValue{
StructValue: &structpb.Struct{
Fields: map[string]*structpb.Value{
resource.SigKey: {
Kind: &structpb.Value_StringValue{StringValue: resource.ResourceReferenceSig},
},
"urn": {
Kind: &structpb.Value_StringValue{StringValue: urn},
},
"id": {
Kind: &structpb.Value_StringValue{StringValue: UnknownStringValue},
},
},
},
},
}
propU, err := UnmarshalPropertyValue(pk, prop, upgradeOpts)
assert.NoError(t, err)
assert.True(t, propU.IsResourceReference())
assert.Equal(t, resource.URN(urn), propU.ResourceReferenceValue().URN)
id := propU.ResourceReferenceValue().ID
assert.True(t, id.IsComputed())
}
}