// 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);
}
}
}