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 + + + + + + +