From db01deb8c8f4841df3e2ad664db48433655cdfa5 Mon Sep 17 00:00:00 2001 From: Fraser Waters Date: Wed, 27 Apr 2022 18:16:22 +0100 Subject: [PATCH] Provider support for dotnet This adds a new namespace to the dotnet sdk "Provider" with classes to support the writing of a native dotnet provider. The Provider class will internally deal with our grpc protocol and call into virtual methods given by the user which use only dotnet native domain models. It is a bit sad all this is manually written, I feel like there _might_ be a good way to codegen this from the protobuf specs such that if they ever change this file could stay in sync. Also the PropertyValue Marshal/Unmarshal duplicates a lot of the existing serialisation logic, and I think that logic could be rewritten as a layer ontop of this (that is we'd have "objects -> PropertyValues" and "PropetyValues -> grpc" rather than what we currently have which is "objects -> grpc" and now "PropertyValues -> grpc") --- .../Pulumi.Automation/Pulumi.Automation.xml | 25 + sdk/dotnet/Pulumi/Provider/Provider.cs | 1502 +++++++++++++++++ sdk/dotnet/Pulumi/Pulumi.csproj | 1 + 3 files changed, 1528 insertions(+) create mode 100644 sdk/dotnet/Pulumi/Provider/Provider.cs diff --git a/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml b/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml index 51ef5e23705a..56aa04345b1b 100644 --- a/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml +++ b/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml @@ -924,6 +924,31 @@ Colorize output. Choices are: always, never, raw, auto (default "auto") + + + Flow log settings to child processes (like plugins) + + + + + Enable verbose logging (e.g., v=3); anything >3 is very verbose + + + + + Log to stderr instead of to files + + + + + Emit tracing to the specified endpoint. Use the file: scheme to write tracing data to a local file + + + + + Print detailed debugging output during resource operations + + Options controlling the behavior of an operation. diff --git a/sdk/dotnet/Pulumi/Provider/Provider.cs b/sdk/dotnet/Pulumi/Provider/Provider.cs new file mode 100644 index 000000000000..0bc56fd4dd8a --- /dev/null +++ b/sdk/dotnet/Pulumi/Provider/Provider.cs @@ -0,0 +1,1502 @@ +using Pulumirpc; +using Pulumi.Serialization; +using Grpc.Core; +using Google.Protobuf.WellKnownTypes; +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Hosting; +using System.Net; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using System.Threading; + +namespace Pulumi.Provider +{ + + public readonly struct ResourceReference + { + public readonly string URN; + public readonly PropertyValue ID; + public readonly string PackageVersion; + + public ResourceReference(string urn, PropertyValue id, string version) + { + URN = urn; + ID = id; + PackageVersion = version; + } + } + + public readonly struct OutputReference + { + public readonly PropertyValue? Value; + public readonly ImmutableArray Dependencies; + + public OutputReference(PropertyValue? value, ImmutableArray dependencies) + { + Value = value; + Dependencies = dependencies; + } + } + + public sealed class PropertyValue + { + private bool IsNull + { + get + { + // Null if all the other properties aren't set + return + BoolValue == null && + NumberValue == null && + StringValue == null && + ArrayValue == null && + ObjectValue == null && + AssetValue == null && + ArchiveValue == null && + SecretValue == null && + ResourceValue == null && + !IsComputed; + } + } + + private readonly bool? BoolValue; + private readonly double? NumberValue; + private readonly string? StringValue; + private readonly ImmutableArray? ArrayValue; + private readonly ImmutableDictionary? ObjectValue; + private readonly Asset? AssetValue; + private readonly Archive? ArchiveValue; + private readonly PropertyValue? SecretValue; + private readonly ResourceReference? ResourceValue; + private readonly OutputReference? OutputValue; + private readonly bool IsComputed; + + public T Match( + Func nullCase, + Func boolCase, + Func numberCase, + Func stringCase, + Func, T> arrayCase, + Func, T> objectCase, + Func assetCase, + Func archiveCase, + Func secretCase, + Func resourceCase, + Func outputCase, + Func computedCase) + { + if (BoolValue != null) return boolCase(BoolValue.Value); + if (NumberValue != null) return numberCase(NumberValue.Value); + if (StringValue != null) return stringCase(StringValue); + if (ArrayValue != null) return arrayCase(ArrayValue.Value); + if (ObjectValue != null) return objectCase(ObjectValue); + if (AssetValue != null) return assetCase(AssetValue); + if (ArchiveValue != null) return archiveCase(ArchiveValue); + if (SecretValue != null) return secretCase(SecretValue); + if (ResourceValue != null) return resourceCase(ResourceValue.Value); + if (OutputValue != null) return outputCase(OutputValue.Value); + if (IsComputed) return computedCase(); + return nullCase(); + } + + private enum SpecialType + { + IsNull, + IsComputed, + } + + public static PropertyValue Null = new PropertyValue(SpecialType.IsComputed); + public static PropertyValue Computed = new PropertyValue(SpecialType.IsNull); + + public PropertyValue(bool value) + { + BoolValue = value; + } + public PropertyValue(double value) + { + NumberValue = value; + } + public PropertyValue(string value) + { + StringValue = value; + } + public PropertyValue(ImmutableArray value) + { + ArrayValue = value; + } + public PropertyValue(ImmutableDictionary value) + { + ObjectValue = value; + } + public PropertyValue(Asset value) + { + AssetValue = value; + } + public PropertyValue(Archive value) + { + ArchiveValue = value; + } + public PropertyValue(PropertyValue value) + { + SecretValue = value; + } + public PropertyValue(ResourceReference value) + { + ResourceValue = value; + } + public PropertyValue(OutputReference value) + { + OutputValue = value; + } + + private PropertyValue(SpecialType type) + { + if (type == SpecialType.IsComputed) + { + IsComputed = true; + } + } + + static bool TryGetStringValue(Google.Protobuf.Collections.MapField fields, string key, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? result) + { + if (fields.TryGetValue(key, out var value) && value.KindCase == Value.KindOneofCase.StringValue) + { + result = value.StringValue; + return true; + } + result = null; + return false; + } + + internal static ImmutableDictionary Marshal(Struct properties) + { + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var item in properties.Fields) + { + builder.Add(item.Key, Marshal(item.Value)); + } + return builder.ToImmutable(); + } + + internal static PropertyValue Marshal(Value value) + { + switch (value.KindCase) + { + case Value.KindOneofCase.NullValue: + return PropertyValue.Null; + case Value.KindOneofCase.BoolValue: + return new PropertyValue(value.BoolValue); + case Value.KindOneofCase.NumberValue: + return new PropertyValue(value.NumberValue); + case Value.KindOneofCase.StringValue: + { + // This could be the special unknown value + if (value.StringValue == Constants.UnknownValue) + { + return PropertyValue.Computed; + } + return new PropertyValue(value.StringValue); + } + case Value.KindOneofCase.ListValue: + { + var listValue = value.ListValue; + var builder = ImmutableArray.CreateBuilder(listValue.Values.Count); + foreach (var item in listValue.Values) + { + builder.Add(Marshal(item)); + } + return new PropertyValue(builder.ToImmutable()); + } + case Value.KindOneofCase.StructValue: + { + // This could be a plain object, or one of our specials + var structValue = value.StructValue; + + if (TryGetStringValue(structValue.Fields, Constants.SpecialSigKey, out var sig)) + { + switch (sig) + { + case Constants.SpecialSecretSig: + { + if (!structValue.Fields.TryGetValue(Constants.ValueName, out var secretValue)) + throw new InvalidOperationException("Secrets must have a field called 'value'"); + + return new PropertyValue(Marshal(secretValue)); + } + case Constants.SpecialAssetSig: + { + if (TryGetStringValue(structValue.Fields, Constants.AssetOrArchivePathName, out var path)) + return new PropertyValue(new FileAsset(path)); + + if (TryGetStringValue(structValue.Fields, Constants.AssetOrArchiveUriName, out var uri)) + return new PropertyValue(new RemoteAsset(uri)); + + if (TryGetStringValue(structValue.Fields, Constants.AssetTextName, out var text)) + return new PropertyValue(new StringAsset(text)); + + throw new InvalidOperationException("Value was marked as Asset, but did not conform to required shape."); + } + case Constants.SpecialArchiveSig: + { + if (TryGetStringValue(structValue.Fields, Constants.AssetOrArchivePathName, out var path)) + return new PropertyValue(new FileArchive(path)); + + if (TryGetStringValue(structValue.Fields, Constants.AssetOrArchiveUriName, out var uri)) + return new PropertyValue(new RemoteArchive(uri)); + + if (structValue.Fields.TryGetValue(Constants.ArchiveAssetsName, out var assetsValue)) + { + if (assetsValue.KindCase == Value.KindOneofCase.StructValue) + { + var assets = ImmutableDictionary.CreateBuilder(); + foreach (var (name, val) in assetsValue.StructValue.Fields) + { + var innerAssetOrArchive = Marshal(val); + if (innerAssetOrArchive.AssetValue != null) + { + assets[name] = innerAssetOrArchive.AssetValue; + } + else if (innerAssetOrArchive.ArchiveValue != null) + { + assets[name] = innerAssetOrArchive.ArchiveValue; + } + else + { + throw new InvalidOperationException("AssetArchive contained an element that wasn't itself an Asset or Archive."); + } + } + + return new PropertyValue(new AssetArchive(assets.ToImmutable())); + } + } + + throw new InvalidOperationException("Value was marked as Archive, but did not conform to required shape."); + } + case Constants.SpecialResourceSig: + { + if (!TryGetStringValue(structValue.Fields, Constants.UrnPropertyName, out var urn)) + { + throw new InvalidOperationException("Value was marked as a Resource, but did not conform to required shape."); + } + + if (!TryGetStringValue(structValue.Fields, Constants.ResourceVersionName, out var version)) + { + version = ""; + } + + if (!structValue.Fields.TryGetValue(Constants.IdPropertyName, out var id)) + { + throw new InvalidOperationException("Value was marked as a Resource, but did not conform to required shape."); + } + + return new PropertyValue(new ResourceReference(urn, Marshal(id), version)); + } + case Constants.SpecialOutputValueSig: + { + PropertyValue? element = null; + if (structValue.Fields.TryGetValue(Constants.ValueName, out var knownElement)) + { + element = Marshal(knownElement); + } + var secret = false; + if (structValue.Fields.TryGetValue(Constants.SecretName, out var v)) + { + if (v.KindCase == Value.KindOneofCase.BoolValue) + { + secret = v.BoolValue; + } + else + { + throw new InvalidOperationException("Value was marked as an Output, but did not conform to required shape."); + } + } + + var dependenciesBuilder = ImmutableArray.CreateBuilder(); + if (structValue.Fields.TryGetValue(Constants.DependenciesName, out var dependencies)) + { + if (dependencies.KindCase == Value.KindOneofCase.ListValue) + { + foreach (var dependency in dependencies.ListValue.Values) + { + if (dependency.KindCase == Value.KindOneofCase.StringValue) + { + dependenciesBuilder.Add(dependency.StringValue); + } + else + { + throw new InvalidOperationException("Value was marked as an Output, but did not conform to required shape."); + } + } + } + else + { + throw new InvalidOperationException("Value was marked as an Output, but did not conform to required shape."); + } + } + + var output = new OutputReference(element, dependenciesBuilder.ToImmutable()); + + if (secret) + { + return new PropertyValue(new PropertyValue(output)); + } + else + { + return new PropertyValue(output); + } + } + + default: + throw new InvalidOperationException($"Unrecognized special signature: {sig}"); + } + } + else + { + // Just a plain object + var builder = ImmutableDictionary.CreateBuilder(); + foreach (var item in structValue.Fields) + { + builder.Add(item.Key, Marshal(item.Value)); + } + return new PropertyValue(builder.ToImmutable()); + } + } + + case Value.KindOneofCase.None: + default: + throw new InvalidOperationException($"Unexpected grpc value: {value}"); + } + } + + internal static Struct Unmarshal(IDictionary properties) + { + var result = new Struct(); + foreach (var item in properties) + { + result.Fields[item.Key] = Unmarshal(item.Value); + } + return result; + } + + private static Value UnmarshalAsset(Asset asset) + { + var result = new Struct(); + result.Fields[Constants.SpecialSigKey] = Value.ForString(Constants.SpecialAssetSig); + result.Fields[asset.PropName] = Value.ForString((string)asset.Value); + return Value.ForStruct(result); + } + + private static Value UnmarshalArchive(Archive archive) + { + var result = new Struct(); + result.Fields[Constants.SpecialSigKey] = Value.ForString(Constants.SpecialAssetSig); + + if (archive.Value is string str) + { + result.Fields[archive.PropName] = Value.ForString(str); + } + else + { + var inner = (ImmutableDictionary)archive.Value; + var innerStruct = new Struct(); + foreach (var item in inner) + { + innerStruct.Fields[item.Key] = UnmarshalAssetOrArchive(item.Value); + } + result.Fields[archive.PropName] = Value.ForStruct(innerStruct); + } + return Value.ForStruct(result); + } + + private static Value UnmarshalAssetOrArchive(AssetOrArchive assetOrArchive) + { + if (assetOrArchive is Asset asset) + { + return UnmarshalAsset(asset); + } + else if (assetOrArchive is Archive archive) + { + return UnmarshalArchive(archive); + } + throw new InvalidOperationException("Internal error, AssetOrArchive was neither an Asset or Archive"); + } + + private static Value UnmarshalOutput(OutputReference output, bool secret) + { + var result = new Struct(); + result.Fields[Constants.SpecialSigKey] = Value.ForString(Constants.SpecialOutputValueSig); + if (output.Value != null) + { + result.Fields[Constants.ValueName] = Unmarshal(output.Value); + } + + var dependencies = new Value[output.Dependencies.Length]; + var i = 0; + foreach (var dependency in output.Dependencies) + { + dependencies[i++] = Value.ForString(dependency); + } + result.Fields[Constants.DependenciesName] = Value.ForList(dependencies); + result.Fields[Constants.SecretName] = Value.ForBool(secret); + + return Value.ForStruct(result); + } + + internal static Value Unmarshal(PropertyValue value) + { + return value.Match( + () => Value.ForNull(), + b => Value.ForBool(b), + n => Value.ForNumber(n), + s => Value.ForString(s), + a => + { + var result = new Value[a.Length]; + for (int i = 0; i < a.Length; ++i) + { + result[i] = Unmarshal(a[i]); + } + return Value.ForList(result); + }, + o => + { + var result = new Struct(); + foreach (var item in o) + { + result.Fields[item.Key] = Unmarshal(item.Value); + } + return Value.ForStruct(result); + }, + asset => UnmarshalAsset(asset), + archive => UnmarshalArchive(archive), + secret => + { + // Special case if our secret value is an output + if (secret.OutputValue != null) + { + return UnmarshalOutput(secret.OutputValue.Value, true); + } + var result = new Struct(); + result.Fields[Constants.SpecialSigKey] = Value.ForString(Constants.SpecialSecretSig); + result.Fields[Constants.ValueName] = Unmarshal(secret); + return Value.ForStruct(result); + }, + resource => + { + var result = new Struct(); + result.Fields[Constants.SpecialSigKey] = Value.ForString(Constants.SpecialResourceSig); + result.Fields[Constants.UrnPropertyName] = Value.ForString(resource.URN); + result.Fields[Constants.IdPropertyName] = Unmarshal(resource.ID); + if (resource.PackageVersion != "") + { + result.Fields[Constants.ResourceVersionName] = Value.ForString(resource.PackageVersion); + } + return Value.ForStruct(result); + }, + output => UnmarshalOutput(output, false), + () => Value.ForString(Constants.UnknownValue) + ); + } + } + + public sealed class CheckRequest + { + public readonly string Urn; + public readonly ImmutableDictionary Olds; + public readonly ImmutableDictionary News; + public readonly ImmutableArray RandomSeed; + + public CheckRequest(string urn, ImmutableDictionary olds, ImmutableDictionary news, ImmutableArray randomSeed) + { + Urn = urn; + Olds = olds; + News = news; + RandomSeed = randomSeed; + } + } + + public sealed class CheckFailure + { + public string Property { get; set; } + public string Reason { get; set; } + + public CheckFailure(string property, string reason) + { + Property = property; + Reason = reason; + } + } + + public sealed class CheckResponse + { + public IDictionary? Inputs { get; set; } + public IList? Failures { get; set; } + } + + + public sealed class DiffRequest + { + public readonly string Urn; + public readonly string ID; + public readonly ImmutableDictionary Olds; + public readonly ImmutableDictionary News; + public readonly ImmutableArray IgnoreChanges; + + public DiffRequest(string urn, string id, ImmutableDictionary olds, ImmutableDictionary news, ImmutableArray ignoreChanges) + { + Urn = urn; + ID = id; + Olds = olds; + News = news; + IgnoreChanges = ignoreChanges; + } + } + + public enum PropertyDiffKind + { + Add = 0, + AddReplace = 1, + Delete = 2, + DeleteReplace = 3, + Update = 4, + UpdateReplace = 5, + } + + public sealed class PropertyDiff + { + public PropertyDiffKind Kind { get; set; } + public bool InputDiff { get; set; } + } + + public sealed class DiffResponse + { + public bool? Changes { get; set; } + + public IList? Replaces { get; set; } + + public IList? Stables { get; set; } + + public bool DeleteBeforeReplace { get; set; } + public IList? Diffs { get; set; } + + public IDictionary? DetailedDiff { get; set; } + } + + public sealed class InvokeRequest + { + public readonly string Tok; + public readonly ImmutableDictionary Args; + + public InvokeRequest(string tok, ImmutableDictionary args) + { + Tok = tok; + Args = args; + } + } + + public sealed class InvokeResponse + { + + public IDictionary? Return { get; set; } + public IList? Failures { get; set; } + } + + public sealed class GetSchemaRequest + { + public readonly int Version; + + public GetSchemaRequest(int version) + { + Version = version; + } + } + + public sealed class GetSchemaResponse + { + public string? Schema { get; set; } + } + + public sealed class ConfigureRequest + { + public readonly ImmutableDictionary Variables; + public readonly ImmutableDictionary Args; + public readonly bool AcceptSecrets; + public readonly bool AcceptResources; + + public ConfigureRequest(ImmutableDictionary variables, ImmutableDictionary args, bool acceptSecrets, bool acceptResources) + { + Variables = variables; + Args = args; + AcceptSecrets = acceptSecrets; + AcceptResources = acceptResources; + } + } + + public sealed class ConfigureResponse + { + public bool AcceptSecrets { get; set; } + public bool SupportsPreview { get; set; } + public bool AcceptResources { get; set; } + public bool AcceptOutputs { get; set; } + } + + public sealed class GetPluginInfoResponse + { + public string? Version { get; set; } + } + + public sealed class CreateRequest + { + public readonly string URN; + public readonly ImmutableDictionary Properties; + public readonly TimeSpan Timeout; + public readonly bool Preview; + + public CreateRequest(string urn, ImmutableDictionary properties, TimeSpan timeout, bool preview) + { + URN = urn; + Properties = properties; + Timeout = timeout; + Preview = preview; + } + } + + public sealed class CreateResponse + { + public string? ID { get; set; } + public IDictionary? Properties { get; set; } + } + + public sealed class ReadRequest { + public readonly string ID; + public readonly string URN; + public readonly ImmutableDictionary Properties; + public readonly ImmutableDictionary Inputs; + + public ReadRequest(string id, string urn, ImmutableDictionary properties, ImmutableDictionary inputs) { + ID = id; + URN = urn; + Properties = properties; + Inputs = inputs; + } + } + + public sealed class ReadResponse { + public string? ID {get;set;} + public IDictionary? Properties {get;set;} + public IDictionary? Inputs{get;set;} + } + + public sealed class UpdateRequest { + public readonly string ID; + public readonly string URN; + public readonly ImmutableDictionary Olds; + public readonly ImmutableDictionary News; + public readonly TimeSpan Timeout; + public readonly ImmutableArray IgnoreChanges; + public readonly bool Preview; + + public UpdateRequest(string id, string urn, ImmutableDictionary olds, ImmutableDictionary news, TimeSpan timeout, ImmutableArray ignoreChanges, bool preview) { + ID = id; + URN = urn; + Olds = olds; + News = news; + Timeout = timeout; + IgnoreChanges = ignoreChanges; + Preview = preview; + } + } + + public sealed class UpdateResponse { + public IDictionary? Properties {get;set;} + } + + public sealed class DeleteRequest { + public readonly string ID; + public readonly string URN; + public readonly ImmutableDictionary Properties; + public readonly TimeSpan Timeout; + + public DeleteRequest(string id, string urn, ImmutableDictionary properties, TimeSpan timeout) { + ID = id; + URN = urn; + Properties = properties; + Timeout = timeout; + } + } + + public sealed class ConstructRequest { + public readonly string Name; + public readonly string Type; + public readonly ImmutableDictionary Inputs; + public readonly ComponentResourceOptions Options; + + public ConstructRequest(string name, string type, ImmutableDictionary inputs, ComponentResourceOptions options) { + Name = name; + Type = type; + Inputs = inputs; + Options = options; + } + } + + public sealed class ConstructResponse { + public string? Urn {get;set;} + public IDictionary? Properties {get;set;} + } + + public sealed class CallRequest { + public readonly string Tok; + public readonly ImmutableDictionary Args; + + public CallRequest(string tok, ImmutableDictionary args) + { + Tok = tok; + Args = args; + } + } + + public sealed class CallResponse { + public IDictionary? Properties {get;set;} + } + + public abstract class Provider + { + public virtual Task CheckConfig(CheckRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task DiffConfig(DiffRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Invoke(InvokeRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task GetSchema(GetSchemaRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Configure(ConfigureRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task GetPluginInfo(CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Cancel(CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Create(CreateRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Read(ReadRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Check(CheckRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Diff(DiffRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Update(UpdateRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Delete(DeleteRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Construct(ConstructRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public virtual Task Call(CallRequest request, CancellationToken ct) + { + throw new NotImplementedException(); + } + + public async Task Serve(string[] args, System.Threading.CancellationToken cancellationToken) + { + // maxRpcMessageSize raises the gRPC Max message size from `4194304` (4mb) to `419430400` (400mb) + var maxRpcMessageSize = 400 * 1024 * 1024; + + var host = Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .ConfigureKestrel(kestrelOptions => + { + kestrelOptions.Listen(IPAddress.Any, 0, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + }) + .ConfigureAppConfiguration((context, config) => + { + // clear so we don't read appsettings.json + // note that we also won't read environment variables for config + config.Sources.Clear(); + }) + .ConfigureLogging(loggingBuilder => + { + // disable default logging + loggingBuilder.ClearProviders(); + }) + .ConfigureServices(services => + { + // to be injected into ResourceProviderService + services.AddSingleton(this); + + services.AddGrpc(grpcOptions => + { + grpcOptions.MaxReceiveMessageSize = maxRpcMessageSize; + grpcOptions.MaxSendMessageSize = maxRpcMessageSize; + }); + }) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapGrpcService(); + }); + }); + }) + .Build(); + + // before starting the host, set up this callback to tell us what port was selected + var portTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var portRegistration = host.Services.GetRequiredService().ApplicationStarted.Register(() => + { + try + { + var serverFeatures = host.Services.GetRequiredService().Features; + var addresses = serverFeatures.Get().Addresses.ToList(); + Debug.Assert(addresses.Count == 1, "Server should only be listening on one address"); + var uri = new Uri(addresses[0]); + portTcs.TrySetResult(uri.Port); + } + catch (Exception ex) + { + portTcs.TrySetException(ex); + } + }); + + await host.StartAsync(cancellationToken); + + var port = await portTcs.Task; + System.Console.WriteLine(port.ToString()); + + await host.WaitForShutdownAsync(cancellationToken); + + host.Dispose(); + } + } + + class ResourceProviderService : ResourceProvider.ResourceProviderBase + { + readonly Provider implementation; + + public ResourceProviderService(Provider implementation) + { + this.implementation = implementation; + } + + public override async Task CheckConfig(Pulumirpc.CheckRequest request, ServerCallContext context) + { + try + { + var domRequest = new CheckRequest(request.Urn, PropertyValue.Marshal(request.Olds), PropertyValue.Marshal(request.News), ImmutableArray.ToImmutableArray(request.RandomSeed)); + var domResponse = await this.implementation.CheckConfig(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.CheckResponse(); + grpcResponse.Inputs = domResponse.Inputs == null ? null : PropertyValue.Unmarshal(domResponse.Inputs); + if (domResponse.Failures != null) + { + foreach (var domFailure in domResponse.Failures) + { + var grpcFailure = new Pulumirpc.CheckFailure(); + grpcFailure.Property = domFailure.Property; + grpcFailure.Reason = domFailure.Reason; + grpcResponse.Failures.Add(grpcFailure); + } + } + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task DiffConfig(Pulumirpc.DiffRequest request, ServerCallContext context) + { + try + { + var domRequest = new DiffRequest(request.Urn, request.Id, PropertyValue.Marshal(request.Olds), PropertyValue.Marshal(request.News), request.IgnoreChanges.ToImmutableArray()); + var domResponse = await this.implementation.DiffConfig(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.DiffResponse(); + if (domResponse.Changes.HasValue) + { + grpcResponse.Changes = domResponse.Changes.Value ? Pulumirpc.DiffResponse.Types.DiffChanges.DiffSome : Pulumirpc.DiffResponse.Types.DiffChanges.DiffNone; + } + if (domResponse.Stables != null) + { + grpcResponse.Stables.AddRange(domResponse.Stables); + } + if (domResponse.Replaces != null) + { + grpcResponse.Replaces.AddRange(domResponse.Replaces); + } + grpcResponse.DeleteBeforeReplace = domResponse.DeleteBeforeReplace; + if (domResponse.Diffs != null) + { + grpcResponse.Diffs.AddRange(domResponse.Diffs); + } + if (domResponse.DetailedDiff != null) + { + foreach (var item in domResponse.DetailedDiff) + { + var domDiff = item.Value; + var grpcDiff = new Pulumirpc.PropertyDiff(); + grpcDiff.InputDiff = domDiff.InputDiff; + grpcDiff.Kind = (Pulumirpc.PropertyDiff.Types.Kind)domDiff.Kind; + grpcResponse.DetailedDiff.Add(item.Key, grpcDiff); + } + } + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Invoke(Pulumirpc.InvokeRequest request, ServerCallContext context) + { + try + { + var domRequest = new InvokeRequest(request.Tok, PropertyValue.Marshal(request.Args)); + var domResponse = await this.implementation.Invoke(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.InvokeResponse(); + grpcResponse.Return = domResponse.Return == null ? null : PropertyValue.Unmarshal(domResponse.Return); + if (domResponse.Failures != null) + { + foreach (var domFailure in domResponse.Failures) + { + var grpcFailure = new Pulumirpc.CheckFailure(); + grpcFailure.Property = domFailure.Property; + grpcFailure.Reason = domFailure.Reason; + grpcResponse.Failures.Add(grpcFailure); + } + } + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task GetSchema(Pulumirpc.GetSchemaRequest request, ServerCallContext context) + { + try + { + var domRequest = new GetSchemaRequest(request.Version); + var domResponse = await this.implementation.GetSchema(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.GetSchemaResponse(); + grpcResponse.Schema = domResponse.Schema ?? ""; + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Configure(Pulumirpc.ConfigureRequest request, ServerCallContext context) + { + try + { + var domRequest = new ConfigureRequest(request.Variables.ToImmutableDictionary(), PropertyValue.Marshal(request.Args), request.AcceptSecrets, request.AcceptResources); + var domResponse = await this.implementation.Configure(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.ConfigureResponse(); + grpcResponse.AcceptSecrets = domResponse.AcceptSecrets; + grpcResponse.SupportsPreview = domResponse.SupportsPreview; + grpcResponse.AcceptResources = domResponse.AcceptResources; + grpcResponse.AcceptOutputs = domResponse.AcceptOutputs; + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task GetPluginInfo(Empty request, ServerCallContext context) + { + try + { + var domResponse = await this.implementation.GetPluginInfo(context.CancellationToken); + var grpcResponse = new Pulumirpc.PluginInfo(); + grpcResponse.Version = domResponse.Version ?? ""; + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Cancel(Empty request, ServerCallContext context) + { + try + { + await this.implementation.Cancel(context.CancellationToken); + return new Empty(); + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Create(Pulumirpc.CreateRequest request, ServerCallContext context) + { + try + { + var domRequest = new CreateRequest(request.Urn, PropertyValue.Marshal(request.Properties), TimeSpan.FromSeconds(request.Timeout), request.Preview); + var domResponse = await this.implementation.Create(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.CreateResponse(); + grpcResponse.Id = domResponse.ID ?? ""; + grpcResponse.Properties = domResponse.Properties == null ? null : PropertyValue.Unmarshal(domResponse.Properties); + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Read(Pulumirpc.ReadRequest request, ServerCallContext context) + { + try + { + var domRequest = new ReadRequest(request.Id, request.Urn, PropertyValue.Marshal(request.Properties), PropertyValue.Marshal(request.Inputs)); + var domResponse = await this.implementation.Read(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.ReadResponse(); + grpcResponse.Id = domResponse.ID ?? ""; + grpcResponse.Properties = domResponse.Properties == null ? null : PropertyValue.Unmarshal(domResponse.Properties); + grpcResponse.Inputs = domResponse.Inputs == null ? null : PropertyValue.Unmarshal(domResponse.Inputs); + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Check(Pulumirpc.CheckRequest request, ServerCallContext context) + { + try + { + var domRequest = new CheckRequest(request.Urn, PropertyValue.Marshal(request.Olds), PropertyValue.Marshal(request.News), ImmutableArray.ToImmutableArray(request.RandomSeed)); + var domResponse = await this.implementation.Check(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.CheckResponse(); + grpcResponse.Inputs = domResponse.Inputs == null ? null : PropertyValue.Unmarshal(domResponse.Inputs); + if (domResponse.Failures != null) + { + foreach (var domFailure in domResponse.Failures) + { + var grpcFailure = new Pulumirpc.CheckFailure(); + grpcFailure.Property = domFailure.Property; + grpcFailure.Reason = domFailure.Reason; + grpcResponse.Failures.Add(grpcFailure); + } + } + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Diff(Pulumirpc.DiffRequest request, ServerCallContext context) + { + try + { + var domRequest = new DiffRequest(request.Urn, request.Id, PropertyValue.Marshal(request.Olds), PropertyValue.Marshal(request.News), request.IgnoreChanges.ToImmutableArray()); + var domResponse = await this.implementation.Diff(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.DiffResponse(); + if (domResponse.Changes.HasValue) + { + grpcResponse.Changes = domResponse.Changes.Value ? Pulumirpc.DiffResponse.Types.DiffChanges.DiffSome : Pulumirpc.DiffResponse.Types.DiffChanges.DiffNone; + } + if (domResponse.Stables != null) + { + grpcResponse.Stables.AddRange(domResponse.Stables); + } + if (domResponse.Replaces != null) + { + grpcResponse.Replaces.AddRange(domResponse.Replaces); + } + grpcResponse.DeleteBeforeReplace = domResponse.DeleteBeforeReplace; + if (domResponse.Diffs != null) + { + grpcResponse.Diffs.AddRange(domResponse.Diffs); + } + if (domResponse.DetailedDiff != null) + { + foreach (var item in domResponse.DetailedDiff) + { + var domDiff = item.Value; + var grpcDiff = new Pulumirpc.PropertyDiff(); + grpcDiff.InputDiff = domDiff.InputDiff; + grpcDiff.Kind = (Pulumirpc.PropertyDiff.Types.Kind)domDiff.Kind; + grpcResponse.DetailedDiff.Add(item.Key, grpcDiff); + } + } + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Update(Pulumirpc.UpdateRequest request, ServerCallContext context) + { + try + { + var domRequest = new UpdateRequest(request.Urn, request.Id, PropertyValue.Marshal(request.Olds), PropertyValue.Marshal(request.News), TimeSpan.FromSeconds(request.Timeout), request.IgnoreChanges.ToImmutableArray(), request.Preview); + var domResponse = await this.implementation.Update(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.UpdateResponse(); + grpcResponse.Properties = domResponse.Properties == null ? null : PropertyValue.Unmarshal(domResponse.Properties); + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override async Task Delete(Pulumirpc.DeleteRequest request, ServerCallContext context) + { + try + { + var domRequest = new DeleteRequest(request.Urn, request.Id, PropertyValue.Marshal(request.Properties), TimeSpan.FromSeconds(request.Timeout)); + await this.implementation.Delete(domRequest, context.CancellationToken); + return new Empty(); + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + //function configureRuntime(req: any, engineAddr: string | undefined) { + // // NOTE: these are globals! We should ensure that all settings are identical between calls, and eventually + // // refactor so we can avoid the global state. + // if (engineAddr === undefined) { + // throw new Error("fatal: Missing address"); + // } + + // settings.resetOptions(req.getProject(), req.getStack(), req.getParallel(), engineAddr, + // req.getMonitorendpoint(), req.getDryrun(), req.getOrganization()); + + // const pulumiConfig: {[key: string]: string} = {}; + // const rpcConfig = req.getConfigMap(); + // if (rpcConfig) { + // for (const [k, v] of rpcConfig.entries()) { + // pulumiConfig[k] = v; + // } + // } + // config.setAllConfig(pulumiConfig, req.getConfigsecretkeysList()); + //} + + public override async Task Construct(Pulumirpc.ConstructRequest request, ServerCallContext context) + { + try + { + // ConfigureRuntime + var deployment = new Deployment(new Deployment.RunnerOptions(){ + + }); + + + + // Rebuild the resource options. + // const dependsOn: resource.Resource[] = []; + // for (const urn of req.getDependenciesList()) { + // dependsOn.push(new resource.DependencyResource(urn)); + // } + // const providers: Record = {}; + // const rpcProviders = req.getProvidersMap(); + // if (rpcProviders) { + // for (const [pkg, ref] of rpcProviders.entries()) { + // providers[pkg] = createProviderResource(ref); + // } + // } + // const opts: resource.ComponentResourceOptions = { + // aliases: req.getAliasesList(), + // dependsOn: dependsOn, + // protect: req.getProtect(), + // providers: providers, + // parent: req.getParent() ? new resource.DependencyResource(req.getParent()) : undefined, + // }; + + var domRequest = new ConstructRequest(request.Name, request.Type, PropertyValue.Marshal(request.Inputs), opts) + var domResponse = await this.implementation.Construct(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.ConstructResponse(); + grpcResponse.Properties = domResponse.Properties == null ? null : PropertyValue.Unmarshal(domResponse.Properties); + return grpcResponse; + // // given that construct calls are serialized, we can attach an uncaught handler to pick up exceptions + // // in underlying user code. When we catch the error, we need to respond to the gRPC request with the error + // // to avoid a hang. + // const uncaughtHandler = (err: Error) => { + // if (!this.uncaughtErrors.has(err)) { + // this.uncaughtErrors.add(err); + // } + // // bubble the uncaught error in the user code back and terminate the outstanding gRPC request. + // callback(err, undefined); + // }; + // process.on("uncaughtException", uncaughtHandler); + // // @ts-ignore 'unhandledRejection' will almost always invoke uncaughtHandler with an Error. so + // // just suppress the TS strictness here. + // process.on("unhandledRejection", uncaughtHandler); + // try { + // const req: any = call.request; + // const type = req.getType(); + // const name = req.getName(); + + // if (!this.provider.construct) { + // callback(new Error(`unknown resource type ${type}`), undefined); + // return; + // } + + // configureRuntime(req, this.engineAddr); + + // const inputs = await deserializeInputs(req.getInputs(), req.getInputdependenciesMap()); + + // // Rebuild the resource options. + // const dependsOn: resource.Resource[] = []; + // for (const urn of req.getDependenciesList()) { + // dependsOn.push(new resource.DependencyResource(urn)); + // } + // const providers: Record = {}; + // const rpcProviders = req.getProvidersMap(); + // if (rpcProviders) { + // for (const [pkg, ref] of rpcProviders.entries()) { + // providers[pkg] = createProviderResource(ref); + // } + // } + // const opts: resource.ComponentResourceOptions = { + // aliases: req.getAliasesList(), + // dependsOn: dependsOn, + // protect: req.getProtect(), + // providers: providers, + // parent: req.getParent() ? new resource.DependencyResource(req.getParent()) : undefined, + // }; + + // const result = await this.provider.construct(name, type, inputs, opts); + + // const resp = new provproto.ConstructResponse(); + + // resp.setUrn(await output(result.urn).promise()); + + // const [state, stateDependencies] = await rpc.serializeResourceProperties(`construct(${type}, ${name})`, result.state); + // const stateDependenciesMap = resp.getStatedependenciesMap(); + // for (const [key, resources] of stateDependencies) { + // const deps = new provproto.ConstructResponse.PropertyDependencies(); + // deps.setUrnsList(await Promise.all(Array.from(resources).map(r => r.urn.promise()))); + // stateDependenciesMap.set(key, deps); + // } + // resp.setState(structproto.Struct.fromJavaScript(state)); + + // // Wait for RPC operations to complete. + // await settings.waitForRPCs(); + + // callback(undefined, resp); + // } catch (e) { + // console.error(`${e}: ${e.stack}`); + // callback(e, undefined); + // } finally { + // // remove these uncaught handlers that are specific to this gRPC callback context + // process.off("uncaughtException", uncaughtHandler); + // process.off("unhandledRejection", uncaughtHandler); + // } + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + + public override Task Call(Pulumirpc.CallRequest request, ServerCallContext context) + { + try + { + var domRequest = new CallRequest(request.Urn, request.Id, PropertyValue.Marshal(request.Properties), TimeSpan.FromSeconds(request.Timeout)); + var domResponse = await this.implementation.Call(domRequest, context.CancellationToken); + var grpcResponse = new Pulumirpc.CallResponse(); + grpcResponse.Properties = domResponse.Properties == null ? null : PropertyValue.Unmarshal(domResponse.Properties); + return grpcResponse; + } + catch (NotImplementedException ex) + { + throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message)); + } + catch (System.Threading.Tasks.TaskCanceledException ex) + { + throw new RpcException(new Status(StatusCode.Cancelled, ex.Message)); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); + } + } + } +} \ No newline at end of file diff --git a/sdk/dotnet/Pulumi/Pulumi.csproj b/sdk/dotnet/Pulumi/Pulumi.csproj index 7c4733e5a6aa..605404949cf8 100644 --- a/sdk/dotnet/Pulumi/Pulumi.csproj +++ b/sdk/dotnet/Pulumi/Pulumi.csproj @@ -32,6 +32,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive