diff --git a/sdk/dotnet/Pulumi/Provider/Host.cs b/sdk/dotnet/Pulumi/Provider/Host.cs
new file mode 100644
index 000000000000..301f2adc1e4d
--- /dev/null
+++ b/sdk/dotnet/Pulumi/Provider/Host.cs
@@ -0,0 +1,112 @@
+using Pulumirpc;
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using System.Collections.Concurrent;
+using Grpc.Net.Client;
+
+namespace Pulumi.Provider
+{
+ public sealed class LogMessage
+ {
+ ///
+ /// the logging level of this message.
+ ///
+ public LogLevel Severity;
+
+ ///
+ /// the contents of the logged message.
+ ///
+ public string Message;
+
+ ///
+ /// the (optional) resource urn this log is associated with.
+ ///
+ public string? Urn;
+
+
+ ///
+ /// the (optional) stream id that a stream of log messages can be associated with. This allows
+ /// clients to not have to buffer a large set of log messages that they all want to be
+ /// conceptually connected. Instead the messages can be sent as chunks (with the same stream id)
+ /// and the end display can show the messages as they arrive, while still stitching them together
+ /// into one total log message.
+ ///
+ /// 0/not-given means: do not associate with any stream.
+ ///
+ public int StreamId;
+
+ ///
+ /// Optional value indicating whether this is a status message.
+ ///
+ public bool Ephemeral;
+
+ public LogMessage(string message)
+ {
+ Message = message;
+ }
+ }
+
+ public interface IHost
+ {
+ public Task LogAsync(LogMessage message);
+
+ }
+ internal class GrpcHost : IHost
+ {
+ private readonly Engine.EngineClient _engine;
+ // Using a static dictionary to keep track of and re-use gRPC channels
+ // According to the docs (https://docs.microsoft.com/en-us/aspnet/core/grpc/performance?view=aspnetcore-6.0#reuse-grpc-channels), creating GrpcChannels is expensive so we keep track of a bunch of them here
+ private static readonly ConcurrentDictionary _engineChannels = new ConcurrentDictionary();
+ private static readonly object _channelsLock = new object();
+ public GrpcHost(string engineAddress)
+ {
+ // Allow for insecure HTTP/2 transport (only needed for netcoreapp3.x)
+ // https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-6.0#call-insecure-grpc-services-with-net-core-client
+ AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
+ // maxRpcMessageSize raises the gRPC Max Message size from `4194304` (4mb) to `419430400` (400mb)
+ const int maxRpcMessageSize = 400 * 1024 * 1024;
+ if (_engineChannels.TryGetValue(engineAddress, out var engineChannel))
+ {
+ // A channel already exists for this address
+ this._engine = new Engine.EngineClient(engineChannel);
+ }
+ else
+ {
+ lock (_channelsLock)
+ {
+ if (_engineChannels.TryGetValue(engineAddress, out var existingChannel))
+ {
+ // A channel already exists for this address
+ this._engine = new Engine.EngineClient(existingChannel);
+ }
+ else
+ {
+ // Inititialize the engine channel once for this address
+ var channel = GrpcChannel.ForAddress(new Uri($"http://{engineAddress}"), new GrpcChannelOptions
+ {
+ MaxReceiveMessageSize = maxRpcMessageSize,
+ MaxSendMessageSize = maxRpcMessageSize,
+ Credentials = Grpc.Core.ChannelCredentials.Insecure,
+ });
+
+ _engineChannels[engineAddress] = channel;
+ this._engine = new Engine.EngineClient(channel);
+ }
+ }
+ }
+ }
+
+ public async Task LogAsync(LogMessage message)
+ {
+ var request = new LogRequest();
+ request.Message = message.Message;
+ request.Ephemeral = message.Ephemeral;
+ request.Urn = message.Urn;
+ request.Severity = (LogSeverity)message.Severity;
+ request.StreamId = message.StreamId;
+ await this._engine.LogAsync(request);
+ }
+ }
+
+}
diff --git a/sdk/dotnet/Pulumi/Provider/Provider.cs b/sdk/dotnet/Pulumi/Provider/Provider.cs
new file mode 100644
index 000000000000..d88538c61319
--- /dev/null
+++ b/sdk/dotnet/Pulumi/Provider/Provider.cs
@@ -0,0 +1,1335 @@
+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;
+using Microsoft.Extensions.Configuration;
+
+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 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 static async Task Serve(string[] args, Func factory, 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();
+
+ var hostConfig = new Dictionary();
+ if (args.Length > 0) {
+ hostConfig.Add("Host", args[0]);
+ }
+ config.AddInMemoryCollection(hostConfig);
+ })
+ .ConfigureLogging(loggingBuilder =>
+ {
+ // disable default logging
+ loggingBuilder.ClearProviders();
+ })
+ .ConfigureServices(services =>
+ {
+ // to be injected into ResourceProviderService
+ services.AddSingleton(factory);
+
+ 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 Func factory;
+ Provider? implementation;
+
+ Provider Implementation
+ {
+ get
+ {
+ if (implementation == null)
+ {
+ throw new RpcException(new Status(StatusCode.FailedPrecondition, "Engine host not yet attached"));
+ }
+ return implementation;
+ }
+ }
+
+ private void CreateProvider(string address)
+ {
+ var host = new GrpcHost(address);
+ implementation = factory(host);
+ }
+
+ public ResourceProviderService(Func factory, IConfiguration configuration)
+ {
+ this.factory = factory;
+
+ var host = configuration.GetValue("Host", null);
+ if (host != null)
+ {
+ CreateProvider(host);
+ }
+ }
+
+ public override Task Attach(Pulumirpc.PluginAttach request, ServerCallContext context)
+ {
+ CreateProvider(request.Address);
+ return Task.FromResult(new Empty());
+ }
+
+ 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 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 (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 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 (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 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 (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 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 (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 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 (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 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 (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 Implementation.Cancel(context.CancellationToken);
+ return new Empty();
+ }
+ catch (NotImplementedException ex)
+ {
+ throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message));
+ }
+ catch (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 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 (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 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 (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 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 (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 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 (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 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 (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 Implementation.Delete(domRequest, context.CancellationToken);
+ return new Empty();
+ }
+ catch (NotImplementedException ex)
+ {
+ throw new RpcException(new Status(StatusCode.Unimplemented, ex.Message));
+ }
+ catch (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 Construct(Pulumirpc.ConstructRequest request, ServerCallContext context)
+ {
+ throw new RpcException(new Status(StatusCode.Unimplemented, "Component resources not yet supported"));
+ }
+
+ public override Task Call(Pulumirpc.CallRequest request, ServerCallContext context)
+ {
+ throw new RpcException(new Status(StatusCode.Unimplemented, "Component resources not yet supported"));
+ }
+ }
+}
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
diff --git a/sdk/dotnet/cmd/pulumi-language-dotnet/main.go b/sdk/dotnet/cmd/pulumi-language-dotnet/main.go
index 5838362336ff..5c50e1b23958 100644
--- a/sdk/dotnet/cmd/pulumi-language-dotnet/main.go
+++ b/sdk/dotnet/cmd/pulumi-language-dotnet/main.go
@@ -810,5 +810,74 @@ func (host *dotnetLanguageHost) GetProgramDependencies(
func (host *dotnetLanguageHost) RunPlugin(
req *pulumirpc.RunPluginRequest, server pulumirpc.LanguageRuntime_RunPluginServer) error {
- return errors.New("not supported")
+ logging.V(5).Infof("Attempting to run dotnet plugin in %s", req.Program)
+
+ closer, stdout, stderr, err := rpcutil.MakeRunPluginStreams(server, false)
+ if err != nil {
+ return err
+ }
+ // best effort close, but we try an explicit close and error check at the end as well
+ defer closer.Close()
+
+ executable := host.exec
+ args := []string{}
+
+ switch {
+ case host.binary != "" && strings.HasSuffix(host.binary, ".dll"):
+ // Portable pre-compiled dll: run `dotnet .dll`
+ args = append(args, host.binary)
+ case host.binary != "":
+ // Self-contained executable: run it directly.
+ executable = host.binary
+ default:
+ // Run from source.
+ args = append(args, "run")
+
+ // If we are certain the project has been built,
+ // passing a --no-build flag to dotnet run results in
+ // up to 1s time savings.
+ if host.dotnetBuildSucceeded {
+ args = append(args, "--no-build")
+ }
+
+ if req.Program != "" {
+ args = append(args, req.Program)
+ }
+ }
+
+ if logging.V(5) {
+ commandStr := strings.Join(args, " ")
+ logging.V(5).Infoln("Language host launching process: ", host.exec, commandStr)
+ }
+
+ // Now simply spawn a process to execute the requested program, wiring up stdout/stderr directly.
+ cmd := exec.Command(executable, req.Args...) // nolint: gas // intentionally running dynamic program name.
+ cmd.Dir = req.Pwd
+ cmd.Env = req.Env
+ cmd.Stdout, cmd.Stderr = stdout, stderr
+ if err := cmd.Run(); err != nil {
+ if exiterr, ok := err.(*exec.ExitError); ok {
+ // If the program ran, but exited with a non-zero error code. This will happen often, since user
+ // errors will trigger this. So, the error message should look as nice as possible.
+ if status, stok := exiterr.Sys().(syscall.WaitStatus); stok {
+ err = errors.Errorf("Program exited with non-zero exit code: %d", status.ExitStatus())
+ } else {
+ err = errors.Wrapf(exiterr, "Program exited unexpectedly")
+ }
+ } else {
+ // Otherwise, we didn't even get to run the program. This ought to never happen unless there's
+ // a bug or system condition that prevented us from running the language exec. Issue a scarier error.
+ err = errors.Wrapf(err, "Problem executing plugin program (could not run language executor)")
+ }
+ }
+
+ if err != nil {
+ return err
+ }
+
+ if err := closer.Close(); err != nil {
+ return err
+ }
+
+ return nil
}
diff --git a/tests/integration/integration_dotnet_test.go b/tests/integration/integration_dotnet_test.go
index 680a94c4c6f7..c0695d4f4e7d 100644
--- a/tests/integration/integration_dotnet_test.go
+++ b/tests/integration/integration_dotnet_test.go
@@ -523,3 +523,10 @@ func TestAboutDotnet(t *testing.T) {
// This one doesn't have a current stack. Assert that we caught it.
assert.Contains(t, stderr, "No current stack")
}
+
+func TestProviderDotnet(t *testing.T) {
+ integration.ProgramTest(t, &integration.ProgramTestOptions{
+ Dependencies: []string{"Pulumi"},
+ Dir: filepath.Join("provider", "dotnet"),
+ })
+}
diff --git a/tests/integration/provider/dotnet/.gitignore b/tests/integration/provider/dotnet/.gitignore
new file mode 100644
index 000000000000..e645270662b4
--- /dev/null
+++ b/tests/integration/provider/dotnet/.gitignore
@@ -0,0 +1,353 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
diff --git a/tests/integration/provider/dotnet/MyStack.cs b/tests/integration/provider/dotnet/MyStack.cs
new file mode 100644
index 000000000000..db9ecd8e8332
--- /dev/null
+++ b/tests/integration/provider/dotnet/MyStack.cs
@@ -0,0 +1,11 @@
+// Copyright 2016-2020, Pulumi Corporation. All rights reserved.
+
+using Pulumi;
+
+class MyStack : Stack
+{
+ public MyStack()
+ {
+ var customA = new TestResource("a", new TestResourceArgs { Echo = 42 });
+ }
+}
diff --git a/tests/integration/provider/dotnet/Program.cs b/tests/integration/provider/dotnet/Program.cs
new file mode 100644
index 000000000000..2cb5c6c691e4
--- /dev/null
+++ b/tests/integration/provider/dotnet/Program.cs
@@ -0,0 +1,7 @@
+using System.Threading.Tasks;
+using Pulumi;
+
+class Program
+{
+ static Task Main() => Deployment.RunAsync();
+}
diff --git a/tests/integration/provider/dotnet/Provider.csproj b/tests/integration/provider/dotnet/Provider.csproj
new file mode 100644
index 000000000000..1d22a3699707
--- /dev/null
+++ b/tests/integration/provider/dotnet/Provider.csproj
@@ -0,0 +1,9 @@
+
+
+
+ Exe
+ net6.0
+ enable
+
+
+
diff --git a/tests/integration/provider/dotnet/Pulumi.yaml b/tests/integration/provider/dotnet/Pulumi.yaml
new file mode 100644
index 000000000000..06d8fd467f75
--- /dev/null
+++ b/tests/integration/provider/dotnet/Pulumi.yaml
@@ -0,0 +1,7 @@
+name: provider_dotnet
+description: A program that constructs remote custom resources.
+runtime: dotnet
+plugins:
+ providers:
+ - name: testprovider
+ path: ./testprovider
\ No newline at end of file
diff --git a/tests/integration/provider/dotnet/TestResource.cs b/tests/integration/provider/dotnet/TestResource.cs
new file mode 100644
index 000000000000..eaa2663e2cf6
--- /dev/null
+++ b/tests/integration/provider/dotnet/TestResource.cs
@@ -0,0 +1,18 @@
+using Pulumi;
+
+class TestResourceArgs : Pulumi.ResourceArgs
+{
+ [Input("echo")]
+ public Input