[sdk/go] Fix regression marshaling assets/archives (#8290)
This change fixes a regression marshaling assets/archives that was introduced after adding support for marshaling output values. For example, when setting an `Asset` on a field typed as `AssetOrAchiveInput`, in `marshalInput`, the input was being converted into an `AssetOrArchiveOutput` via the `ToAssetOrArchiveOutputWithContext`. Awaiting the output returns an `*asset` struct, which is itself an `AssetInput`, which causes infinite recursion when passed recursively to `marshalInput`. The fix is to skip the `Input` checks in recursive calls, which is equivalent to the previous behavior before the regression was introduced. Another issue was that when the input is converted to an output, this would result in `marshalInput` always returning an output value, even if the original passed-in value was not an output. To address this, if the output's value is known, not a secret, and has no dependencies, we can return the value itself rather than wrapping it as an output.
This commit is contained in:
parent
530641576d
commit
fbf62399ca
|
@ -18,3 +18,6 @@
|
|||
|
||||
- [sdk/dotnet] - Fix a race condition when detecting exceptions in stack creation
|
||||
[#8294](https://github.com/pulumi/pulumi/pull/8294)
|
||||
|
||||
- [sdk/go] - Fix regression marshaling assets/archives.
|
||||
[#8290](https://github.com/pulumi/pulumi/pull/8290)
|
||||
|
|
|
@ -1504,13 +1504,7 @@ func TestConstructResult(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resource.PropertyMap{
|
||||
"foo": resource.NewOutputProperty(resource.Output{
|
||||
Element: resource.NewStringProperty("hi"),
|
||||
Known: true,
|
||||
}),
|
||||
"someValue": resource.NewOutputProperty(resource.Output{
|
||||
Element: resource.NewStringProperty("something"),
|
||||
Known: true,
|
||||
}),
|
||||
"foo": resource.NewStringProperty("hi"),
|
||||
"someValue": resource.NewStringProperty("something"),
|
||||
}, resolvedProps)
|
||||
}
|
||||
|
|
|
@ -214,12 +214,20 @@ const cannotAwaitFmt = "cannot marshal Output value of type %T; please use Apply
|
|||
|
||||
// marshalInput marshals an input value, returning its raw serializable value along with any dependencies.
|
||||
func marshalInput(v interface{}, destType reflect.Type, await bool) (resource.PropertyValue, []Resource, error) {
|
||||
return marshalInputImpl(v, destType, await, false /*skipInputCheck*/)
|
||||
}
|
||||
|
||||
// marshalInputImpl marshals an input value, returning its raw serializable value along with any dependencies.
|
||||
func marshalInputImpl(v interface{},
|
||||
destType reflect.Type,
|
||||
await,
|
||||
skipInputCheck bool) (resource.PropertyValue, []Resource, error) {
|
||||
var deps []Resource
|
||||
for {
|
||||
valueType := reflect.TypeOf(v)
|
||||
|
||||
// If this is an Input, make sure it is of the proper type and await it if it is an output/
|
||||
if input, ok := v.(Input); ok {
|
||||
if input, ok := v.(Input); !skipInputCheck && ok {
|
||||
if inputType := reflect.ValueOf(input); inputType.Kind() == reflect.Ptr && inputType.IsNil() {
|
||||
// input type is a ptr type with a nil backing value
|
||||
return resource.PropertyValue{}, nil, nil
|
||||
|
@ -259,34 +267,45 @@ func marshalInput(v interface{}, destType reflect.Type, await bool) (resource.Pr
|
|||
return resource.PropertyValue{}, nil, err
|
||||
}
|
||||
|
||||
// Get the underlying value, if known.
|
||||
var element resource.PropertyValue
|
||||
if known {
|
||||
element, _, err = marshalInputImpl(ov, destType, await, true /*skipInputCheck*/)
|
||||
if err != nil {
|
||||
return resource.PropertyValue{}, nil, err
|
||||
}
|
||||
|
||||
// If it's known, not a secret, and has no deps, return the value itself.
|
||||
if !secret && len(outputDeps) == 0 {
|
||||
return element, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Expand dependencies.
|
||||
urnSet, err := expandDependencies(context.TODO(), outputDeps)
|
||||
if err != nil {
|
||||
return resource.PropertyValue{}, nil, err
|
||||
}
|
||||
urns := urnSet.sortedValues()
|
||||
var dependencies []resource.URN
|
||||
if len(urns) > 0 {
|
||||
dependencies = make([]resource.URN, len(urns))
|
||||
for i, urn := range urns {
|
||||
if len(urnSet) > 0 {
|
||||
dependencies = make([]resource.URN, len(urnSet))
|
||||
for i, urn := range urnSet.sortedValues() {
|
||||
dependencies[i] = resource.URN(urn)
|
||||
}
|
||||
}
|
||||
|
||||
out := resource.Output{
|
||||
return resource.NewOutputProperty(resource.Output{
|
||||
Element: element,
|
||||
Known: known,
|
||||
Secret: secret,
|
||||
Dependencies: dependencies,
|
||||
}
|
||||
if known {
|
||||
out.Element, _, err = marshalInput(ov, destType, await)
|
||||
if err != nil {
|
||||
return resource.PropertyValue{}, nil, err
|
||||
}
|
||||
}
|
||||
return resource.NewOutputProperty(out), outputDeps, nil
|
||||
}), outputDeps, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Set skipInputCheck to false, so that if we loop around we don't skip the input check.
|
||||
skipInputCheck = false
|
||||
|
||||
// If v is nil, just return that.
|
||||
if v == nil {
|
||||
return resource.PropertyValue{}, nil, nil
|
||||
|
|
|
@ -915,20 +915,29 @@ func TestOutputValueMarshalling(t *testing.T) {
|
|||
|
||||
out := ctx.newOutput(anyOutputType, resources...)
|
||||
out.getState().resolve(value.value, known, secret, nil)
|
||||
inputs := Map{"value": out}
|
||||
|
||||
expected := resource.Output{
|
||||
Known: known,
|
||||
Secret: secret,
|
||||
Dependencies: deps,
|
||||
expectedValue := value.expected
|
||||
if !known || secret || len(deps) > 0 {
|
||||
v := resource.Output{
|
||||
Known: known,
|
||||
Secret: secret,
|
||||
Dependencies: deps,
|
||||
}
|
||||
if known {
|
||||
v.Element = value.expected
|
||||
}
|
||||
expectedValue = resource.NewOutputProperty(v)
|
||||
}
|
||||
if known {
|
||||
expected.Element = value.expected
|
||||
|
||||
expected := resource.PropertyMap{"value": expectedValue}
|
||||
if value.value == nil && known && !secret && len(deps) == 0 {
|
||||
// marshalInputs excludes plain nil values.
|
||||
expected = resource.PropertyMap{}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("value=%v, known=%v, secret=%v, deps=%v", value, known, secret, deps)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
inputs := Map{"value": out}
|
||||
expected := resource.PropertyMap{"value": resource.NewOutputProperty(expected)}
|
||||
actual, _, _, err := marshalInputs(inputs)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, actual)
|
||||
|
@ -1129,6 +1138,42 @@ func (o TemplateTagSpecificationArrayOutput) ToTemplateTagSpecificationArrayOutp
|
|||
return o
|
||||
}
|
||||
|
||||
type bucketObjectArgs struct {
|
||||
Source AssetOrArchive `pulumi:"source"`
|
||||
}
|
||||
|
||||
type BucketObjectArgs struct {
|
||||
Source AssetOrArchiveInput
|
||||
}
|
||||
|
||||
func (BucketObjectArgs) ElementType() reflect.Type {
|
||||
return reflect.TypeOf((*bucketObjectArgs)(nil)).Elem()
|
||||
}
|
||||
|
||||
type myResourceArgs struct {
|
||||
Res Resource `pulumi:"res"`
|
||||
}
|
||||
|
||||
type MyResourceArgs struct {
|
||||
Res ResourceInput
|
||||
}
|
||||
|
||||
func (MyResourceArgs) ElementType() reflect.Type {
|
||||
return reflect.TypeOf((*myResourceArgs)(nil)).Elem()
|
||||
}
|
||||
|
||||
type myNestedOutputArgs struct {
|
||||
Nested interface{} `pulumi:"nested"`
|
||||
}
|
||||
|
||||
type MyNestedOutputArgs struct {
|
||||
Nested Input
|
||||
}
|
||||
|
||||
func (MyNestedOutputArgs) ElementType() reflect.Type {
|
||||
return reflect.TypeOf((*myNestedOutputArgs)(nil)).Elem()
|
||||
}
|
||||
|
||||
func TestOutputValueMarshallingNested(t *testing.T) {
|
||||
ctx, err := NewContext(context.Background(), RunInfo{})
|
||||
assert.Nil(t, err)
|
||||
|
@ -1149,6 +1194,26 @@ func TestOutputValueMarshallingNested(t *testing.T) {
|
|||
unknownStringOutput := ctx.newOutput(stringOutputType).(StringOutput)
|
||||
unknownStringOutput.getState().resolve("", false /*known*/, false /*secret*/, nil)
|
||||
|
||||
assetOutputType := reflect.TypeOf((*AssetOutput)(nil)).Elem()
|
||||
fileAssetOutput := ctx.newOutput(assetOutputType).(AssetOutput)
|
||||
fileAssetOutput.getState().resolve(&asset{path: "foo.txt"}, true /*known*/, false /*secret*/, nil)
|
||||
fileAssetSecretOutput := ctx.newOutput(assetOutputType).(AssetOutput)
|
||||
fileAssetSecretOutput.getState().resolve(&asset{path: "foo.txt"}, true /*known*/, true /*secret*/, nil)
|
||||
fileAssetOutputDeps := ctx.newOutput(assetOutputType).(AssetOutput)
|
||||
fileAssetOutputDeps.getState().resolve(&asset{path: "foo.txt"}, true /*known*/, false, /*secret*/
|
||||
[]Resource{newSimpleCustomResource(ctx, "fakeURN", "fakeID")})
|
||||
|
||||
anyOutputType := reflect.TypeOf((*AnyOutput)(nil)).Elem()
|
||||
|
||||
nestedOutput := ctx.newOutput(anyOutputType).(AnyOutput)
|
||||
nestedOutput.getState().resolve(fileAssetOutput, true /*known*/, false /*secret*/, nil)
|
||||
|
||||
nestedPtrOutput := ctx.newOutput(anyOutputType).(AnyOutput)
|
||||
nestedPtrOutput.getState().resolve(&fileAssetOutput, true /*known*/, false /*secret*/, nil)
|
||||
|
||||
nestedNestedOutput := ctx.newOutput(anyOutputType).(AnyOutput)
|
||||
nestedNestedOutput.getState().resolve(nestedOutput, true /*known*/, false /*secret*/, nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input Input
|
||||
|
@ -1277,6 +1342,114 @@ func TestOutputValueMarshallingNested(t *testing.T) {
|
|||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "bucket object with file asset",
|
||||
input: &BucketObjectArgs{
|
||||
Source: NewFileAsset("foo.txt"),
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"source": resource.NewAssetProperty(&resource.Asset{
|
||||
Path: "foo.txt",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "bucket object with file archive",
|
||||
input: &BucketObjectArgs{
|
||||
Source: NewFileArchive("bar.zip"),
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"source": resource.NewArchiveProperty(&resource.Archive{
|
||||
Path: "bar.zip",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "bucket object with file asset output",
|
||||
input: &BucketObjectArgs{
|
||||
Source: fileAssetOutput,
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"source": resource.NewAssetProperty(&resource.Asset{
|
||||
Path: "foo.txt",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "bucket object with file asset secret output",
|
||||
input: &BucketObjectArgs{
|
||||
Source: fileAssetSecretOutput,
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"source": resource.NewOutputProperty(resource.Output{
|
||||
Element: resource.NewAssetProperty(&resource.Asset{
|
||||
Path: "foo.txt",
|
||||
}),
|
||||
Known: true,
|
||||
Secret: true,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "bucket object with file asset with deps",
|
||||
input: &BucketObjectArgs{
|
||||
Source: fileAssetOutputDeps,
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"source": resource.NewOutputProperty(resource.Output{
|
||||
Element: resource.NewAssetProperty(&resource.Asset{
|
||||
Path: "foo.txt",
|
||||
}),
|
||||
Known: true,
|
||||
Dependencies: []resource.URN{"fakeURN"},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "resource",
|
||||
input: &MyResourceArgs{
|
||||
Res: NewResourceInput(newSimpleCustomResource(ctx, "fakeURN", "fakeID")),
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"res": resource.NewResourceReferenceProperty(resource.ResourceReference{
|
||||
URN: "fakeURN",
|
||||
ID: resource.NewStringProperty("fakeID"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "nested output",
|
||||
input: &MyNestedOutputArgs{
|
||||
Nested: nestedOutput,
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"nested": resource.NewAssetProperty(&resource.Asset{
|
||||
Path: "foo.txt",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "nested ptr output",
|
||||
input: &MyNestedOutputArgs{
|
||||
Nested: nestedPtrOutput,
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"nested": resource.NewAssetProperty(&resource.Asset{
|
||||
Path: "foo.txt",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "nested nested output",
|
||||
input: &MyNestedOutputArgs{
|
||||
Nested: nestedNestedOutput,
|
||||
},
|
||||
expected: resource.NewObjectProperty(resource.PropertyMap{
|
||||
"nested": resource.NewAssetProperty(&resource.Asset{
|
||||
Path: "foo.txt",
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue