From d0a7814a44fab2fbe67e3c6536c04719d1671ce0 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") --- sdk/dotnet/Pulumi/Provider/Host.cs | 112 ++ sdk/dotnet/Pulumi/Provider/Provider.cs | 1335 +++++++++++++++++ sdk/dotnet/Pulumi/Pulumi.csproj | 1 + tests/integration/provider/dotnet/.gitignore | 353 +++++ tests/integration/provider/dotnet/MyStack.cs | 13 + tests/integration/provider/dotnet/Program.cs | 7 + .../provider/dotnet/Provider.csproj | 9 + tests/integration/provider/dotnet/Pulumi.yaml | 7 + .../provider/dotnet/testprovider/Program.cs | 9 + .../provider/dotnet/testprovider/Provider.cs | 5 + .../dotnet/testprovider/TestProvider.csproj | 13 + 11 files changed, 1864 insertions(+) create mode 100644 sdk/dotnet/Pulumi/Provider/Host.cs create mode 100644 sdk/dotnet/Pulumi/Provider/Provider.cs create mode 100644 tests/integration/provider/dotnet/.gitignore create mode 100644 tests/integration/provider/dotnet/MyStack.cs create mode 100644 tests/integration/provider/dotnet/Program.cs create mode 100644 tests/integration/provider/dotnet/Provider.csproj create mode 100644 tests/integration/provider/dotnet/Pulumi.yaml create mode 100644 tests/integration/provider/dotnet/testprovider/Program.cs create mode 100644 tests/integration/provider/dotnet/testprovider/Provider.cs create mode 100644 tests/integration/provider/dotnet/testprovider/TestProvider.csproj 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/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..2f59bd6e2a51 --- /dev/null +++ b/tests/integration/provider/dotnet/MyStack.cs @@ -0,0 +1,13 @@ +// Copyright 2016-2020, Pulumi Corporation. All rights reserved. + +using Pulumi; + +class MyStack : Stack +{ + public MyStack() + { + var componentA = new Component("a", new ComponentArgs { Echo = 42 }); + var componentB = new Component("b", new ComponentArgs { Echo = componentA.Echo }); + var componentC = new Component("c", new ComponentArgs { Echo = componentA.ChildId }); + } +} 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/testprovider/Program.cs b/tests/integration/provider/dotnet/testprovider/Program.cs new file mode 100644 index 000000000000..9a0ec6fa7d64 --- /dev/null +++ b/tests/integration/provider/dotnet/testprovider/Program.cs @@ -0,0 +1,9 @@ +using Pulumi.Provider; + + +public static class Program { + public static void Main(string[] args) { + var provider = new Provider(); + + } +} \ No newline at end of file diff --git a/tests/integration/provider/dotnet/testprovider/Provider.cs b/tests/integration/provider/dotnet/testprovider/Provider.cs new file mode 100644 index 000000000000..41fe55513bdc --- /dev/null +++ b/tests/integration/provider/dotnet/testprovider/Provider.cs @@ -0,0 +1,5 @@ +using Pulumi + +public class Provider : Provider { + +} \ No newline at end of file diff --git a/tests/integration/provider/dotnet/testprovider/TestProvider.csproj b/tests/integration/provider/dotnet/testprovider/TestProvider.csproj new file mode 100644 index 000000000000..97a6c5bce478 --- /dev/null +++ b/tests/integration/provider/dotnet/testprovider/TestProvider.csproj @@ -0,0 +1,13 @@ + + + + Exe + net6.0 + enable + + + + + + +