// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Pulumi.Serialization;
namespace Pulumi
{
///
/// Stack is the root resource for a Pulumi stack. Derive from this class to create your
/// stack definitions.
///
public class Stack : ComponentResource
{
///
/// Constant to represent the 'root stack' resource for a Pulumi application. The purpose
/// of this is solely to make it easy to write an like so:
///
/// aliases = { new Alias { Parent = Pulumi.Stack.Root } }
///
/// This indicates that the prior name for a resource was created based on it being parented
/// directly by the stack itself and no other resources. Note: this is equivalent to:
///
/// aliases = { new Alias { Parent = null } }
///
/// However, the former form is preferable as it is more self-descriptive, while the latter
/// may look a bit confusing and may incorrectly look like something that could be removed
/// without changing semantics.
///
internal static readonly Resource? Root = null;
///
/// is the type name that should be used to construct
/// the root component in the tree of Pulumi resources allocated by a deployment.This must
/// be kept up to date with
/// github.com/pulumi/pulumi/pkg/v2/resource/stack.RootPulumiStackTypeName.
///
internal const string _rootPulumiStackTypeName = "pulumi:pulumi:Stack";
///
/// The outputs of this stack, if the init callback exited normally.
///
internal Output> Outputs =
Output.Create>(ImmutableDictionary.Empty);
///
/// Create a Stack with stack resources defined in derived class constructor.
///
public Stack(StackOptions? options = null)
: base(_rootPulumiStackTypeName,
$"{Deployment.Instance.ProjectName}-{Deployment.Instance.StackName}",
ConvertOptions(options))
{
Deployment.InternalInstance.Stack = this;
}
///
/// Create a Stack with stack resources created by the init callback.
/// An instance of this will be automatically created when any overload is called.
///
internal Stack(Func>> init, StackOptions? options) : this(options)
{
try
{
this.Outputs = Output.Create(RunInitAsync(init));
}
finally
{
this.RegisterOutputs(this.Outputs);
}
}
///
/// Inspect all public properties of the stack to find outputs. Validate the values and register them as stack outputs.
///
internal void RegisterPropertyOutputs()
{
var outputs = (from property in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
let attr = property.GetCustomAttribute()
where attr != null
let name = attr?.Name ?? property.Name
select new KeyValuePair(name, property.GetValue(this))).ToList();
// Check that none of the values are null: catch unassigned outputs
var nulls = (from kv in outputs
where kv.Value == null
select kv.Key).ToList();
if (nulls.Any())
{
var message = $"Output(s) '{string.Join(", ", nulls)}' have no value assigned. [Output] attributed properties must be assigned inside Stack constructor.";
throw new RunException(message);
}
// Check that all the values are Output
var wrongTypes = (from kv in outputs
let type = kv.Value.GetType()
let isOutput = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Output<>)
where !isOutput
select kv.Key).ToList();
if (wrongTypes.Any())
{
var message = $"Output(s) '{string.Join(", ", wrongTypes)}' have incorrect type. [Output] attributed properties must be instances of Output.";
throw new RunException(message);
}
IDictionary dict = new Dictionary(outputs);
this.Outputs = Output.Create(dict);
this.RegisterOutputs(this.Outputs);
}
private async Task> RunInitAsync(Func>> init)
{
var dictionary = await init().ConfigureAwait(false);
return dictionary == null
? ImmutableDictionary.Empty
: dictionary.ToImmutableDictionary();
}
private static ComponentResourceOptions? ConvertOptions(StackOptions? options)
{
if (options == null)
return null;
return new ComponentResourceOptions
{
ResourceTransformations = options.ResourceTransformations
};
}
}
}