Enable output marshaling in .NET (#8316)
This commit is contained in:
parent
5dd7851293
commit
164a2ec818
|
@ -7,6 +7,9 @@
|
|||
- [schema] Add IsOverlay option to disable codegen for particular types
|
||||
[#8338](https://github.com/pulumi/pulumi/pull/8338)
|
||||
|
||||
- [sdk/dotnet] - Marshal output values.
|
||||
[#8316](https://github.com/pulumi/pulumi/pull/8316)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [engine] - Compute dependents correctly during targeted deletes.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Pulumi.Tests
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Pulumi.Tests.Serialization
|
|||
Fields =
|
||||
{
|
||||
{ Constants.SpecialSigKey, new Value { StringValue = Constants.SpecialSecretSig } },
|
||||
{ Constants.SecretValueName, value },
|
||||
{ Constants.ValueName, value },
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
141
sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs
Normal file
141
sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Pulumi.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Pulumi.Tests.Serialization
|
||||
{
|
||||
public class MarshalOutputTests : PulumiTest
|
||||
{
|
||||
public static IEnumerable<object[]> BasicSerializeData =>
|
||||
from value in new object?[]
|
||||
{
|
||||
null,
|
||||
0.0,
|
||||
1.0,
|
||||
"",
|
||||
"hi",
|
||||
ImmutableDictionary<string, object?>.Empty,
|
||||
ImmutableArray<object?>.Empty,
|
||||
}
|
||||
from deps in new[] { Array.Empty<string>(), new[] { "fakeURN1", "fakeURN2" } }
|
||||
from isKnown in new[] { true, false }
|
||||
from isSecret in new[] { true, false }
|
||||
select new object[] { value, deps, isKnown, isSecret };
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(BasicSerializeData))]
|
||||
public static Task TestBasicSerialize(object? value, string[] deps, bool isKnown, bool isSecret) => RunInNormal(async () =>
|
||||
{
|
||||
var resources = ImmutableHashSet.CreateRange<Resource>(deps.Select(d => new DependencyResource(d)));
|
||||
var data = OutputData.Create(resources, value, isKnown, isSecret);
|
||||
var input = new Output<object?>(Task.FromResult(data));
|
||||
|
||||
var expected = isKnown && !isSecret && deps.Length == 0
|
||||
? value
|
||||
: CreateOutputValue(value, isKnown, isSecret, deps);
|
||||
|
||||
var s = new Serializer(excessiveDebugOutput: false);
|
||||
var actual = await s.SerializeAsync("", input, keepResources: true, keepOutputValues: true);
|
||||
Assert.Equal(expected, actual);
|
||||
});
|
||||
|
||||
public sealed class FooArgs : ResourceArgs
|
||||
{
|
||||
[Input("foo")]
|
||||
public Input<string>? Foo { get; set; }
|
||||
}
|
||||
|
||||
public sealed class BarArgs : ResourceArgs
|
||||
{
|
||||
[Input("foo")]
|
||||
public Input<FooArgs>? Foo { get; set; }
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> SerializeData() => new object[][]
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
new FooArgs { Foo = "hello" },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", "hello")
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new FooArgs { Foo = Output.Create("hello") },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", "hello")
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new FooArgs { Foo = Output.CreateSecret("hello") },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", CreateOutputValue("hello", isSecret: true))
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new List<Input<string>> { "hello" },
|
||||
ImmutableArray<object>.Empty.Add("hello")
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new List<Input<string>> { Output.Create("hello") },
|
||||
ImmutableArray<object>.Empty.Add("hello")
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new List<Input<string>> { Output.CreateSecret("hello") },
|
||||
ImmutableArray<object>.Empty.Add(CreateOutputValue("hello", isSecret: true))
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new Dictionary<string, Input<string>> { { "foo", "hello" } },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", "hello")
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new Dictionary<string, Input<string>> { { "foo", Output.Create("hello") } },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", "hello")
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new Dictionary<string, Input<string>> { { "foo", Output.CreateSecret("hello") } },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", CreateOutputValue("hello", isSecret: true))
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new BarArgs { Foo = new FooArgs { Foo = "hello" } },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo",
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", "hello"))
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
new BarArgs { Foo = new FooArgs { Foo = Output.Create("hello") } },
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo",
|
||||
ImmutableDictionary<string, object>.Empty.Add("foo", "hello"))
|
||||
},
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SerializeData))]
|
||||
public static Task TestSerialize(object input, object expected) => RunInNormal(async () =>
|
||||
{
|
||||
var s = new Serializer(excessiveDebugOutput: false);
|
||||
var actual = await s.SerializeAsync("", input, keepResources: true, keepOutputValues: true);
|
||||
Assert.Equal(expected, actual);
|
||||
});
|
||||
|
||||
private static ImmutableDictionary<string, object?> CreateOutputValue(
|
||||
object? value, bool isKnown = true, bool isSecret = false, params string[] deps)
|
||||
{
|
||||
var b = ImmutableDictionary.CreateBuilder<string, object?>();
|
||||
b.Add(Constants.SpecialSigKey, Constants.SpecialOutputValueSig);
|
||||
if (isKnown) b.Add(Constants.ValueName, value);
|
||||
if (isSecret) b.Add(Constants.SecretName, isSecret);
|
||||
if (deps.Length > 0) b.Add(Constants.DependenciesName, deps.ToImmutableArray());
|
||||
return b.ToImmutableDictionary();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -192,7 +192,7 @@ namespace Pulumi
|
|||
{
|
||||
if (!this._featureSupport.ContainsKey(feature))
|
||||
{
|
||||
var request = new SupportsFeatureRequest {Id = feature };
|
||||
var request = new SupportsFeatureRequest { Id = feature };
|
||||
var response = await this.Monitor.SupportsFeatureAsync(request).ConfigureAwait(false);
|
||||
this._featureSupport[feature] = response.HasSupport;
|
||||
}
|
||||
|
@ -203,5 +203,16 @@ namespace Pulumi
|
|||
{
|
||||
return MonitorSupportsFeature("resourceReferences");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the monitor supports the "outputValues" feature.
|
||||
/// </summary>
|
||||
internal Task<bool> MonitorSupportsOutputValues()
|
||||
{
|
||||
return MonitorSupportsFeature("outputValues");
|
||||
}
|
||||
|
||||
// Because the secrets feature predates the Pulumi .NET SDK, we assume
|
||||
// that the monitor supports secrets.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,8 +53,10 @@ namespace Pulumi
|
|||
}
|
||||
|
||||
var (serialized, argDependencies) = await SerializeFilteredPropertiesAsync(
|
||||
$"call:{token}",
|
||||
argsDict, _ => true, keepResources: true).ConfigureAwait(false);
|
||||
$"call:{token}",
|
||||
argsDict, _ => true,
|
||||
keepResources: true,
|
||||
keepOutputValues: await MonitorSupportsOutputValues().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
Log.Debug($"Call RPC prepared: token={token}" +
|
||||
(_excessiveDebugOutput ? $", obj={serialized}" : ""));
|
||||
|
||||
|
|
|
@ -148,7 +148,8 @@ namespace Pulumi
|
|||
label: $"invoke:{token}",
|
||||
args: argsDict,
|
||||
acceptKey: key => true,
|
||||
keepResources: keepResources
|
||||
keepResources: keepResources,
|
||||
keepOutputValues: false
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ namespace Pulumi
|
|||
await SerializeResourcePropertiesAsync(
|
||||
label,
|
||||
dictionary,
|
||||
await this.MonitorSupportsResourceReferences().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
await this.MonitorSupportsResourceReferences().ConfigureAwait(false),
|
||||
keepOutputValues: remote && await MonitorSupportsOutputValues().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
LogExcessive($"Serialized properties: t={type}, name={name}, custom={custom}, remote={remote}");
|
||||
|
||||
// Wait for the parent to complete.
|
||||
|
@ -123,7 +124,7 @@ namespace Pulumi
|
|||
private static Task<ImmutableArray<Resource>> GatherExplicitDependenciesAsync(InputList<Resource> resources)
|
||||
=> resources.ToOutput().GetValueAsync(whenUnknown: ImmutableArray<Resource>.Empty);
|
||||
|
||||
private static async Task<HashSet<string>> GetAllTransitivelyReferencedResourceUrnsAsync(
|
||||
internal static async Task<HashSet<string>> GetAllTransitivelyReferencedResourceUrnsAsync(
|
||||
HashSet<Resource> resources)
|
||||
{
|
||||
// Go through 'resources', but transitively walk through **Component** resources, collecting any
|
||||
|
@ -152,8 +153,10 @@ namespace Pulumi
|
|||
// * Comp3 and Cust5 because Comp3 is a child of a remote component resource
|
||||
var transitivelyReachableResources = GetTransitivelyReferencedChildResourcesOfComponentResources(resources);
|
||||
|
||||
var transitivelyReachableCustomResources = transitivelyReachableResources.Where(res => {
|
||||
switch (res) {
|
||||
var transitivelyReachableCustomResources = transitivelyReachableResources.Where(res =>
|
||||
{
|
||||
switch (res)
|
||||
{
|
||||
case CustomResource _: return true;
|
||||
case ComponentResource component: return component.remote;
|
||||
default: return false; // Unreachable
|
||||
|
|
|
@ -28,8 +28,8 @@ namespace Pulumi
|
|||
var urn = await resource.Urn.GetValueAsync(whenUnknown: default!).ConfigureAwait(false);
|
||||
var props = await outputs.GetValueAsync(whenUnknown: default!).ConfigureAwait(false);
|
||||
|
||||
var serialized = await SerializeAllPropertiesAsync(
|
||||
opLabel, props, await MonitorSupportsResourceReferences().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
var keepResources = await MonitorSupportsResourceReferences().ConfigureAwait(false);
|
||||
var serialized = await SerializeAllPropertiesAsync(opLabel, props, keepResources).ConfigureAwait(false);
|
||||
Log.Debug($"RegisterResourceOutputs RPC prepared: urn={urn}" +
|
||||
(_excessiveDebugOutput ? $", outputs={JsonFormatter.Default.Format(serialized)}" : ""));
|
||||
|
||||
|
|
|
@ -20,16 +20,20 @@ namespace Pulumi
|
|||
/// to registerResource.
|
||||
/// </summary>
|
||||
private static Task<SerializationResult> SerializeResourcePropertiesAsync(
|
||||
string label, IDictionary<string, object?> args, bool keepResources)
|
||||
string label, IDictionary<string, object?> args, bool keepResources, bool keepOutputValues)
|
||||
{
|
||||
return SerializeFilteredPropertiesAsync(
|
||||
label, args, key => key != Constants.IdPropertyName && key != Constants.UrnPropertyName, keepResources);
|
||||
label, args,
|
||||
key => key != Constants.IdPropertyName && key != Constants.UrnPropertyName,
|
||||
keepResources, keepOutputValues: keepOutputValues);
|
||||
}
|
||||
|
||||
private static async Task<Struct> SerializeAllPropertiesAsync(
|
||||
string label, IDictionary<string, object?> args, bool keepResources)
|
||||
string label, IDictionary<string, object?> args, bool keepResources, bool keepOutputValues = false)
|
||||
{
|
||||
var result = await SerializeFilteredPropertiesAsync(label, args, _ => true, keepResources).ConfigureAwait(false);
|
||||
var result = await SerializeFilteredPropertiesAsync(
|
||||
label, args, _ => true,
|
||||
keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
return result.Serialized;
|
||||
}
|
||||
|
||||
|
@ -38,10 +42,20 @@ namespace Pulumi
|
|||
/// awaiting all interior promises for properties with keys that match the provided filter,
|
||||
/// creating a reasonable POCO object that can be remoted over to registerResource.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="label">label</param>
|
||||
/// <param name="args">args</param>
|
||||
/// <param name="acceptKey">acceptKey</param>
|
||||
/// <param name="keepResources">keepResources</param>
|
||||
/// <param name="keepOutputValues">
|
||||
/// Specifies if we should marshal output values. It is the callers
|
||||
/// responsibility to ensure that the monitor supports the OutputValues
|
||||
/// feature.
|
||||
/// </param>
|
||||
private static async Task<SerializationResult> SerializeFilteredPropertiesAsync(
|
||||
string label, IDictionary<string, object?> args, Predicate<string> acceptKey, bool keepResources)
|
||||
string label, IDictionary<string, object?> args, Predicate<string> acceptKey, bool keepResources, bool keepOutputValues)
|
||||
{
|
||||
var result = await SerializeFilteredPropertiesRawAsync(label, args, acceptKey, keepResources);
|
||||
var result = await SerializeFilteredPropertiesRawAsync(label, args, acceptKey, keepResources, keepOutputValues);
|
||||
return result.ToSerializationResult();
|
||||
}
|
||||
|
||||
|
@ -50,7 +64,7 @@ namespace Pulumi
|
|||
/// last step of encoding the value into a Protobuf form.
|
||||
/// </summary>
|
||||
private static async Task<RawSerializationResult> SerializeFilteredPropertiesRawAsync(
|
||||
string label, IDictionary<string, object?> args, Predicate<string> acceptKey, bool keepResources)
|
||||
string label, IDictionary<string, object?> args, Predicate<string> acceptKey, bool keepResources, bool keepOutputValues)
|
||||
{
|
||||
var propertyToDependentResources = ImmutableDictionary.CreateBuilder<string, HashSet<Resource>>();
|
||||
var result = ImmutableDictionary.CreateBuilder<string, object>();
|
||||
|
@ -61,7 +75,7 @@ namespace Pulumi
|
|||
{
|
||||
// We treat properties with null values as if they do not exist.
|
||||
var serializer = new Serializer(_excessiveDebugOutput);
|
||||
var v = await serializer.SerializeAsync($"{label}.{key}", val, keepResources).ConfigureAwait(false);
|
||||
var v = await serializer.SerializeAsync($"{label}.{key}", val, keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
if (v != null)
|
||||
{
|
||||
result[key] = v;
|
||||
|
|
|
@ -34,7 +34,14 @@ namespace Pulumi.Serialization
|
|||
/// </summary>
|
||||
public const string SpecialResourceSig = "5cf8f73096256a8f31e491e813e4eb8e";
|
||||
|
||||
public const string SecretValueName = "value";
|
||||
/// <summary>
|
||||
/// SpecialOutputValueSig is a randomly assigned hash used to identify outputs in maps. See sdk/go/common/resource/properties.go.
|
||||
/// </summary>
|
||||
public const string SpecialOutputValueSig = "d0e6a833031e9bbcd3f4e8bde6ca49a4";
|
||||
|
||||
public const string SecretName = "secret";
|
||||
public const string ValueName = "value";
|
||||
public const string DependenciesName = "dependencies";
|
||||
|
||||
public const string AssetTextName = "text";
|
||||
public const string ArchiveAssetsName = "assets";
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace Pulumi.Serialization
|
|||
});
|
||||
|
||||
public static OutputData<object?> Deserialize(Value value)
|
||||
=> DeserializeCore(value,
|
||||
=> DeserializeCore(value,
|
||||
v => v.KindCase switch
|
||||
{
|
||||
Value.KindOneofCase.NumberValue => DeserializerDouble(v),
|
||||
|
@ -120,7 +120,7 @@ namespace Pulumi.Serialization
|
|||
while (IsSpecialStruct(value, out var sig) &&
|
||||
sig == Constants.SpecialSecretSig)
|
||||
{
|
||||
if (!value.StructValue.Fields.TryGetValue(Constants.SecretValueName, out var secretValue))
|
||||
if (!value.StructValue.Fields.TryGetValue(Constants.ValueName, out var secretValue))
|
||||
throw new InvalidOperationException("Secrets must have a field called 'value'");
|
||||
|
||||
isSecret = true;
|
||||
|
@ -222,7 +222,8 @@ namespace Pulumi.Serialization
|
|||
throw new InvalidOperationException("Value was marked as a Resource, but did not conform to required shape.");
|
||||
}
|
||||
|
||||
if (!TryGetStringValue(value.StructValue.Fields, Constants.ResourceVersionName, out var version)) {
|
||||
if (!TryGetStringValue(value.StructValue.Fields, Constants.ResourceVersionName, out var version))
|
||||
{
|
||||
version = "";
|
||||
}
|
||||
|
||||
|
@ -231,7 +232,8 @@ namespace Pulumi.Serialization
|
|||
var qualifiedTypeParts = qualifiedType.Split('$');
|
||||
var type = qualifiedTypeParts[^1];
|
||||
|
||||
if (ResourcePackages.TryConstruct(type, version, urn, out resource)) {
|
||||
if (ResourcePackages.TryConstruct(type, version, urn, out resource))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016-2019, Pulumi Corporation
|
||||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
@ -21,7 +21,7 @@ namespace Pulumi.Serialization
|
|||
|
||||
public Serializer(bool excessiveDebugOutput)
|
||||
{
|
||||
this.DependentResources = new HashSet<Resource>();
|
||||
DependentResources = new HashSet<Resource>();
|
||||
_excessiveDebugOutput = excessiveDebugOutput;
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ namespace Pulumi.Serialization
|
|||
/// <item><see cref="Asset"/>s</item>
|
||||
/// <item><see cref="Archive"/>s</item>
|
||||
/// <item><see cref="Resource"/>s</item>
|
||||
/// <item><see cref="ResourceArgs"/>s</item>
|
||||
/// <item><see cref="ResourceArgs"/></item>
|
||||
/// <item><see cref="JsonElement"/></item>
|
||||
/// </list>
|
||||
/// Additionally, other more complex objects can be serialized as long as they are built
|
||||
|
@ -67,7 +67,7 @@ namespace Pulumi.Serialization
|
|||
/// </list>
|
||||
/// No other result type are allowed to be returned.
|
||||
/// </summary>
|
||||
public async Task<object?> SerializeAsync(string ctx, object? prop, bool keepResources)
|
||||
public async Task<object?> SerializeAsync(string ctx, object? prop, bool keepResources, bool keepOutputValues = false)
|
||||
{
|
||||
// IMPORTANT:
|
||||
// IMPORTANT: Keep this in sync with serializesPropertiesSync in invoke.ts
|
||||
|
@ -87,10 +87,15 @@ namespace Pulumi.Serialization
|
|||
}
|
||||
|
||||
if (prop is InputArgs args)
|
||||
return await SerializeInputArgsAsync(ctx, args, keepResources).ConfigureAwait(false);
|
||||
{
|
||||
return await SerializeInputArgsAsync(ctx, args, keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (prop is AssetOrArchive assetOrArchive)
|
||||
{
|
||||
// There's no need to pass keepOutputValues when serializing assets or archives.
|
||||
return await SerializeAssetOrArchiveAsync(ctx, assetOrArchive, keepResources).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (prop is Task)
|
||||
{
|
||||
|
@ -105,7 +110,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
Log.Debug($"Serialize property[{ctx}]: Recursing into IInput");
|
||||
}
|
||||
|
||||
return await SerializeAsync(ctx, input.ToOutput(), keepResources).ConfigureAwait(false);
|
||||
return await SerializeAsync(ctx, input.ToOutput(), keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (prop is IUnion union)
|
||||
|
@ -115,7 +120,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
Log.Debug($"Serialize property[{ctx}]: Recursing into IUnion");
|
||||
}
|
||||
|
||||
return await SerializeAsync(ctx, union.Value, keepResources).ConfigureAwait(false);
|
||||
return await SerializeAsync(ctx, union.Value, keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (prop is JsonElement element)
|
||||
|
@ -134,9 +139,9 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
{
|
||||
Log.Debug($"Serialize property[{ctx}]: Recursing into Output");
|
||||
}
|
||||
|
||||
var data = await output.GetDataAsync().ConfigureAwait(false);
|
||||
this.DependentResources.AddRange(data.Resources);
|
||||
DependentResources.AddRange(data.Resources);
|
||||
var propResources = new HashSet<Resource>(data.Resources);
|
||||
|
||||
// When serializing an Output, we will either serialize it as its resolved value or the "unknown value"
|
||||
// sentinel. We will do the former for all outputs created directly by user code (such outputs always
|
||||
|
@ -144,15 +149,56 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
var isKnown = data.IsKnown;
|
||||
var isSecret = data.IsSecret;
|
||||
|
||||
var valueSerializer = new Serializer(_excessiveDebugOutput);
|
||||
var value = await valueSerializer.SerializeAsync($"{ctx}.id", data.Value, keepResources, keepOutputValues: false).ConfigureAwait(false);
|
||||
var promiseDeps = valueSerializer.DependentResources;
|
||||
DependentResources.UnionWith(promiseDeps);
|
||||
propResources.UnionWith(promiseDeps);
|
||||
|
||||
if (keepOutputValues)
|
||||
{
|
||||
if (isKnown && !isSecret && propResources.Count == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var urnDeps = new HashSet<Resource>();
|
||||
foreach (var resource in propResources)
|
||||
{
|
||||
var urnSerializer = new Serializer(_excessiveDebugOutput);
|
||||
await urnSerializer.SerializeAsync($"{ctx} dependency", resource.Urn, keepResources, keepOutputValues: false).ConfigureAwait(false);
|
||||
urnDeps.UnionWith(urnSerializer.DependentResources);
|
||||
}
|
||||
DependentResources.UnionWith(urnDeps);
|
||||
propResources.UnionWith(urnDeps);
|
||||
|
||||
var dependencies = await Deployment.GetAllTransitivelyReferencedResourceUrnsAsync(propResources).ConfigureAwait(false);
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, object?>();
|
||||
builder.Add(Constants.SpecialSigKey, Constants.SpecialOutputValueSig);
|
||||
if (isKnown)
|
||||
{
|
||||
builder.Add(Constants.ValueName, value);
|
||||
}
|
||||
if (isSecret)
|
||||
{
|
||||
builder.Add(Constants.SecretName, isSecret);
|
||||
}
|
||||
if (dependencies.Count > 0)
|
||||
{
|
||||
builder.Add(Constants.DependenciesName,
|
||||
dependencies.OrderBy(x => x, StringComparer.Ordinal).ToImmutableArray<object>());
|
||||
}
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
if (!isKnown)
|
||||
return Constants.UnknownValue;
|
||||
|
||||
var value = await SerializeAsync($"{ctx}.id", data.Value, keepResources).ConfigureAwait(false);
|
||||
if (isSecret)
|
||||
{
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, object?>();
|
||||
builder.Add(Constants.SpecialSigKey, Constants.SpecialSecretSig);
|
||||
builder.Add(Constants.SecretValueName, value);
|
||||
builder.Add(Constants.ValueName, value);
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
|
@ -167,12 +213,12 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
Log.Debug($"Serialize property[{ctx}]: Encountered CustomResource");
|
||||
}
|
||||
|
||||
this.DependentResources.Add(customResource);
|
||||
DependentResources.Add(customResource);
|
||||
|
||||
var id = await SerializeAsync($"{ctx}.id", customResource.Id, keepResources).ConfigureAwait(false);
|
||||
var id = await SerializeAsync($"{ctx}.id", customResource.Id, keepResources, keepOutputValues: false).ConfigureAwait(false);
|
||||
if (keepResources)
|
||||
{
|
||||
var urn = await SerializeAsync($"{ctx}.urn", customResource.Urn, keepResources).ConfigureAwait(false);
|
||||
var urn = await SerializeAsync($"{ctx}.urn", customResource.Urn, keepResources, keepOutputValues: false).ConfigureAwait(false);
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, object?>();
|
||||
builder.Add(Constants.SpecialSigKey, Constants.SpecialResourceSig);
|
||||
builder.Add(Constants.ResourceUrnName, urn);
|
||||
|
@ -206,7 +252,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
Log.Debug($"Serialize property[{ctx}]: Encountered ComponentResource");
|
||||
}
|
||||
|
||||
var urn = await SerializeAsync($"{ctx}.urn", componentResource.Urn, keepResources).ConfigureAwait(false);
|
||||
var urn = await SerializeAsync($"{ctx}.urn", componentResource.Urn, keepResources, keepOutputValues: false).ConfigureAwait(false);
|
||||
if (keepResources)
|
||||
{
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, object?>();
|
||||
|
@ -218,10 +264,14 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
}
|
||||
|
||||
if (prop is IDictionary dictionary)
|
||||
return await SerializeDictionaryAsync(ctx, dictionary, keepResources).ConfigureAwait(false);
|
||||
{
|
||||
return await SerializeDictionaryAsync(ctx, dictionary, keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (prop is IList list)
|
||||
return await SerializeListAsync(ctx, list, keepResources).ConfigureAwait(false);
|
||||
{
|
||||
return await SerializeListAsync(ctx, list, keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (prop is Enum e && e.GetTypeCode() == TypeCode.Int32)
|
||||
{
|
||||
|
@ -257,27 +307,27 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
case JsonValueKind.False:
|
||||
return element.GetBoolean();
|
||||
case JsonValueKind.Array:
|
||||
{
|
||||
var result = ImmutableArray.CreateBuilder<object?>();
|
||||
var index = 0;
|
||||
foreach (var child in element.EnumerateArray())
|
||||
{
|
||||
result.Add(SerializeJson($"{ctx}[{index}]", child));
|
||||
index++;
|
||||
}
|
||||
var result = ImmutableArray.CreateBuilder<object?>();
|
||||
var index = 0;
|
||||
foreach (var child in element.EnumerateArray())
|
||||
{
|
||||
result.Add(SerializeJson($"{ctx}[{index}]", child));
|
||||
index++;
|
||||
}
|
||||
|
||||
return result.ToImmutable();
|
||||
}
|
||||
return result.ToImmutable();
|
||||
}
|
||||
case JsonValueKind.Object:
|
||||
{
|
||||
var result = ImmutableDictionary.CreateBuilder<string, object?>();
|
||||
foreach (var x in element.EnumerateObject())
|
||||
{
|
||||
result[x.Name] = SerializeJson($"{ctx}.{x.Name}", x.Value);
|
||||
}
|
||||
var result = ImmutableDictionary.CreateBuilder<string, object?>();
|
||||
foreach (var x in element.EnumerateObject())
|
||||
{
|
||||
result[x.Name] = SerializeJson($"{ctx}.{x.Name}", x.Value);
|
||||
}
|
||||
|
||||
return result.ToImmutable();
|
||||
}
|
||||
return result.ToImmutable();
|
||||
}
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown {nameof(JsonElement)}.{nameof(JsonElement.ValueKind)}: {element.ValueKind}");
|
||||
}
|
||||
|
@ -296,7 +346,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
throw new InvalidOperationException("Cannot serialize invalid archive");
|
||||
|
||||
var propName = assetOrArchive.PropName;
|
||||
var value = await SerializeAsync(ctx + "." + propName, assetOrArchive.Value, keepResources).ConfigureAwait(false);
|
||||
var value = await SerializeAsync(ctx + "." + propName, assetOrArchive.Value, keepResources, keepOutputValues: false).ConfigureAwait(false);
|
||||
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, object>();
|
||||
builder.Add(Constants.SpecialSigKey, assetOrArchive.SigKey);
|
||||
|
@ -304,7 +354,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
private async Task<ImmutableDictionary<string, object>> SerializeInputArgsAsync(string ctx, InputArgs args, bool keepResources)
|
||||
private async Task<ImmutableDictionary<string, object>> SerializeInputArgsAsync(string ctx, InputArgs args, bool keepResources, bool keepOutputValues)
|
||||
{
|
||||
if (_excessiveDebugOutput)
|
||||
{
|
||||
|
@ -312,10 +362,10 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
}
|
||||
|
||||
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
|
||||
return await SerializeDictionaryAsync(ctx, dictionary, keepResources).ConfigureAwait(false);
|
||||
return await SerializeDictionaryAsync(ctx, dictionary, keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<ImmutableArray<object?>> SerializeListAsync(string ctx, IList list, bool keepResources)
|
||||
private async Task<ImmutableArray<object?>> SerializeListAsync(string ctx, IList list, bool keepResources, bool keepOutputValues)
|
||||
{
|
||||
if (_excessiveDebugOutput)
|
||||
{
|
||||
|
@ -330,13 +380,13 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
Log.Debug($"Serialize property[{ctx}]: array[{i}] element");
|
||||
}
|
||||
|
||||
result.Add(await SerializeAsync($"{ctx}[{i}]", list[i], keepResources).ConfigureAwait(false));
|
||||
result.Add(await SerializeAsync($"{ctx}[{i}]", list[i], keepResources, keepOutputValues).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
return result.MoveToImmutable();
|
||||
}
|
||||
|
||||
private async Task<ImmutableDictionary<string, object>> SerializeDictionaryAsync(string ctx, IDictionary dictionary, bool keepResources)
|
||||
private async Task<ImmutableDictionary<string, object>> SerializeDictionaryAsync(string ctx, IDictionary dictionary, bool keepResources, bool keepOutputValues)
|
||||
{
|
||||
if (_excessiveDebugOutput)
|
||||
{
|
||||
|
@ -359,7 +409,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
|
||||
// When serializing an object, we omit any keys with null values. This matches
|
||||
// JSON semantics.
|
||||
var v = await SerializeAsync($"{ctx}.{stringKey}", dictionary[stringKey], keepResources).ConfigureAwait(false);
|
||||
var v = await SerializeAsync($"{ctx}.{stringKey}", dictionary[stringKey], keepResources, keepOutputValues).ConfigureAwait(false);
|
||||
if (v != null)
|
||||
{
|
||||
result[stringKey] = v;
|
||||
|
|
Loading…
Reference in a new issue