Merge remote-tracking branch 'origin/master' into ctpp

This commit is contained in:
Fraser Waters 2021-11-02 09:19:17 +00:00
commit 0d9043f6a2
51 changed files with 364 additions and 195 deletions

5
.gitignore vendored
View file

@ -2,7 +2,7 @@
**/vendor/
**/node_modules/
**/bin
**/.vscode/
**/.vscode/**/*
**/.vs/
**/.ionide/
**/.idea/
@ -17,6 +17,9 @@ coverage.cov
# VSCode creates this binary when running tests in the debugger
**/debug.test
# Check in vscode settings for this workspace. This is so we can save common settings like setting go build tags to use by default.
!**/.vscode/settings.json
# Go tests run "in tree" and this folder will linger if they fail (the integration test framework cleans
# it up when they pass.)
**/command-output/

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"go.buildTags": "all"
}

View file

@ -8,10 +8,8 @@
- [sdk/go] - Respect implicit parents in alias resolution
[#8288](https://github.com/pulumi/pulumi/pull/8288)
- [sdk/dotnet] - Fix a race condition when detecting exceptions in stack creation
[#8294](https://github.com/pulumi/pulumi/pull/8294)
- Clarify error message string in `sdk/go/common/diag/errors.go`
[#8284](https://github.com/pulumi/pulumi/pull/8284)
- Clarify error message string in `sdk/go/common/diag/errors.go`
[#8284](https://github.com/pulumi/pulumi/pull/8284)
- [cli] Add `--json` flag to `up`, `destroy` and `refresh`.
@ -20,8 +18,11 @@
However, the streaming output can be extended to `preview` by using the `PULUMI_ENABLE_STREAMING_JSON_PREVIEW` environment variable.
[#8275](https://github.com/pulumi/pulumi/pull/8275)
- [sdk/python] - Expand dependencies when marshaling output values
[#8301](https://github.com/pulumi/pulumi/pull/8301)
- [codegen/go] - Interaction between the `plain` and `default` tags of a type.
- [codegen/go] - Interaction between the `plain` and `default` tags of a type.
[#8254](https://github.com/pulumi/pulumi/pull/8254)
- [sdk/dotnet] - Fix a race condition when detecting exceptions in stack creation
@ -30,6 +31,13 @@
- [sdk/go] - Fix regression marshaling assets/archives.
[#8290](https://github.com/pulumi/pulumi/pull/8290)
- [sdk/dotnet] - Don't panic on schema mismatches
[#8286](https://github.com/pulumi/pulumi/pull/8286)
- [codegen/python] - Fixes issue with `$fn_output` functions failing in
preview when called with unknown arguments
[#8320](https://github.com/pulumi/pulumi/pull/8320)
### Miscellaneous
- [sdk/python] - Drop support for python 3.6

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -13,6 +13,7 @@
# limitations under the License.
import asyncio
import json
import pytest
@ -32,8 +33,20 @@ def my_mocks():
pulumi.runtime.settings.configure(old_settings)
@pytest.fixture
def my_preview_mocks():
old_settings = pulumi.runtime.settings.SETTINGS
try:
mocks = MyMocks()
pulumi.runtime.mocks.set_mocks(mocks, preview=True)
yield mocks
finally:
pulumi.runtime.settings.configure(old_settings)
class MyMocks(pulumi.runtime.Mocks):
def call(self, args):
if args.token in ['mypkg::funcWithAllOptionalInputs',
'mypkg::funcWithDefaultValue']:
a = args.args.get('a', None)
@ -49,6 +62,15 @@ class MyMocks(pulumi.runtime.Mocks):
'value': [args.args]}
if args.token == 'mypkg::listStorageAccountKeys':
if 'accountName' not in args.args or \
not args.args['accountName'] or \
pulumi.contains_unknowns(args.args['accountName']):
raise Exception(
'Missing required argument: '
'The argument "account_name" is required, '
'but no definition was found')
return {'keys': [
dict(creationTime='my-creation-time',
keyName='my-key-name',
@ -193,6 +215,15 @@ def test_list_storage_accounts(my_mocks):
)])
@pulumi.runtime.test
def test_preview_with_unknowns(my_preview_mocks):
def check(r):
assert False, 'check() should not be called when args contain unknowns'
return list_storage_account_keys_output(account_name=unknown()).apply(check)
def jstr(x):
return json.dumps(x, sort_keys=True)
@ -203,3 +234,14 @@ def r(x):
def out(x):
return pulumi.Output.from_input(x).apply(lambda x: x)
def unknown():
is_known_fut: asyncio.Future[bool] = asyncio.Future()
is_secret_fut: asyncio.Future[bool] = asyncio.Future()
is_known_fut.set_result(False)
is_secret_fut.set_result(False)
value_fut: asyncio.Future[Any] = asyncio.Future()
value_fut.set_result(pulumi.UNKNOWN)
return pulumi.Output(set(), value_fut, is_known_fut, is_secret_fut)

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -224,9 +224,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -3017,9 +3017,10 @@ def lift_output_func(func: typing.Any) -> typing.Callable[[_F], _F]:
def lifted_func(*args, opts=None, **kwargs):
bound_args = func_sig.bind(*args, **kwargs)
# Convert tuple to list, see pulumi/pulumi#8172
args_list = list(bound_args.args)
return pulumi.Output.from_input({
'args': bound_args.args,
'args': args_list,
'kwargs': bound_args.kwargs
}).apply(lambda resolved_args: func(*resolved_args['args'],
opts=opts,

View file

@ -36,6 +36,22 @@ namespace Pulumi.Tests.Mocks
public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args) => throw new Exception("Not used");
}
class MyInvalidMocks : IMocks
{
public Task<object> CallAsync(MockCallArgs args)
{
return Task.FromResult<object>(args);
}
public Task<(string? id, object state)> NewResourceAsync(MockResourceArgs args) =>
args.Type switch
{
"aws:ec2/instance:Instance" => Task.FromResult<(string?, object)>(("i-1234567890abcdef0", new Dictionary<string, object> { { "publicIp", unchecked((int)0xcb00710c) }, })),
"pkg:index:MyCustom" => Task.FromResult<(string?, object)>((args.Name + "_id", args.Inputs)),
_ => throw new Exception($"Unknown resource {args.Type}")
};
}
public class MocksTests
{
[Fact]
@ -108,6 +124,20 @@ namespace Pulumi.Tests.Mocks
Assert.Contains("' failed with an unhandled exception:", exception!.Message);
Assert.Contains("Grpc.Core.RpcException: Status(StatusCode=\"Unknown\", Detail=\"error code 404\")", exception!.Message);
}
[Fact]
public async Task TestStackWithInvalidSchema()
{
var resources = await Deployment.TestAsync<MyStack>(new MyInvalidMocks(), new TestOptions { IsPreview = false });
var stack = resources.OfType<MyStack>().FirstOrDefault();
Assert.NotNull(stack);
var ip = await stack!.PublicIp.GetValueAsync(whenUnknown: default!);
Assert.Null(ip);
// TODO: It would be good to assert that a warning was logged to the engine but getting hold of warnings requires re-plumbing what TestAsync returns.
}
}
public static class Testing

View file

@ -36,7 +36,7 @@ namespace Pulumi.Tests.Serialization
private async Task Test(object args, string expected)
{
var serialized = await SerializeToValueAsync(args);
var converted = Converter.ConvertValue<JsonElement>("", serialized);
var converted = Converter.ConvertValue<JsonElement>(NoWarn, "", serialized);
var value = converted.Value.GetProperty("v").GetProperty("s").GetString();
Assert.Equal(expected, value);
}

View file

@ -13,7 +13,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void True()
{
var data = Converter.ConvertValue<bool>("", new Value { BoolValue = true });
var data = Converter.ConvertValue<bool>(NoWarn, "", new Value { BoolValue = true });
Assert.True(data.Value);
Assert.True(data.IsKnown);
}
@ -21,7 +21,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void False()
{
var data = Converter.ConvertValue<bool>("", new Value { BoolValue = false });
var data = Converter.ConvertValue<bool>(NoWarn, "", new Value { BoolValue = false });
Assert.False(data.Value);
Assert.True(data.IsKnown);
@ -30,7 +30,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void SecretTrue()
{
var data = Converter.ConvertValue<bool>("", CreateSecretValue(new Value { BoolValue = true }));
var data = Converter.ConvertValue<bool>(NoWarn, "", CreateSecretValue(new Value { BoolValue = true }));
Assert.True(data.Value);
Assert.True(data.IsKnown);
@ -40,7 +40,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void SecretFalse()
{
var data = Converter.ConvertValue<bool>("", CreateSecretValue(new Value { BoolValue = false }));
var data = Converter.ConvertValue<bool>(NoWarn, "", CreateSecretValue(new Value { BoolValue = false }));
Assert.False(data.Value);
Assert.True(data.IsKnown);
@ -48,12 +48,16 @@ namespace Pulumi.Tests.Serialization
}
[Fact]
public void NonBooleanThrows()
public void NonBooleanLogs()
{
Assert.Throws<InvalidOperationException>(() =>
{
Converter.ConvertValue<bool>("", new Value { StringValue = "" });
});
string? loggedError = null;
Action<string> warn = error => loggedError = error;
var data = Converter.ConvertValue<bool>(warn, "", new Value { StringValue = "" });
Assert.False(data.Value);
Assert.True(data.IsKnown);
Assert.Equal("Expected System.Boolean but got System.String deserializing ", loggedError);
}
[Fact]
@ -61,7 +65,7 @@ namespace Pulumi.Tests.Serialization
{
return RunInPreview(() =>
{
var data = Converter.ConvertValue<bool>("", new Value { NullValue = NullValue.NullValue });
var data = Converter.ConvertValue<bool>(NoWarn, "", new Value { NullValue = NullValue.NullValue });
Assert.False(data.Value);
Assert.True(data.IsKnown);
@ -73,7 +77,7 @@ namespace Pulumi.Tests.Serialization
{
return RunInNormal(() =>
{
var data = Converter.ConvertValue<bool>("", new Value { NullValue = NullValue.NullValue });
var data = Converter.ConvertValue<bool>(NoWarn, "", new Value { NullValue = NullValue.NullValue });
Assert.False(data.Value);
Assert.True(data.IsKnown);
@ -83,25 +87,16 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void UnknownProducesFalseUnknown()
{
var data = Converter.ConvertValue<bool>("", UnknownValue);
var data = Converter.ConvertValue<bool>(NoWarn, "", UnknownValue);
Assert.False(data.Value);
Assert.False(data.IsKnown);
}
[Fact]
public void StringTest()
{
Assert.Throws<InvalidOperationException>(() =>
{
Converter.ConvertValue<bool>("", new Value { StringValue = "" });
});
}
[Fact]
public void NullableTrue()
{
var data = Converter.ConvertValue<bool?>("", new Value { BoolValue = true });
var data = Converter.ConvertValue<bool?>(NoWarn, "", new Value { BoolValue = true });
Assert.True(data.Value);
Assert.True(data.IsKnown);
}
@ -109,7 +104,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void NullableFalse()
{
var data = Converter.ConvertValue<bool?>("", new Value { BoolValue = false });
var data = Converter.ConvertValue<bool?>(NoWarn, "", new Value { BoolValue = false });
Assert.False(data.Value);
Assert.True(data.IsKnown);
@ -118,7 +113,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void NullableNull()
{
var data = Converter.ConvertValue<bool?>("", new Value { NullValue = NullValue.NullValue });
var data = Converter.ConvertValue<bool?>(NoWarn, "", new Value { NullValue = NullValue.NullValue });
Assert.Null(data.Value);
Assert.True(data.IsKnown);

View file

@ -81,7 +81,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task TestComplexType1()
{
var data = Converter.ConvertValue<ComplexType1>("", await SerializeToValueAsync(new Dictionary<string, object>
var data = Converter.ConvertValue<ComplexType1>(NoWarn, "", await SerializeToValueAsync(new Dictionary<string, object>
{
{ "s", "str" },
{ "b", true },
@ -133,10 +133,10 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task TestComplexType2()
{
var data = Converter.ConvertValue<ComplexType2>("", await SerializeToValueAsync(new Dictionary<string, object>
var data = Converter.ConvertValue<ComplexType2>(NoWarn, "", await SerializeToValueAsync(new Dictionary<string, object>
{
{
"c",
"c",
new Dictionary<string, object>
{
{ "s", "str1" },
@ -168,7 +168,7 @@ namespace Pulumi.Tests.Serialization
}
}
},
{
{
"c2Map",
new Dictionary<string, object>
{
@ -231,5 +231,48 @@ namespace Pulumi.Tests.Serialization
}
#endregion
[Fact]
public async Task TestComplexTypeTypeMismatches()
{
var warnings = new List<string>();
var data = Converter.ConvertValue<ComplexType1>(warnings.Add, "", await SerializeToValueAsync(new Dictionary<string, object>
{
{ "s", 24 },
{ "b", "hi" },
{ "i", "string" },
{ "d", true },
{ "array", new List<object> { false, 99, true, "hello" } },
{ "dict", new Dictionary<object, object> { { "k", 10 }, { "v", "hello" } } },
{ "obj", "test" },
{ "size", "bigger" },
{ "color", true },
}));
Assert.Null(data.Value.S);
Assert.False(data.Value.B);
Assert.Equal(0, data.Value.I);
Assert.Equal(0.0, data.Value.D);
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(false).Add(false).Add(true).Add(false), data.Value.Array);
AssertEx.MapEqual(ImmutableDictionary<string, int>.Empty.Add("k", 10).Add("v", 0), data.Value.Dict);
Assert.Equal("test", data.Value.Obj);
Assert.Equal(default(ContainerSize), data.Value.Size);
Assert.Equal(default(ContainerColor), data.Value.Color);
Assert.True(data.IsKnown);
AssertEx.SequenceEqual(new string[] {
"Expected System.String but got System.Double deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(s)",
"Expected System.Boolean but got System.String deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(b)",
"Expected System.Double but got System.String deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(i)",
"Expected System.Double but got System.Boolean deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(d)",
"Expected System.Boolean but got System.Double deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(array)",
"Expected System.Boolean but got System.String deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(array)",
"Expected System.Double but got System.String deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(dict)",
"Expected System.Double but got System.String deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(size)",
"Expected System.String or System.Double but got System.Boolean deserializing Pulumi.Tests.Serialization.ComplexTypeConverterTests+ComplexType1(color)",
}, warnings);
}
}
}

View file

@ -1,5 +1,6 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
@ -35,5 +36,10 @@ namespace Pulumi.Tests.Serialization
var v = Deserializer.Deserialize(value).Value;
return v == null ? default! : (T)v;
}
protected static void NoWarn(string error)
{
throw new Exception("Test did not expect warn to be called");
}
}
}

View file

@ -91,7 +91,7 @@ namespace Pulumi.Tests.Serialization
[MemberData(nameof(StringEnums))]
public async Task StringEnum(ContainerColor input)
{
var data = Converter.ConvertValue<ContainerColor>("", await SerializeToValueAsync(input));
var data = Converter.ConvertValue<ContainerColor>(NoWarn, "", await SerializeToValueAsync(input));
Assert.Equal(input, data.Value);
Assert.True(data.IsKnown);
@ -108,7 +108,7 @@ namespace Pulumi.Tests.Serialization
[MemberData(nameof(DoubleEnums))]
public async Task DoubleEnum(ContainerBrightness input)
{
var data = Converter.ConvertValue<ContainerBrightness>("", await SerializeToValueAsync(input));
var data = Converter.ConvertValue<ContainerBrightness>(NoWarn, "", await SerializeToValueAsync(input));
Assert.Equal(input, data.Value);
Assert.True(data.IsKnown);
@ -123,7 +123,7 @@ namespace Pulumi.Tests.Serialization
[InlineData((ContainerSize)int.MaxValue)]
public async Task Int32Enum(ContainerSize input)
{
var data = Converter.ConvertValue<ContainerSize>("", await SerializeToValueAsync(input));
var data = Converter.ConvertValue<ContainerSize>(NoWarn, "", await SerializeToValueAsync(input));
Assert.Equal(input, data.Value);
Assert.True(data.IsKnown);
@ -200,19 +200,23 @@ namespace Pulumi.Tests.Serialization
public static IEnumerable<object[]> EnumsWithUnconvertibleValues()
=> new[]
{
new object[] { typeof(ContainerColor), new Value { NumberValue = 1.0 } },
new object[] { typeof(ContainerBrightness), new Value { StringValue = "hello" } },
new object[] { typeof(ContainerSize), new Value { StringValue = "hello" } },
new object[] { typeof(ContainerColor), new Value { NumberValue = 1.0 }, "Expected target type Pulumi.Tests.Serialization.EnumConverterTests+ContainerColor to have a constructor with a single System.Double parameter." },
new object[] { typeof(ContainerBrightness), new Value { StringValue = "hello" }, "Expected target type Pulumi.Tests.Serialization.EnumConverterTests+ContainerBrightness to have a constructor with a single System.String parameter." },
new object[] { typeof(ContainerSize), new Value { StringValue = "hello" }, "Expected System.Double but got System.String deserializing " },
};
[Theory]
[MemberData(nameof(EnumsWithUnconvertibleValues))]
public void ConvertingUnconvertibleValuesThrows(Type targetType, Value value)
public void ConvertingUnconvertibleValuesLogs(Type targetType, Value value, string expectedError)
{
Assert.Throws<InvalidOperationException>(() =>
{
Converter.ConvertValue("", value, targetType);
});
string? loggedError = null;
Action<string> warn = error => loggedError = error;
var data = Converter.ConvertValue(warn, "", value, targetType);
Assert.Null(data.Value);
Assert.True(data.IsKnown);
Assert.Equal(expectedError, loggedError);
}
}
}

View file

@ -12,7 +12,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void IgnoreInternalProperty()
{
var data = Converter.ConvertValue<ImmutableDictionary<string, string>>("", new Value
var data = Converter.ConvertValue<ImmutableDictionary<string, string>>(NoWarn, "", new Value
{
StructValue = new Struct
{

View file

@ -13,7 +13,7 @@ namespace Pulumi.Tests.Serialization
{
var element = JsonDocument.Parse(json).RootElement;
var serialized = await SerializeToValueAsync(element);
var converted = Converter.ConvertValue<JsonElement>("", serialized);
var converted = Converter.ConvertValue<JsonElement>(NoWarn, "", serialized);
Assert.Equal(expected, converted.Value.ToString());
}

View file

@ -13,7 +13,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task EmptyList()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<bool>()));
var data = Converter.ConvertValue<ImmutableArray<bool>>(NoWarn, "", await SerializeToValueAsync(new List<bool>()));
Assert.Equal(ImmutableArray<bool>.Empty, data.Value);
Assert.True(data.IsKnown);
@ -22,7 +22,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task ListWithElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<bool> { true }));
var data = Converter.ConvertValue<ImmutableArray<bool>>(NoWarn, "", await SerializeToValueAsync(new List<bool> { true }));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true), data.Value);
Assert.True(data.IsKnown);
@ -31,7 +31,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task SecretListWithElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(Output.CreateSecret(new List<object> { true })));
var data = Converter.ConvertValue<ImmutableArray<bool>>(NoWarn, "", await SerializeToValueAsync(Output.CreateSecret(new List<object> { true })));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true), data.Value);
Assert.True(data.IsKnown);
@ -41,7 +41,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task ListWithSecretElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<object> { Output.CreateSecret(true) }));
var data = Converter.ConvertValue<ImmutableArray<bool>>(NoWarn, "", await SerializeToValueAsync(new List<object> { Output.CreateSecret(true) }));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true), data.Value);
Assert.True(data.IsKnown);
@ -51,7 +51,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task ListWithUnknownElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<object> { Output<bool>.CreateUnknown(true) }));
var data = Converter.ConvertValue<ImmutableArray<bool>>(NoWarn, "", await SerializeToValueAsync(new List<object> { Output<bool>.CreateUnknown(true) }));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(false), data.Value);
Assert.False(data.IsKnown);

View file

@ -26,7 +26,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void SimpleCase()
{
var data = Converter.ConvertValue<RecursiveType>("", new Value
var data = Converter.ConvertValue<RecursiveType>(NoWarn, "", new Value
{
StructValue = new Struct
{

View file

@ -15,7 +15,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void T0()
{
var data = Converter.ConvertValue<Union<int, string>>("", new Value { NumberValue = 1 });
var data = Converter.ConvertValue<Union<int, string>>(NoWarn, "", new Value { NumberValue = 1 });
Assert.True(data.Value.IsT0);
Assert.True(data.IsKnown);
Assert.Equal(1, data.Value.AsT0);
@ -24,7 +24,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public void T1()
{
var data = Converter.ConvertValue<Union<int, string>>("", new Value { StringValue = "foo" });
var data = Converter.ConvertValue<Union<int, string>>(NoWarn, "", new Value { StringValue = "foo" });
Assert.True(data.Value.IsT1);
Assert.True(data.IsKnown);
Assert.Equal("foo", data.Value.AsT1);
@ -33,7 +33,7 @@ namespace Pulumi.Tests.Serialization
[Fact]
public async Task MixedList()
{
var data = Converter.ConvertValue<ImmutableArray<Union<int, string>>>("",
var data = Converter.ConvertValue<ImmutableArray<Union<int, string>>>(NoWarn, "",
await SerializeToValueAsync(new List<object> { 1, "foo" }));
Assert.True(data.IsKnown);
Assert.Equal(2, data.Value.Length);
@ -46,12 +46,16 @@ namespace Pulumi.Tests.Serialization
}
[Fact]
public void WrongTypeThrows()
public void WrongTypeLogs()
{
Assert.Throws<InvalidOperationException>(() =>
{
Converter.ConvertValue<Union<int, string>>("", new Value { BoolValue = true });
});
string? loggedError = null;
Action<string> warn = error => loggedError = error;
var data = Converter.ConvertValue<Union<int, string>>(warn, "", new Value { BoolValue = true });
Assert.Equal(default(Union<int, string>), data.Value);
Assert.True(data.IsKnown);
Assert.Equal("Expected System.Int32 or System.String but got System.Boolean deserializing ", loggedError);
}
}
}

View file

@ -26,7 +26,7 @@ namespace Pulumi
var (result, deps) = await CallRawAsync(token, args, self, options).ConfigureAwait(false);
if (convertResult)
{
var converted = Converter.ConvertValue<T>($"{token} result", new Value { StructValue = result });
var converted = Converter.ConvertValue<T>(err => Log.Warn(err, self), $"{token} result", new Value { StructValue = result });
return new OutputData<T>(deps, converted.Value, converted.IsKnown, converted.IsSecret);
}

View file

@ -56,7 +56,7 @@ namespace Pulumi
// tracking, which is a good future direction also for
// `Invoke`.
var result = await InvokeRawAsync(token, args, options).ConfigureAwait(false);
var data = Converter.ConvertValue<T>($"{token} result",
var data = Converter.ConvertValue<T>(err => Log.Warn(err), $"{token} result",
new Value { StructValue = result.Serialized });
var resources = ImmutableHashSet.CreateRange(
result.PropertyToDependentResources.Values.SelectMany(r => r)
@ -77,7 +77,7 @@ namespace Pulumi
return default!;
}
var data = Converter.ConvertValue<T>($"{token} result", new Value { StructValue = result.Serialized });
var data = Converter.ConvertValue<T>(err => Log.Warn(err), $"{token} result", new Value { StructValue = result.Serialized });
return data.Value;
}

View file

@ -119,7 +119,7 @@ namespace Pulumi
dependencies = ImmutableHashSet<Resource>.Empty;
}
var converted = Converter.ConvertValue($"{resource.GetType().FullName}.{fieldName}", value,
var converted = Converter.ConvertValue(err => Log.Warn(err, resource), $"{resource.GetType().FullName}.{fieldName}", value,
completionSource.TargetType, dependencies);
completionSource.SetValue(converted);
}

View file

@ -17,38 +17,41 @@ namespace Pulumi.Serialization
{
internal static class Converter
{
public static OutputData<T> ConvertValue<T>(string context, Value value)
public static OutputData<T> ConvertValue<T>(Action<string> warn, string context, Value value)
{
var (data, isKnown, isSecret) = ConvertValue(context, value, typeof(T));
return new OutputData<T>(ImmutableHashSet<Resource>.Empty, (T)data!, isKnown, isSecret);
var (data, isKnown, isSecret) = ConvertValue(warn, context, value, typeof(T));
var result = data == null ? default(T)! : (T)data;
return new OutputData<T>(ImmutableHashSet<Resource>.Empty, result!, isKnown, isSecret);
}
public static OutputData<object?> ConvertValue(string context, Value value, Type targetType)
public static OutputData<object?> ConvertValue(Action<string> warn, string context, Value value, Type targetType)
{
return ConvertValue(context, value, targetType, ImmutableHashSet<Resource>.Empty);
return ConvertValue(warn, context, value, targetType, ImmutableHashSet<Resource>.Empty);
}
public static OutputData<object?> ConvertValue(
string context, Value value, Type targetType, ImmutableHashSet<Resource> resources)
Action<string> warn, string context, Value value, Type targetType, ImmutableHashSet<Resource> resources)
{
CheckTargetType(context, targetType, new HashSet<Type>());
var (deserialized, isKnown, isSecret) = Deserializer.Deserialize(value);
var converted = ConvertObject(context, deserialized, targetType);
var converted = ConvertObject(warn, context, deserialized, targetType);
return new OutputData<object?>(resources, converted, isKnown, isSecret);
}
private static object? ConvertObject(string context, object? val, Type targetType)
private static object? ConvertObject(Action<string> warn, string context, object? val, Type targetType)
{
var (result, exception) = TryConvertObject(context, val, targetType);
if (exception != null)
throw exception;
var (result, error) = TryConvertObject(warn, context, val, targetType);
if (error != null)
{
warn(error);
}
return result;
}
private static (object?, InvalidOperationException?) TryConvertObject(string context, object? val, Type targetType)
private static (object?, string?) TryConvertObject(Action<string> warn, string context, object? val, Type targetType)
{
var targetIsNullable = targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>);
@ -75,7 +78,7 @@ namespace Pulumi.Serialization
// We're not null and we're converting to Nullable<T>, just convert our value to be a T.
if (targetIsNullable)
return TryConvertObject(context, val, targetType.GenericTypeArguments.Single());
return TryConvertObject(warn, context, val, targetType.GenericTypeArguments.Single());
if (targetType == typeof(string))
return TryEnsureType<string>(context, val);
@ -131,7 +134,7 @@ namespace Pulumi.Serialization
if (targetType.IsEnum)
{
var underlyingType = targetType.GetEnumUnderlyingType();
var (value, exception) = TryConvertObject(context, val, underlyingType);
var (value, exception) = TryConvertObject(warn, context, val, underlyingType);
if (exception != null || value is null)
return (null, exception);
@ -144,16 +147,16 @@ namespace Pulumi.Serialization
if (valType != typeof(string) &&
valType != typeof(double))
{
return (null, new InvalidOperationException(
$"Expected {typeof(string).FullName} or {typeof(double).FullName} but got {valType.FullName} deserializing {context}"));
return (null,
$"Expected {typeof(string).FullName} or {typeof(double).FullName} but got {valType.FullName} deserializing {context}");
}
var enumTypeConstructor = targetType.GetConstructor(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { valType }, null);
if (enumTypeConstructor == null)
{
return (null, new InvalidOperationException(
$"Expected target type {targetType.FullName} to have a constructor with a single {valType.FullName} parameter."));
return (null,
$"Expected target type {targetType.FullName} to have a constructor with a single {valType.FullName} parameter.");
}
return (enumTypeConstructor.Invoke(new[] { val }), null);
}
@ -161,26 +164,25 @@ namespace Pulumi.Serialization
if (targetType.IsConstructedGenericType)
{
if (targetType.GetGenericTypeDefinition() == typeof(Union<,>))
return TryConvertOneOf(context, val, targetType);
return TryConvertOneOf(warn, context, val, targetType);
if (targetType.GetGenericTypeDefinition() == typeof(ImmutableArray<>))
return TryConvertArray(context, val, targetType);
return TryConvertArray(warn, context, val, targetType);
if (targetType.GetGenericTypeDefinition() == typeof(ImmutableDictionary<,>))
return TryConvertDictionary(context, val, targetType);
return TryConvertDictionary(warn, context, val, targetType);
throw new InvalidOperationException(
$"Unexpected generic target type {targetType.FullName} when deserializing {context}");
}
if (targetType.GetCustomAttribute<OutputTypeAttribute>() == null)
return (null, new InvalidOperationException(
$"Unexpected target type {targetType.FullName} when deserializing {context}"));
return (null, $"Unexpected target type {targetType.FullName} when deserializing {context}");
var constructor = GetPropertyConstructor(targetType);
if (constructor == null)
return (null, new InvalidOperationException(
$"Expected target type {targetType.FullName} to have [{nameof(OutputConstructorAttribute)}] constructor when deserializing {context}"));
return (null,
$"Expected target type {targetType.FullName} to have [{nameof(OutputConstructorAttribute)}] constructor when deserializing {context}");
var (dictionary, tempException) = TryEnsureType<ImmutableDictionary<string, object>>(context, val);
if (tempException != null)
@ -197,25 +199,22 @@ namespace Pulumi.Serialization
// unknown vals. That's ok. We'll pass that through to 'Convert' and will get the
// default value needed for the parameter type.
dictionary!.TryGetValue(parameter.Name!, out var argValue);
var (temp, tempException1) = TryConvertObject($"{targetType.FullName}({parameter.Name})", argValue, parameter.ParameterType);
if (tempException1 != null)
return (null, tempException1);
arguments[i] = temp;
arguments[i] = ConvertObject(warn, $"{targetType.FullName}({parameter.Name})", argValue, parameter.ParameterType);
}
return (constructor.Invoke(arguments), null);
}
private static (object?, InvalidOperationException?) TryConvertJsonElement(
private static (object?, string?) TryConvertJsonElement(
string context, object val)
{
using var stream = new MemoryStream();
using (var writer = new Utf8JsonWriter(stream))
{
var exception = TryWriteJson(context, writer, val);
if (exception != null)
return (null, exception);
var error = TryWriteJson(context, writer, val);
if (error != null)
return (null, error);
}
stream.Position = 0;
@ -224,7 +223,7 @@ namespace Pulumi.Serialization
return (element, null);
}
private static InvalidOperationException? TryWriteJson(string context, Utf8JsonWriter writer, object? val)
private static string? TryWriteJson(string context, Utf8JsonWriter writer, object? val)
{
switch (val)
{
@ -262,41 +261,42 @@ namespace Pulumi.Serialization
writer.WriteEndObject();
return null;
default:
return new InvalidOperationException($"Unexpected type {val.GetType().FullName} when converting {context} to {nameof(JsonElement)}");
return $"Unexpected type {val.GetType().FullName} when converting {context} to {nameof(JsonElement)}";
}
}
private static (T, InvalidOperationException?) TryEnsureType<T>(string context, object val)
=> val is T t ? (t, null) : (default(T)!, new InvalidOperationException($"Expected {typeof(T).FullName} but got {val.GetType().FullName} deserializing {context}"));
private static (T, string?) TryEnsureType<T>(string context, object val)
=> val is T t ? (t, null) : (default(T)!, $"Expected {typeof(T).FullName} but got {val.GetType().FullName} deserializing {context}");
private static (object?, InvalidOperationException?) TryConvertOneOf(string context, object val, Type oneOfType)
private static (object?, string?) TryConvertOneOf(Action<string> warn, string context, object val, Type oneOfType)
{
var firstType = oneOfType.GenericTypeArguments[0];
var secondType = oneOfType.GenericTypeArguments[1];
var (val1, exception1) = TryConvertObject($"{context}.AsT0", val, firstType);
var (val1, exception1) = TryConvertObject(warn, $"{context}.AsT0", val, firstType);
if (exception1 == null)
{
var fromT0Method = oneOfType.GetMethod(nameof(Union<int, int>.FromT0), BindingFlags.Public | BindingFlags.Static);
return (fromT0Method?.Invoke(null, new[] { val1 }), null);
}
var (val2, exception2) = TryConvertObject($"{context}.AsT1", val, secondType);
var (val2, exception2) = TryConvertObject(warn, $"{context}.AsT1", val, secondType);
if (exception2 == null)
{
var fromT1Method = oneOfType.GetMethod(nameof(Union<int, int>.FromT1), BindingFlags.Public | BindingFlags.Static);
return (fromT1Method?.Invoke(null, new[] { val2 }), null);
}
return (null, new InvalidOperationException($"Expected {firstType.FullName} or {secondType.FullName} but got {val.GetType().FullName} deserializing {context}"));
return (null, $"Expected {firstType.FullName} or {secondType.FullName} but got {val.GetType().FullName} deserializing {context}");
}
private static (object?, InvalidOperationException?) TryConvertArray(
private static (object?, string?) TryConvertArray(
Action<string> warn,
string fieldName, object val, Type targetType)
{
if (!(val is ImmutableArray<object> array))
return (null, new InvalidOperationException(
$"Expected {typeof(ImmutableArray<object>).FullName} but got {val.GetType().FullName} deserializing {fieldName}"));
return (null,
$"Expected {typeof(ImmutableArray<object>).FullName} but got {val.GetType().FullName} deserializing {fieldName}");
var builder =
typeof(ImmutableArray).GetMethod(nameof(ImmutableArray.CreateBuilder), Array.Empty<Type>())!
@ -309,9 +309,7 @@ namespace Pulumi.Serialization
var elementType = targetType.GenericTypeArguments.Single();
foreach (var element in array)
{
var (e, exception) = TryConvertObject(fieldName, element, elementType);
if (exception != null)
return (null, exception);
var e = ConvertObject(warn, fieldName, element, elementType);
builderAdd.Invoke(builder, new[] { e });
}
@ -319,12 +317,13 @@ namespace Pulumi.Serialization
return (builderToImmutable.Invoke(builder, null), null);
}
private static (object?, InvalidOperationException?) TryConvertDictionary(
private static (object?, string?) TryConvertDictionary(
Action<string> warn,
string fieldName, object val, Type targetType)
{
if (!(val is ImmutableDictionary<string, object> dictionary))
return (null, new InvalidOperationException(
$"Expected {typeof(ImmutableDictionary<string, object>).FullName} but got {val.GetType().FullName} deserializing {fieldName}"));
return (null,
$"Expected {typeof(ImmutableDictionary<string, object>).FullName} but got {val.GetType().FullName} deserializing {fieldName}");
// check if already in the form we need. no need to convert anything.
if (targetType == typeof(ImmutableDictionary<string, object>))
@ -332,25 +331,21 @@ namespace Pulumi.Serialization
var keyType = targetType.GenericTypeArguments[0];
if (keyType != typeof(string))
return (null, new InvalidOperationException(
$"Unexpected type {targetType.FullName} when deserializing {fieldName}. ImmutableDictionary's TKey type was not {typeof(string).FullName}"));
return (null,
$"Unexpected type {targetType.FullName} when deserializing {fieldName}. ImmutableDictionary's TKey type was not {typeof(string).FullName}");
var builder =
typeof(ImmutableDictionary).GetMethod(nameof(ImmutableDictionary.CreateBuilder), Array.Empty<Type>())!
.MakeGenericMethod(targetType.GenericTypeArguments)
.Invoke(obj: null, parameters: null)!;
// var b = ImmutableDictionary.CreateBuilder<string, object>().Add()
var builderAdd = builder.GetType().GetMethod(nameof(ImmutableDictionary<string, object>.Builder.Add), targetType.GenericTypeArguments)!;
var builderToImmutable = builder.GetType().GetMethod(nameof(ImmutableDictionary<string, object>.Builder.ToImmutable))!;
var elementType = targetType.GenericTypeArguments[1];
foreach (var (key, element) in dictionary)
{
var (e, exception) = TryConvertObject(fieldName, element, elementType);
if (exception != null)
return (null, exception);
var e = ConvertObject(warn, fieldName, element, elementType);
builderAdd.Invoke(builder, new[] { key, e });
}

View file

@ -624,7 +624,7 @@ export async function getAllTransitivelyReferencedResourceURNs(resources: Set<Re
// Then the transitively reachable resources of Comp1 will be [Cust1, Cust2, Cust3, Remote1].
// It will *not* include:
// * Cust4 because it is a child of a custom resource
// * Comp2 because it is a non-remote component resoruce
// * Comp2 because it is a non-remote component resource
// * Comp3 and Cust5 because Comp3 is a child of a remote component resource
// To do this, first we just get the transitively reachable set of resources (not diving

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# 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.
@ -69,7 +69,7 @@ def is_custom_timeouts(obj: Any) -> bool:
def is_stack(obj: Any) -> bool:
"""
Returns true if the given type is an Output, false otherwise.
Returns true if the given type is a Stack, false otherwise.
"""
from .stack import Stack # pylint: disable=import-outside-toplevel
return isinstance(obj, Stack)

View file

@ -640,11 +640,13 @@ class RegisterResponse:
self.propertyDependencies = propertyDependencies
# Merge all providers opts (opts.provider and both list and dict forms of opts.providers) into a single dict.
def convert_providers(
provider: Optional['ProviderResource'],
providers: Optional[Union[Mapping[str, 'ProviderResource'],
Sequence['ProviderResource']]]) -> Mapping[str, 'ProviderResource']:
"""
Merge all providers opts (opts.provider and both list and dict forms of opts.providers) into a single dict.
"""
if provider is not None:
return convert_providers(None, [provider])
@ -661,7 +663,7 @@ def convert_providers(
return result
async def _add_dependency(deps: Set[str], res: 'Resource', from_resource: 'Resource'):
async def _add_dependency(deps: Set[str], res: 'Resource', from_resource: Optional['Resource']):
"""
_add_dependency adds a dependency on the given resource to the set of deps.
@ -700,14 +702,14 @@ async def _add_dependency(deps: Set[str], res: 'Resource', from_resource: 'Resou
if not res._remote:
return
no_cycles = declare_dependency(from_resource, res)
no_cycles = declare_dependency(from_resource, res) if from_resource else True
if no_cycles:
urn = await res.urn.future()
if urn:
deps.add(urn)
async def _expand_dependencies(deps: Iterable['Resource'], from_resource: 'Resource') -> Set[str]:
async def _expand_dependencies(deps: Iterable['Resource'], from_resource: Optional['Resource']) -> Set[str]:
"""
_expand_dependencies expands the given iterable of Resources into a set of URNs.
"""

View file

@ -1,4 +1,4 @@
# Copyright 2016-2018, Pulumi Corporation.
# 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.
@ -27,6 +27,7 @@ from google.protobuf import struct_pb2
from semver import VersionInfo as Version
import six
from . import known_types, settings
from .resource import _expand_dependencies
from .. import log
from .. import _types
from .. import urn as urn_util
@ -278,7 +279,7 @@ async def serialize_property(value: 'Input[Any]',
if known_types.is_output(value):
output = cast('Output', value)
value_resources = await output.resources()
value_resources: Set['Resource'] = await output.resources()
deps.extend(value_resources)
# When serializing an Output, we will either serialize it as its resolved value or the
@ -287,14 +288,19 @@ async def serialize_property(value: 'Input[Any]',
# resolved with known values.
is_known = await output._is_known
is_secret = await output._is_secret
value = await serialize_property(output.future(), deps, input_transformer, typ, keep_output_values=False)
promise_deps: List['Resource'] = []
value = await serialize_property(output.future(), promise_deps, input_transformer, typ, keep_output_values=False)
deps.extend(promise_deps)
value_resources.update(promise_deps)
if keep_output_values and await settings.monitor_supports_output_values():
# TODO[pulumi/pulumi#7977]: Expand dependencies
dependencies: Set[str] = set()
urn_deps: List['Resource'] = []
for resource in value_resources:
urn = await serialize_property(resource.urn, deps, input_transformer, keep_output_values=False)
dependencies.add(cast(str, urn))
await serialize_property(resource.urn, urn_deps, input_transformer, keep_output_values=False)
promise_deps.extend(set(urn_deps))
value_resources.update(urn_deps)
dependencies = await _expand_dependencies(value_resources, None)
output_value: Dict[str, Any] = {
_special_sig_key: _special_output_value_sig