// Copyright 2016-2021, Pulumi Corporation using System; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Google.Protobuf.WellKnownTypes; using Pulumi.Serialization; using Pulumirpc; namespace Pulumi { public sealed partial class Deployment { Task IDeployment.InvokeAsync(string token, InvokeArgs args, InvokeOptions? options) => InvokeAsync(token, args, options, convertResult: false); Task IDeployment.InvokeAsync(string token, InvokeArgs args, InvokeOptions? options) => InvokeAsync(token, args, options, convertResult: true); Output IDeployment.Invoke(string token, InvokeArgs args, InvokeOptions? options) => new Output(RawInvoke(token, args, options)); private async Task> RawInvoke(string token, InvokeArgs args, InvokeOptions? options) { // This method backs all `Fn.Invoke()` calls that generate // `Output` and may include `Input` values in the // `args`. It needs to decide which control-flow tracking // features are supported in the SDK and which ones in the // provider implementing the invoke logic. // // Current choices are: // // - any resource dependency found by a recursive // traversal of `args` that awaits and inspects every // `Input` will always be propagated into the // `Output`; the provider cannot "swallow" // dependencies // // - the provider is responsible for deciding whether the // `Output` is secret and known, and may add // additional dependencies // // This means that presence of secrets or unknowns in the // `args` does not guarantee the result is secret or // unknown, which differs from Pulumi SDKs that choose to // implement these invokes via `Apply` (currently Go and // Python). // // Differences from `Call`: the `Invoke` gRPC protocol // does not yet support passing or returning out-of-band // dependencies to the provider, and in-band `Resource` // value support is subject to feature negotiation (see // `MonitorSupportsResourceReferences`). So `Call` makes // 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 data = Converter.ConvertValue(err => Log.Warn(err), $"{token} result", new Value { StructValue = result.Serialized }); var resources = ImmutableHashSet.CreateRange( result.PropertyToDependentResources.Values.SelectMany(r => r) .Union(data.Resources)); return new OutputData(resources: resources, value: data.Value, isKnown: data.IsKnown, isSecret: data.IsSecret); } private async Task InvokeAsync( string token, InvokeArgs args, InvokeOptions? options, bool convertResult) { var result = await InvokeRawAsync(token, args, options).ConfigureAwait(false); if (!convertResult) { return default!; } var data = Converter.ConvertValue(err => Log.Warn(err), $"{token} result", new Value { StructValue = result.Serialized }); return data.Value; } private async Task InvokeRawAsync(string token, InvokeArgs args, 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}" + (_excessiveDebugOutput ? $", obj={serialized}" : "")); var provider = await ProviderResource.RegisterAsync(GetProvider(token, options)).ConfigureAwait(false); var result = await this.Monitor.InvokeAsync(new InvokeRequest { Tok = token, Provider = provider ?? "", Version = options?.Version ?? "", Args = serialized, AcceptResources = !_disableResourceReferences, }).ConfigureAwait(false); if (result.Failures.Count > 0) { var reasons = ""; foreach (var reason in result.Failures) { if (reasons != "") { reasons += "; "; } reasons += $"{reason.Reason} ({reason.Property})"; } throw new InvokeException($"Invoke of '{token}' failed: {reasons}"); } return new SerializationResult(result.Return, argsSerializationResult.PropertyToDependentResources); } private static ProviderResource? GetProvider(string token, InvokeOptions? options) => options?.Provider ?? options?.Parent?.GetProvider(token); private class InvokeException : Exception { public InvokeException(string error) : base(error) { } } } }