[sdk/dotnet] Fix deserializing resources (#5921)

This change implements getResource in the mock monitor. This uncovered some issues deserializing resources, which this change also addresses.
This commit is contained in:
Justin Van Patten 2020-12-10 22:58:08 -08:00 committed by GitHub
parent afd5ad6a97
commit 160220bc4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 126 additions and 20 deletions

View file

@ -19,6 +19,10 @@ CHANGELOG
- [sdk/python] Implement getResource in the mock monitor.
[#5919](https://github.com/pulumi/pulumi/pull/5919)
- [sdk/dotnet] Implement getResource in the mock monitor and fix some issues around
deserializing resources.
[#5921](https://github.com/pulumi/pulumi/pull/5921)
## 2.15.4 (2020-12-08)
- Fix a problem where `pulumi import` could panic on an import error due to missing error message.

View file

@ -1,5 +1,6 @@
// Copyright 2016-2020, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
@ -18,10 +19,17 @@ namespace Pulumi.Tests.Mocks
public Task<(string id, object state)> NewResourceAsync(string type, string name, ImmutableDictionary<string, object> inputs, string? provider, string? id)
{
Assert.Equal("aws:ec2/instance:Instance", type);
return Task.FromResult<(string, object)>(("i-1234567890abcdef0", new Dictionary<string, object> {
{ "publicIp", "203.0.113.12" },
}));
switch (type)
{
case "aws:ec2/instance:Instance":
return Task.FromResult<(string, object)>(("i-1234567890abcdef0", new Dictionary<string, object> {
{ "publicIp", "203.0.113.12" },
}));
case "pkg:index:MyCustom":
return Task.FromResult<(string, object)>((name + "_id", inputs));
default:
throw new Exception($"Unknown resource {type}");
}
}
}
@ -38,7 +46,22 @@ namespace Pulumi.Tests.Mocks
var ip = await instance.PublicIp.GetValueAsync();
Assert.Equal("203.0.113.12", ip);
}
[Fact]
public async Task TestCustomWithResourceReference()
{
var resources = await Testing.RunAsync<MyStack>();
var mycustom = resources.OfType<MyCustom>().FirstOrDefault();
Assert.NotNull(mycustom);
var instance = await mycustom.Instance.GetValueAsync();
Assert.IsType<Instance>(instance);
var ip = await instance.PublicIp.GetValueAsync();
Assert.Equal("203.0.113.12", ip);
}
[Fact]
public async Task TestStack()
{

View file

@ -2,6 +2,7 @@
namespace Pulumi.Tests.Mocks
{
[ResourceType("aws:ec2/instance:Instance", null)]
public partial class Instance : Pulumi.CustomResource
{
[Output("publicIp")]
@ -17,6 +18,23 @@ namespace Pulumi.Tests.Mocks
{
}
public partial class MyCustom : Pulumi.CustomResource
{
[Output("instance")]
public Output<Instance> Instance { get; private set; } = null!;
public MyCustom(string name, MyCustomArgs args, CustomResourceOptions? options = null)
: base("pkg:index:MyCustom", name, args ?? new MyCustomArgs(), options)
{
}
}
public sealed class MyCustomArgs : Pulumi.ResourceArgs
{
[Input("instance")]
public Input<Instance>? Instance { get; set; }
}
public class MyStack : Stack
{
[Output("publicIp")]
@ -25,6 +43,10 @@ namespace Pulumi.Tests.Mocks
public MyStack()
{
var myInstance = new Instance("instance", new InstanceArgs());
var myCustom = new MyCustom("mycustom", new MyCustomArgs
{
Instance = myInstance,
});
this.PublicIp = myInstance.PublicIp;
}
}

View file

@ -106,6 +106,9 @@ namespace Pulumi.Serialization
if (targetType == typeof(JsonElement))
return TryConvertJsonElement(context, val);
if (targetType.IsSubclassOf(typeof(Resource)) || targetType == typeof(Resource))
return TryEnsureType<Resource>(context, val);
if (targetType.IsEnum)
{
var underlyingType = targetType.GetEnumUnderlyingType();
@ -357,6 +360,11 @@ namespace Pulumi.Serialization
return;
}
if (targetType.IsSubclassOf(typeof(Resource)) || targetType == typeof(Resource))
{
return;
}
if (targetType == typeof(ImmutableDictionary<string, object>))
{
// This type is what is generated for things like azure/aws tags. It's an untyped

View file

@ -35,7 +35,7 @@ namespace Pulumi
_resourceTypes ??= DiscoverResourceTypes();
}
var minimalVersion = version != null ? SemVersion.Parse(version) : new SemVersion(0);
var minimalVersion = !string.IsNullOrEmpty(version) ? SemVersion.Parse(version) : new SemVersion(0);
var yes = _resourceTypes.TryGetValue(name, out var types);
if (!yes)
{
@ -45,7 +45,7 @@ namespace Pulumi
var matches =
from vt in types
let resourceVersion = vt.Item1 != null ? SemVersion.Parse(vt.Item1) : minimalVersion
let resourceVersion = !string.IsNullOrEmpty(vt.Item1) ? SemVersion.Parse(vt.Item1) : minimalVersion
where resourceVersion >= minimalVersion
where (version == null || vt.Item1 == null || minimalVersion.Major == resourceVersion.Major)
orderby resourceVersion descending

View file

@ -14,8 +14,9 @@ namespace Pulumi.Testing
internal class MockMonitor : IMonitor
{
private readonly IMocks _mocks;
private readonly Serializer _serializer = new Serializer();
private readonly Serializer _serializer = new Serializer(excessiveDebugOutput: false);
private readonly Dictionary<string, object> _registeredResources = new Dictionary<string, object>();
public readonly List<Resource> Resources = new List<Resource>();
public MockMonitor(IMocks mocks)
@ -31,9 +32,25 @@ namespace Pulumi.Testing
public async Task<InvokeResponse> InvokeAsync(InvokeRequest request)
{
var result = await _mocks.CallAsync(request.Tok, ToDictionary(request.Args), request.Provider)
var args = ToDictionary(request.Args);
var urn = (string)args["urn"];
if (request.Tok == "pulumi:pulumi:getResource")
{
object? registeredResource;
lock (_registeredResources)
{
if (!_registeredResources.TryGetValue(urn, out registeredResource))
{
throw new InvalidOperationException($"Unknown resource {urn}");
}
}
return new InvokeResponse { Return = await SerializeAsync(registeredResource).ConfigureAwait(false) };
}
var result = await _mocks.CallAsync(request.Tok, args, request.Provider)
.ConfigureAwait(false);
return new InvokeResponse {Return = await SerializeAsync(result).ConfigureAwait(false)};
return new InvokeResponse { Return = await SerializeAsync(result).ConfigureAwait(false) };
}
public async Task<ReadResourceResponse> ReadResourceAsync(Resource resource, ReadResourceRequest request)
@ -41,6 +58,18 @@ namespace Pulumi.Testing
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name,
ToDictionary(request.Properties), request.Provider, request.Id).ConfigureAwait(false);
var urn = NewUrn(request.Parent, request.Type, request.Name);
var serializedState = await SerializeToDictionary(state).ConfigureAwait(false);
lock (_registeredResources)
{
var builder = ImmutableDictionary.CreateBuilder<string, object>();
builder.Add("urn", urn);
builder.Add("id", id);
builder.Add("state", serializedState);
_registeredResources[urn] = builder.ToImmutable();
}
lock (this.Resources)
{
this.Resources.Add(resource);
@ -48,8 +77,8 @@ namespace Pulumi.Testing
return new ReadResourceResponse
{
Urn = NewUrn(request.Parent, request.Type, request.Name),
Properties = await SerializeAsync(state).ConfigureAwait(false)
Urn = urn,
Properties = Serializer.CreateStruct(serializedState),
};
}
@ -71,12 +100,24 @@ namespace Pulumi.Testing
var (id, state) = await _mocks.NewResourceAsync(request.Type, request.Name, ToDictionary(request.Object),
request.Provider, request.ImportId).ConfigureAwait(false);
var urn = NewUrn(request.Parent, request.Type, request.Name);
var serializedState = await SerializeToDictionary(state).ConfigureAwait(false);
lock (_registeredResources)
{
var builder = ImmutableDictionary.CreateBuilder<string, object>();
builder.Add("urn", urn);
builder.Add("id", id ?? request.ImportId);
builder.Add("state", serializedState);
_registeredResources[urn] = builder.ToImmutable();
}
return new RegisterResourceResponse
{
Id = id ?? request.ImportId,
Urn = NewUrn(request.Parent, request.Type, request.Name),
Object = await SerializeAsync(state).ConfigureAwait(false)
Urn = urn,
Object = Serializer.CreateStruct(serializedState),
};
}
@ -84,7 +125,7 @@ namespace Pulumi.Testing
private static string NewUrn(string parent, string type, string name)
{
if (!string.IsNullOrEmpty(parent))
if (!string.IsNullOrEmpty(parent))
{
var qualifiedType = parent.Split("::")[2];
var parentType = qualifiedType.Split("$").First();
@ -107,11 +148,19 @@ namespace Pulumi.Testing
return builder.ToImmutable();
}
private async Task<ImmutableDictionary<string, object>> SerializeToDictionary(object o)
{
if (o is IDictionary<string, object> d)
{
o = d.ToImmutableDictionary();
}
return await _serializer.SerializeAsync("", o, true).ConfigureAwait(false) as ImmutableDictionary<string, object>
?? throw new InvalidOperationException($"{o.GetType().FullName} is not a supported argument type");
}
private async Task<Struct> SerializeAsync(object o)
{
var dict = (o as IDictionary<string, object>)?.ToImmutableDictionary()
?? await _serializer.SerializeAsync("", o, true).ConfigureAwait(false) as ImmutableDictionary<string, object>
?? throw new InvalidOperationException($"{o.GetType().FullName} is not a supported argument type");
var dict = await SerializeToDictionary(o).ConfigureAwait(false);
return Serializer.CreateStruct(dict);
}
}