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