// Copyright 2016-2019, Pulumi Corporation using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Pulumi.Testing; namespace Pulumi { public partial class Deployment { /// /// for more details. /// /// Callback that creates stack resources. public static Task RunAsync(Action action) => RunAsync(() => { action(); return ImmutableDictionary.Empty; }); /// /// for more details. /// /// Callback that creates stack resources. /// A dictionary of stack outputs. public static Task RunAsync(Func> func) => RunAsync(() => Task.FromResult(func())); /// /// for more details. /// /// Callback that creates stack resources. public static Task RunAsync(Func func) => RunAsync(async () => { await func().ConfigureAwait(false); return ImmutableDictionary.Empty; }); /// /// is an /// entry-point to a Pulumi application. .NET applications should perform all startup logic /// they need in their Main method and then end with: /// /// /// static Task<int> Main(string[] args) /// { /// // program initialization code ... /// /// return Deployment.Run(async () => /// { /// // Code that creates resources. /// }); /// } /// /// /// Importantly: Cloud resources cannot be created outside of the lambda passed to any of the /// overloads. Because cloud Resource construction is /// inherently asynchronous, the result of this function is a which should /// then be returned or awaited. This will ensure that any problems that are encountered during /// the running of the program are properly reported. Failure to do this may lead to the /// program ending early before all resources are properly registered. /// /// The function passed to /// can optionally return an . The keys and values /// in this dictionary will become the outputs for the Pulumi Stack that is created. /// /// Callback that creates stack resources. /// Stack options. public static Task RunAsync(Func>> func, StackOptions? options = null) => CreateRunnerAndRunAsync(() => new Deployment(), runner => runner.RunAsync(func, options)); /// /// is an entry-point to a Pulumi /// application. .NET applications should perform all startup logic they /// need in their Main method and then end with: /// /// /// static Task<int> Main(string[] args) {// program /// initialization code ... /// /// return Deployment.Run<MyStack>();} /// /// /// /// Deployment will instantiate a new stack instance based on the type /// passed as TStack type parameter. Importantly, cloud resources cannot /// be created outside of the component. /// /// /// Because cloud Resource construction is inherently asynchronous, the /// result of this function is a which should then /// be returned or awaited. This will ensure that any problems that are /// encountered during the running of the program are properly reported. /// Failure to do this may lead to the program ending early before all /// resources are properly registered. /// /// public static Task RunAsync() where TStack : Stack, new() => CreateRunnerAndRunAsync(() => new Deployment(), runner => runner.RunAsync()); /// /// is an entry-point to a Pulumi /// application. .NET applications should perform all startup logic they /// need in their Main method and then end with: /// /// /// static Task<int> Main(string[] args) {// program /// initialization code ... /// /// return Deployment.Run<MyStack>(serviceProvider);} /// /// /// /// Deployment will instantiate a new stack instance based on the type /// passed as TStack type parameter using the serviceProvider. /// Importantly, cloud resources cannot be created outside of the /// component. /// /// /// Because cloud Resource construction is inherently asynchronous, the /// result of this function is a which should then /// be returned or awaited. This will ensure that any problems that are /// encountered during the running of the program are properly reported. /// Failure to do this may lead to the program ending early before all /// resources are properly registered. /// /// public static Task RunAsync(IServiceProvider serviceProvider) where TStack : Stack => CreateRunnerAndRunAsync(() => new Deployment(), runner => runner.RunAsync(serviceProvider)); /// /// Entry point to test a Pulumi application. Deployment will /// instantiate a new stack instance based on the type passed as TStack /// type parameter using the given service provider. This method creates /// no real resources. /// Note: Currently, unit tests that call /// /// must run serially; parallel execution is not supported. /// /// Hooks to mock the engine calls. /// /// Optional settings for the test run. /// The type of the stack to test. /// Test result containing created resources and errors, if any. public static Task> TestWithServiceProviderAsync(IMocks mocks, IServiceProvider serviceProvider, TestOptions? options = null) where TStack : Stack => TestAsync(mocks, runner => runner.RunAsync(serviceProvider), options); /// /// Entry point to test a Pulumi application. Deployment will /// instantiate a new stack instance based on the type passed as TStack /// type parameter. This method creates no real resources. /// Note: Currently, unit tests that call /// must run serially; parallel execution is not supported. /// /// Hooks to mock the engine calls. /// Optional settings for the test run. /// The type of the stack to test. /// Test result containing created resources and errors, if any. public static Task> TestAsync(IMocks mocks, TestOptions? options = null) where TStack : Stack, new() => TestAsync(mocks, runner => runner.RunAsync(), options); private static async Task> TestAsync(IMocks mocks, Func> runAsync, TestOptions? options = null) { var result = await TryTestAsync(mocks, runAsync, options); if (result.Exception != null) { throw result.Exception; } return result.Resources; } /// /// Like `TestAsync`, but instead of throwing the errors /// detected in the engine, returns them in the result tuple. /// This enables tests to observe partially constructed /// `Resources` vector in presence of deliberate errors. /// internal static async Task<(ImmutableArray Resources, Exception? Exception)> TryTestAsync( IMocks mocks, Func> runAsync, TestOptions? options = null) { var engine = new MockEngine(); var monitor = new MockMonitor(mocks); await CreateRunnerAndRunAsync(() => new Deployment(engine, monitor, options), runAsync).ConfigureAwait(false); Exception? err = engine.Errors.Count switch { 1 => new RunException(engine.Errors.Single()), var v when v > 1 => new AggregateException(engine.Errors.Select(e => new RunException(e))), _ => null }; return (Resources: monitor.Resources.ToImmutableArray(), Exception: err); } internal static Task<(ImmutableArray Resources, Exception? Exception)> TryTestAsync( IMocks mocks, TestOptions? options = null) where TStack : Stack, new() => TryTestAsync(mocks, runner => runner.RunAsync(), options); // this method *must* remain marked async // in order to protect the scope of the AsyncLocal Deployment.Instance we cannot elide the task (return it early) // if the task is returned early and not awaited, than it is possible for any code that runs before the eventual await // to be executed synchronously and thus have multiple calls to one of the Run methods affecting each others Deployment.Instance internal static async Task CreateRunnerAndRunAsync( Func deploymentFactory, Func> runAsync) { var deployment = deploymentFactory(); Instance = new DeploymentInstance(deployment); return await runAsync(deployment._runner).ConfigureAwait(false); } } }