Fix 8322 (#8339)
* Fix 8322 * Untabify * Untabify again * Yet More untabify * More untabify * Final untabify * Add CHANGELOG_PENDING * Apply suggestions from code review Co-authored-by: Justin Van Patten <jvp@justinvp.com> * PR feedback Co-authored-by: Justin Van Patten <jvp@justinvp.com>
This commit is contained in:
parent
99fdad0ed9
commit
e37892ac4a
|
@ -3,5 +3,9 @@
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
- [sdk/dotnet] - Fixes failing preview for programs that call data
|
||||
sources (`F.Invoke`) with unknown outputs
|
||||
[#8339](https://github.com/pulumi/pulumi/pull/8339)
|
||||
|
||||
- [programgen/go] - Don't change imported resource names.
|
||||
[#8353](https://github.com/pulumi/pulumi/pull/8353)
|
||||
|
|
|
@ -17,6 +17,18 @@ namespace Pulumi.Tests.Mocks
|
|||
}
|
||||
}
|
||||
|
||||
public sealed class GetRoleInvokeArgs : Pulumi.InvokeArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The friendly IAM role name to match.
|
||||
/// </summary>
|
||||
[Input("name", required: true)]
|
||||
public Input<string> Name { get; set; } = null!;
|
||||
|
||||
public GetRoleInvokeArgs()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[OutputType]
|
||||
public sealed class GetRoleResult
|
||||
|
@ -42,5 +54,8 @@ namespace Pulumi.Tests.Mocks
|
|||
{
|
||||
public static Task<GetRoleResult> InvokeAsync(GetRoleArgs args, InvokeOptions? options = null)
|
||||
=> Pulumi.Deployment.Instance.InvokeAsync<GetRoleResult>("aws:iam/getRole:getRole", args ?? new GetRoleArgs(), options);
|
||||
|
||||
public static Output<GetRoleResult> Invoke(GetRoleInvokeArgs args, InvokeOptions? options = null)
|
||||
=> Pulumi.Deployment.Instance.Invoke<GetRoleResult>("aws:iam/getRole:getRole", args ?? new GetRoleInvokeArgs(), options);
|
||||
}
|
||||
}
|
||||
|
|
56
sdk/dotnet/Pulumi.Tests/Mocks/Issue8322.cs
Normal file
56
sdk/dotnet/Pulumi.Tests/Mocks/Issue8322.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Pulumi.Serialization;
|
||||
using Pulumi.Testing;
|
||||
|
||||
namespace Pulumi.Tests.Mocks
|
||||
{
|
||||
/// <summary>
|
||||
/// Supports testing that Invoke gets skippped when passed unknown inputs,
|
||||
/// instead of failing and breaking preview.
|
||||
///
|
||||
/// See pulumi/pulumi#8322.
|
||||
/// </summary>
|
||||
public sealed class Issue8322
|
||||
{
|
||||
public class ReproStack : Stack
|
||||
{
|
||||
[Output("result")]
|
||||
public Output<string> Result { get; private set; }
|
||||
|
||||
public ReproStack()
|
||||
{
|
||||
// First we need an unknown output (in preview). Use
|
||||
// Instance here as Output.CreateUnknown is not public.
|
||||
var instance = new Instance("instance1", new InstanceArgs());
|
||||
var unknown = instance.PublicIp;
|
||||
// To reproduce the bug, we call an Invoke with unknowns.
|
||||
var result = GetRole.Invoke(new GetRoleInvokeArgs() { Name = unknown });
|
||||
this.Result = result.Apply(r => r.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public class ReproMocks : IMocks
|
||||
{
|
||||
public Task<object> CallAsync(MockCallArgs args)
|
||||
{
|
||||
throw new Exception("CallAsync should not be called");
|
||||
}
|
||||
|
||||
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", Constants.UnknownValue },
|
||||
})),
|
||||
_ => throw new Exception($"Unknown resource {args.Type}")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -125,6 +125,18 @@ namespace Pulumi.Tests.Mocks
|
|||
Assert.Contains("Grpc.Core.RpcException: Status(StatusCode=\"Unknown\", Detail=\"error code 404\")", exception!.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestInvokeToleratesUnknownsInPreview()
|
||||
{
|
||||
var resources = await Deployment.TestAsync<Issue8322.ReproStack>(
|
||||
new Issue8322.ReproMocks(),
|
||||
new TestOptions() { IsPreview = true }
|
||||
);
|
||||
var stack = resources.OfType<Issue8322.ReproStack>().Single();
|
||||
var result = await stack.Result.GetValueAsync(whenUnknown: "unknown!");
|
||||
Assert.Equal("unknown!", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TestStackWithInvalidSchema()
|
||||
{
|
||||
|
|
|
@ -55,7 +55,22 @@ namespace Pulumi
|
|||
// the provider fully responsible for depdendency
|
||||
// tracking, which is a good future direction also for
|
||||
// `Invoke`.
|
||||
var result = await InvokeRawAsync(token, args, options).ConfigureAwait(false);
|
||||
|
||||
var keepResources = await this.MonitorSupportsResourceReferences().ConfigureAwait(false);
|
||||
var serializedArgs = await SerializeInvokeArgs(token, args, keepResources);
|
||||
|
||||
// Short-circuit actually invoking if `Unknowns` are
|
||||
// present in `args`, otherwise preview can break.
|
||||
if (Serializer.ContainsUnknowns(serializedArgs.PropertyValues))
|
||||
{
|
||||
return new OutputData<T>(resources: ImmutableHashSet<Resource>.Empty,
|
||||
value: default!,
|
||||
isKnown: false,
|
||||
isSecret: false);
|
||||
}
|
||||
|
||||
var protoArgs = serializedArgs.ToSerializationResult();
|
||||
var result = await InvokeRawAsync(token, protoArgs, options).ConfigureAwait(false);
|
||||
var data = Converter.ConvertValue<T>(err => Log.Warn(err), $"{token} result",
|
||||
new Value { StructValue = result.Serialized });
|
||||
var resources = ImmutableHashSet.CreateRange(
|
||||
|
@ -81,27 +96,8 @@ namespace Pulumi
|
|||
return data.Value;
|
||||
}
|
||||
|
||||
private async Task<SerializationResult> InvokeRawAsync(string token, InvokeArgs args, InvokeOptions? options)
|
||||
private async Task<SerializationResult> InvokeRawAsync(string token, SerializationResult argsSerializationResult, InvokeOptions? options)
|
||||
{
|
||||
var label = $"Invoking function: token={token} asynchronously";
|
||||
Log.Debug(label);
|
||||
|
||||
// Be resilient to misbehaving callers.
|
||||
// ReSharper disable once ConstantNullCoalescingCondition
|
||||
args ??= InvokeArgs.Empty;
|
||||
|
||||
// Wait for all values to be available, and then perform the RPC.
|
||||
var argsDict = await args.ToDictionaryAsync().ConfigureAwait(false);
|
||||
|
||||
var keepResources = await this.MonitorSupportsResourceReferences().ConfigureAwait(false);
|
||||
|
||||
var argsSerializationResult = await SerializeFilteredPropertiesAsync(
|
||||
label: $"invoke:{token}",
|
||||
args: argsDict,
|
||||
acceptKey: key => true,
|
||||
keepResources: keepResources
|
||||
).ConfigureAwait(false);
|
||||
|
||||
var serialized = argsSerializationResult.Serialized;
|
||||
|
||||
Log.Debug($"Invoke RPC prepared: token={token}" +
|
||||
|
@ -137,6 +133,33 @@ namespace Pulumi
|
|||
return new SerializationResult(result.Return, argsSerializationResult.PropertyToDependentResources);
|
||||
}
|
||||
|
||||
private async Task<RawSerializationResult> SerializeInvokeArgs(string token, InvokeArgs args, bool keepResources)
|
||||
{
|
||||
Log.Debug($"Invoking function: token={token} asynchronously");
|
||||
|
||||
// Be resilient to misbehaving callers.
|
||||
// ReSharper disable once ConstantNullCoalescingCondition
|
||||
args ??= InvokeArgs.Empty;
|
||||
|
||||
// Wait for all values to be available.
|
||||
var argsDict = await args.ToDictionaryAsync().ConfigureAwait(false);
|
||||
|
||||
return await SerializeFilteredPropertiesRawAsync(
|
||||
label: $"invoke:{token}",
|
||||
args: argsDict,
|
||||
acceptKey: key => true,
|
||||
keepResources: keepResources
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<SerializationResult> InvokeRawAsync(string token, InvokeArgs args, InvokeOptions? options)
|
||||
{
|
||||
var keepResources = await this.MonitorSupportsResourceReferences().ConfigureAwait(false);
|
||||
var argsSerializationRawResult = await SerializeInvokeArgs(token, args, keepResources);
|
||||
var argsSerializationResult = argsSerializationRawResult.ToSerializationResult();
|
||||
return await InvokeRawAsync(token, argsSerializationResult, options);
|
||||
}
|
||||
|
||||
private static ProviderResource? GetProvider(string token, InvokeOptions? options)
|
||||
=> options?.Provider ?? options?.Parent?.GetProvider(token);
|
||||
|
||||
|
|
|
@ -40,6 +40,17 @@ namespace Pulumi
|
|||
/// </summary>
|
||||
private static async Task<SerializationResult> SerializeFilteredPropertiesAsync(
|
||||
string label, IDictionary<string, object?> args, Predicate<string> acceptKey, bool keepResources)
|
||||
{
|
||||
var result = await SerializeFilteredPropertiesRawAsync(label, args, acceptKey, keepResources);
|
||||
return result.ToSerializationResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acts as `SerializeFilteredPropertiesAsync` without the
|
||||
/// 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)
|
||||
{
|
||||
var propertyToDependentResources = ImmutableDictionary.CreateBuilder<string, HashSet<Resource>>();
|
||||
var result = ImmutableDictionary.CreateBuilder<string, object>();
|
||||
|
@ -59,8 +70,8 @@ namespace Pulumi
|
|||
}
|
||||
}
|
||||
|
||||
return new SerializationResult(
|
||||
Serializer.CreateStruct(result.ToImmutable()),
|
||||
return new RawSerializationResult(
|
||||
result.ToImmutable(),
|
||||
propertyToDependentResources.ToImmutable());
|
||||
}
|
||||
|
||||
|
@ -85,5 +96,24 @@ namespace Pulumi
|
|||
propertyToDependentResources = PropertyToDependentResources;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct RawSerializationResult
|
||||
{
|
||||
public readonly ImmutableDictionary<string, object> PropertyValues;
|
||||
public readonly ImmutableDictionary<string, HashSet<Resource>> PropertyToDependentResources;
|
||||
|
||||
public RawSerializationResult(
|
||||
ImmutableDictionary<string, object> propertyValues,
|
||||
ImmutableDictionary<string, HashSet<Resource>> propertyToDependentResources)
|
||||
{
|
||||
PropertyValues = propertyValues;
|
||||
PropertyToDependentResources = propertyToDependentResources;
|
||||
}
|
||||
|
||||
public SerializationResult ToSerializationResult()
|
||||
=> new SerializationResult(
|
||||
Serializer.CreateStruct(PropertyValues),
|
||||
PropertyToDependentResources);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2016-2021, Pulumi Corporation
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Pulumi.Serialization
|
||||
{
|
||||
internal static class ImmutableDictionaryExtensions
|
||||
{
|
||||
public static bool AnyValues<TKey, TValue>(
|
||||
this ImmutableDictionary<TKey, TValue> immutableDictionary,
|
||||
Func<TValue, bool> predicate)
|
||||
where TKey : notnull
|
||||
{
|
||||
foreach (var (_, val) in immutableDictionary)
|
||||
{
|
||||
if (predicate(val))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -385,6 +385,27 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:
|
|||
_ => throw new InvalidOperationException("Unsupported value when converting to protobuf: " + value.GetType().FullName),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Detects encoded `Unknown` values in objects that conform
|
||||
/// to the grammar returned by `SerializeAsync`.
|
||||
///
|
||||
/// This possibly needs to be revisited to detect `Unknown`
|
||||
/// values before `SerializeAsync` converts them, in the more
|
||||
/// generic Output representation.
|
||||
/// </summary>
|
||||
internal static bool ContainsUnknowns(object? value)
|
||||
=> value switch
|
||||
{
|
||||
null => false,
|
||||
int _ => false,
|
||||
double d => false,
|
||||
bool b => false,
|
||||
string s => s == Constants.UnknownValue,
|
||||
ImmutableArray<object> list => list.Any(v => ContainsUnknowns(v)),
|
||||
ImmutableDictionary<string, object> dict => dict.AnyValues(v => ContainsUnknowns(v)),
|
||||
_ => throw new InvalidOperationException("Unsupported value when converting to protobuf: " + value.GetType().FullName),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Given a <see cref="ImmutableDictionary{TKey, TValue}"/> produced by <see cref="SerializeAsync"/>,
|
||||
/// produces the equivalent <see cref="Struct"/> that can be passed to the Pulumi engine.
|
||||
|
|
Loading…
Reference in a new issue