2019-11-21 22:46:14 +01:00
|
|
|
// Copyright 2016-2019, Pulumi Corporation
|
2019-10-26 01:59:50 +02:00
|
|
|
|
|
|
|
using System;
|
2020-10-27 18:12:12 +01:00
|
|
|
using System.Collections.Concurrent;
|
2021-04-09 21:55:34 +02:00
|
|
|
using System.Threading;
|
2019-10-26 01:59:50 +02:00
|
|
|
using System.Threading.Tasks;
|
2020-03-11 23:10:01 +01:00
|
|
|
using Pulumi.Testing;
|
2019-10-26 01:59:50 +02:00
|
|
|
|
|
|
|
namespace Pulumi
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// <see cref="Deployment"/> is the entry-point to a Pulumi application. .NET applications
|
|
|
|
/// should perform all startup logic they need in their <c>Main</c> method and then end with:
|
|
|
|
/// <para>
|
|
|
|
/// <c>
|
|
|
|
/// static Task<int> Main(string[] args)
|
|
|
|
/// {
|
|
|
|
/// // program initialization code ...
|
|
|
|
///
|
|
|
|
/// return Deployment.Run(async () =>
|
|
|
|
/// {
|
|
|
|
/// // Code that creates resources.
|
|
|
|
/// });
|
|
|
|
/// }
|
|
|
|
/// </c>
|
|
|
|
/// </para>
|
|
|
|
/// Importantly: Cloud resources cannot be created outside of the lambda passed to any of the
|
|
|
|
/// <see cref="Deployment.RunAsync(Action)"/> overloads. Because cloud Resource construction is
|
|
|
|
/// inherently asynchronous, the result of this function is a <see cref="Task{T}"/> 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.
|
|
|
|
/// </summary>
|
|
|
|
public sealed partial class Deployment : IDeploymentInternal
|
|
|
|
{
|
2020-03-11 23:10:01 +01:00
|
|
|
private static readonly object _instanceLock = new object();
|
2021-04-09 21:55:34 +02:00
|
|
|
private static AsyncLocal<DeploymentInstance?> _instance = new AsyncLocal<DeploymentInstance?>();
|
2019-10-26 01:59:50 +02:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The current running deployment instance. This is only available from inside the function
|
|
|
|
/// passed to <see cref="Deployment.RunAsync(Action)"/> (or its overloads).
|
|
|
|
/// </summary>
|
2020-04-14 10:30:25 +02:00
|
|
|
public static DeploymentInstance Instance
|
2019-10-26 01:59:50 +02:00
|
|
|
{
|
2021-04-09 21:55:34 +02:00
|
|
|
get => _instance.Value ?? throw new InvalidOperationException("Trying to acquire Deployment.Instance before 'Run' was called.");
|
|
|
|
internal set
|
|
|
|
{
|
|
|
|
lock (_instanceLock)
|
|
|
|
{
|
|
|
|
if (_instance.Value != null)
|
|
|
|
{
|
|
|
|
throw new InvalidOperationException("Deployment.Instance should only be set once at the beginning of a 'Run' call.");
|
|
|
|
}
|
|
|
|
|
|
|
|
_instance.Value = value;
|
|
|
|
}
|
|
|
|
}
|
2019-10-26 01:59:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
internal static IDeploymentInternal InternalInstance
|
2020-04-14 10:30:25 +02:00
|
|
|
=> Instance.Internal;
|
2019-10-26 01:59:50 +02:00
|
|
|
|
2020-12-10 19:21:05 +01:00
|
|
|
private static readonly bool _disableResourceReferences =
|
|
|
|
Environment.GetEnvironmentVariable("PULUMI_DISABLE_RESOURCE_REFERENCES") == "1" ||
|
|
|
|
string.Equals(Environment.GetEnvironmentVariable("PULUMI_DISABLE_RESOURCE_REFERENCES"), "TRUE", StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
2019-10-26 01:59:50 +02:00
|
|
|
private readonly string _projectName;
|
|
|
|
private readonly string _stackName;
|
|
|
|
private readonly bool _isDryRun;
|
2020-10-27 18:12:12 +01:00
|
|
|
private readonly ConcurrentDictionary<string, bool> _featureSupport = new ConcurrentDictionary<string, bool>();
|
2019-10-26 01:59:50 +02:00
|
|
|
|
|
|
|
private readonly ILogger _logger;
|
|
|
|
private readonly IRunner _runner;
|
|
|
|
|
2020-03-11 23:10:01 +01:00
|
|
|
internal IEngine Engine { get; }
|
|
|
|
internal IMonitor Monitor { get; }
|
2019-10-26 01:59:50 +02:00
|
|
|
|
|
|
|
internal Stack? _stack;
|
|
|
|
internal Stack Stack
|
|
|
|
{
|
|
|
|
get => _stack ?? throw new InvalidOperationException("Trying to acquire Deployment.Stack before 'Run' was called.");
|
|
|
|
set => _stack = (value ?? throw new ArgumentNullException(nameof(value)));
|
|
|
|
}
|
|
|
|
|
|
|
|
private Deployment()
|
|
|
|
{
|
|
|
|
var monitor = Environment.GetEnvironmentVariable("PULUMI_MONITOR");
|
|
|
|
var engine = Environment.GetEnvironmentVariable("PULUMI_ENGINE");
|
|
|
|
var project = Environment.GetEnvironmentVariable("PULUMI_PROJECT");
|
|
|
|
var stack = Environment.GetEnvironmentVariable("PULUMI_STACK");
|
|
|
|
var pwd = Environment.GetEnvironmentVariable("PULUMI_PWD");
|
|
|
|
var dryRun = Environment.GetEnvironmentVariable("PULUMI_DRY_RUN");
|
|
|
|
var queryMode = Environment.GetEnvironmentVariable("PULUMI_QUERY_MODE");
|
|
|
|
var parallel = Environment.GetEnvironmentVariable("PULUMI_PARALLEL");
|
|
|
|
var tracing = Environment.GetEnvironmentVariable("PULUMI_TRACING");
|
|
|
|
|
2019-11-09 18:05:24 +01:00
|
|
|
if (string.IsNullOrEmpty(monitor) ||
|
|
|
|
string.IsNullOrEmpty(engine) ||
|
|
|
|
string.IsNullOrEmpty(project) ||
|
|
|
|
string.IsNullOrEmpty(stack) ||
|
|
|
|
!bool.TryParse(dryRun, out var dryRunValue) ||
|
|
|
|
!bool.TryParse(queryMode, out var queryModeValue) ||
|
|
|
|
!int.TryParse(parallel, out var parallelValue))
|
|
|
|
{
|
|
|
|
throw new InvalidOperationException("Program run without the Pulumi engine available; re-run using the `pulumi` CLI");
|
|
|
|
}
|
2019-10-26 01:59:50 +02:00
|
|
|
|
|
|
|
_isDryRun = dryRunValue;
|
|
|
|
_stackName = stack;
|
|
|
|
_projectName = project;
|
|
|
|
|
|
|
|
Serilog.Log.Debug("Creating Deployment Engine.");
|
2020-03-11 23:10:01 +01:00
|
|
|
this.Engine = new GrpcEngine(engine);
|
2019-10-26 01:59:50 +02:00
|
|
|
Serilog.Log.Debug("Created Deployment Engine.");
|
|
|
|
|
|
|
|
Serilog.Log.Debug("Creating Deployment Monitor.");
|
2020-03-11 23:10:01 +01:00
|
|
|
this.Monitor = new GrpcMonitor(monitor);
|
2019-10-26 01:59:50 +02:00
|
|
|
Serilog.Log.Debug("Created Deployment Monitor.");
|
|
|
|
|
|
|
|
_runner = new Runner(this);
|
|
|
|
_logger = new Logger(this, this.Engine);
|
|
|
|
}
|
|
|
|
|
2020-03-11 23:10:01 +01:00
|
|
|
/// <summary>
|
2021-04-09 21:55:34 +02:00
|
|
|
/// This constructor is called from <see cref="TestAsync(IMocks, Func{IRunner, Task{int}}, TestOptions?)"/>
|
|
|
|
/// with a mocked monitor and dummy values for project and stack.
|
|
|
|
/// <para/>
|
|
|
|
/// This constructor is also used in deployment tests in order to
|
|
|
|
/// instantiate mock deployments.
|
2020-03-11 23:10:01 +01:00
|
|
|
/// </summary>
|
2021-04-09 21:55:34 +02:00
|
|
|
internal Deployment(IEngine engine, IMonitor monitor, TestOptions? options)
|
2020-03-11 23:10:01 +01:00
|
|
|
{
|
|
|
|
_isDryRun = options?.IsPreview ?? true;
|
|
|
|
_stackName = options?.StackName ?? "stack";
|
|
|
|
_projectName = options?.ProjectName ?? "project";
|
|
|
|
this.Engine = engine;
|
|
|
|
this.Monitor = monitor;
|
|
|
|
_runner = new Runner(this);
|
|
|
|
_logger = new Logger(this, this.Engine);
|
|
|
|
}
|
|
|
|
|
2019-10-26 01:59:50 +02:00
|
|
|
string IDeployment.ProjectName => _projectName;
|
|
|
|
string IDeployment.StackName => _stackName;
|
|
|
|
bool IDeployment.IsDryRun => _isDryRun;
|
|
|
|
|
|
|
|
ILogger IDeploymentInternal.Logger => _logger;
|
|
|
|
IRunner IDeploymentInternal.Runner => _runner;
|
|
|
|
|
|
|
|
Stack IDeploymentInternal.Stack
|
|
|
|
{
|
|
|
|
get => Stack;
|
|
|
|
set => Stack = value;
|
|
|
|
}
|
2020-10-27 18:12:12 +01:00
|
|
|
|
|
|
|
private async Task<bool> MonitorSupportsFeature(string feature)
|
|
|
|
{
|
|
|
|
if (!this._featureSupport.ContainsKey(feature))
|
|
|
|
{
|
|
|
|
var request = new Pulumirpc.SupportsFeatureRequest {Id = feature };
|
|
|
|
var response = await this.Monitor.SupportsFeatureAsync(request).ConfigureAwait(false);
|
|
|
|
this._featureSupport[feature] = response.HasSupport;
|
|
|
|
}
|
|
|
|
return this._featureSupport[feature];
|
|
|
|
}
|
|
|
|
|
|
|
|
internal Task<bool> MonitorSupportsResourceReferences()
|
|
|
|
{
|
|
|
|
return MonitorSupportsFeature("resourceReferences");
|
|
|
|
}
|
2019-10-26 01:59:50 +02:00
|
|
|
}
|
|
|
|
}
|