Add **preview** .NET Core support for pulumi. (#3399)

This commit is contained in:
CyrusNajmabadi 2019-10-25 16:59:50 -07:00 committed by GitHub
parent 602232ec27
commit 394c91d7f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
141 changed files with 9350 additions and 2 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
**/node_modules/
**/bin
**/.vscode/
**/.vs/
coverage.cov
*.coverprofile

View file

@ -3,6 +3,9 @@ CHANGELOG
## HEAD (Unreleased)
- Adds a **preview** of .NET support for Pulumi. This code is an preview state and is subject
to change at any point.
## 1.4.0 (2019-10-24)
- `FileAsset` in the Python SDK now accepts anything implementing `os.PathLike` in addition to `str`.

View file

@ -1,5 +1,5 @@
PROJECT_NAME := Pulumi SDK
SUB_PROJECTS := sdk/nodejs sdk/python sdk/go
SUB_PROJECTS := sdk/dotnet sdk/nodejs sdk/python sdk/go
include build/common.mk
PROJECT := github.com/pulumi/pulumi

View file

@ -57,6 +57,7 @@ import (
const PythonRuntime = "python"
const NodeJSRuntime = "nodejs"
const GoRuntime = "go"
const DotNetRuntime = "dotnet"
// RuntimeValidationStackInfo contains details related to the stack that runtime validation logic may want to use.
type RuntimeValidationStackInfo struct {
@ -219,6 +220,8 @@ type ProgramTestOptions struct {
GoBin string
// PipenvBin is a location of a `pipenv` executable to run. Taken from the $PATH if missing.
PipenvBin string
// DotNetBin is a location of a `dotnet` executable to be run. Taken from the $PATH if missing.
DotNetBin string
// Additional environment variables to pass for each command we run.
Env []string
@ -575,6 +578,7 @@ type programTester struct {
yarnBin string // the `yarn` binary we are using.
goBin string // the `go` binary we are using.
pipenvBin string // The `pipenv` binary we are using.
dotNetBin string // the `dotnet` binary we are using.
eventLog string // The path to the event log for this test.
}
@ -604,6 +608,10 @@ func (pt *programTester) getPipenvBin() (string, error) {
return getCmdBin(&pt.pipenvBin, "pipenv", pt.opts.PipenvBin)
}
func (pt *programTester) getDotNetBin() (string, error) {
return getCmdBin(&pt.dotNetBin, "dotnet", pt.opts.DotNetBin)
}
func (pt *programTester) pulumiCmd(args []string) ([]string, error) {
bin, err := pt.getBin()
if err != nil {
@ -1373,6 +1381,8 @@ func (pt *programTester) prepareProject(projinfo *engine.Projinfo) error {
return pt.preparePythonProject(projinfo)
case GoRuntime:
return pt.prepareGoProject(projinfo)
case DotNetRuntime:
return pt.prepareDotNetProject(projinfo)
default:
return errors.Errorf("unrecognized project runtime: %s", rt)
}
@ -1574,3 +1584,40 @@ func (pt *programTester) prepareGoProject(projinfo *engine.Projinfo) error {
outBin := filepath.Join(gopath, "bin", string(projinfo.Proj.Name))
return pt.runCommand("go-build", []string{goBin, "build", "-o", outBin, "."}, cwd)
}
// prepareDotNetProject runs setup necessary to get a .NET project ready for `pulumi` commands.
func (pt *programTester) prepareDotNetProject(projinfo *engine.Projinfo) error {
dotNetBin, err := pt.getDotNetBin()
if err != nil {
return errors.Wrap(err, "locating `dotnet` binary")
}
cwd, _, err := projinfo.GetPwdMain()
if err != nil {
return err
}
localNuget := os.Getenv("PULUMI_LOCAL_NUGET")
if localNuget == "" {
usr, err := user.Current()
if err != nil {
return errors.Wrap(err, "could not determine current user")
}
localNuget = filepath.Join(usr.HomeDir, ".nuget", "local")
}
// dotnet add package requires a specific version in case of a pre-release, so we have to look it up.
matches, err := filepath.Glob(filepath.Join(localNuget, "Pulumi.?.?.*.nupkg"))
if err != nil {
return errors.Wrap(err, "failed to find a local Pulumi NuGet package")
}
if len(matches) != 1 {
return errors.New(fmt.Sprintf("attempting to find a local Pulumi NuGet package yielded %v results", matches))
}
file := filepath.Base(matches[0])
r := strings.NewReplacer("Pulumi.", "", ".nupkg", "")
version := r.Replace(file)
return pt.runCommand("dotnet-add-package",
[]string{dotNetBin, "add", "package", "Pulumi", "-s", localNuget, "-v", version}, cwd)
}

239
sdk/dotnet/.editorconfig Normal file
View file

@ -0,0 +1,239 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = lf
insert_final_newline = true
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
# this. and Me. preferences
dotnet_style_qualification_for_event = false:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
# Expression-level preferences
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_object_initializer = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
# Field preferences
dotnet_style_readonly_field = true:suggestion
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
# Expression-level preferences
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.namespace_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.namespace_should_be_pascal_case.symbols = namespace
dotnet_naming_rule.namespace_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_or_internal_field_should_be_start_with___camelcased.severity = suggestion
dotnet_naming_rule.private_or_internal_field_should_be_start_with___camelcased.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_start_with___camelcased.style = start_with___camelcased
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal
dotnet_naming_symbols.non_field_members.required_modifiers =
dotnet_naming_symbols.namespace.applicable_kinds = namespace
dotnet_naming_symbols.namespace.applicable_accessibilities = *
dotnet_naming_symbols.namespace.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.start_with___camelcased.required_prefix = _
dotnet_naming_style.start_with___camelcased.required_suffix =
dotnet_naming_style.start_with___camelcased.word_separator =
dotnet_naming_style.start_with___camelcased.capitalization = camel_case
# RS0016: Add public types and members to the declared API
dotnet_diagnostic.RS0016.severity = error
# RS0017: Remove deleted types and members from the declared API
dotnet_diagnostic.RS0017.severity = error
# RS0022: Constructor make noninheritable base class inheritable
dotnet_diagnostic.RS0022.severity = error
# RS0024: The contents of the public API files are invalid
dotnet_diagnostic.RS0024.severity = error
# RS0025: Do not duplicate symbols in public API files
dotnet_diagnostic.RS0025.severity = error
# RS0026: Do not add multiple public overloads with optional parameters
dotnet_diagnostic.RS0026.severity = error
# RS0027: Public API with optional parameter(s) should have the most parameters amongst its public overloads.
dotnet_diagnostic.RS0027.severity = error

4
sdk/dotnet/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
[Bb]in/
[Oo]bj/
.leu
Pulumi/Pulumi.xml

39
sdk/dotnet/Makefile Normal file
View file

@ -0,0 +1,39 @@
PROJECT_NAME := Pulumi .NET Core SDK
LANGHOST_PKG := github.com/pulumi/pulumi/sdk/dotnet/cmd/pulumi-language-dotnet
VERSION := $(shell ../../scripts/get-version)
VERSION_DOTNET := ${VERSION:v%=%} # strip v from the beginning
VERSION_PREFIX := $(firstword $(subst -, ,${VERSION_DOTNET})) # e.g. 1.5.0
VERSION_SUFFIX := $(word 2,$(subst -, ,${VERSION_DOTNET})) # e.g. alpha.1
PROJECT_PKGS := $(shell go list ./cmd...)
PULUMI_LOCAL_NUGET ?= ~/.nuget/local
TESTPARALLELISM := 10
include ../../build/common.mk
build::
dotnet build dotnet.sln /p:VersionPrefix=${VERSION_PREFIX} /p:VersionSuffix=${VERSION_SUFFIX}
echo "Copying NuGet packages to ${PULUMI_LOCAL_NUGET}"
mkdir -p ${PULUMI_LOCAL_NUGET}
find . -name '*.nupkg' -exec cp -p {} ${PULUMI_LOCAL_NUGET} \;
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${LANGHOST_PKG}
install_plugin::
GOBIN=$(PULUMI_BIN) go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${LANGHOST_PKG}
install:: install_plugin
lint::
golangci-lint run
dotnet_test::
dotnet test
test_fast:: dotnet_test
$(GO_TEST_FAST) ${PROJECT_PKGS}
test_all:: dotnet_test
$(GO_TEST) ${PROJECT_PKGS}
dist::
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${LANGHOST_PKG}

View file

@ -0,0 +1,15 @@
namespace Pulumi.FSharp
open System.Collections.Generic
open Pulumi
[<AutoOpen>]
module Ops =
let input<'a> (v: 'a): Input<'a> = Input.op_Implicit v
let io<'a> (v: Output<'a>): Input<'a> = Input.op_Implicit v
module Deployment =
let run (f: unit -> IDictionary<string, obj>) =
Deployment.RunAsync (fun () -> f())
|> Async.AwaitTask
|> Async.RunSynchronously

View file

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Pulumi</Authors>
<Company>Pulumi Corp.</Company>
<Description>F#-specific helpers for the Pulumi .NET SDK.</Description>
<PackageProjectUrl>https://www.pulumi.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/pulumi/pulumi</RepositoryUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageIcon>pulumi_logo_64x64.png</PackageIcon>
</PropertyGroup>
<PropertyGroup>
<NoWarn>NU5105</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pulumi\Pulumi.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\pulumi_logo_64x64.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,4 @@
using Xunit;
// Unfortunately, we depend on static state. So for now disable parallelization.
[assembly: CollectionBehavior(DisableTestParallelization = true)]

View file

@ -0,0 +1,25 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
using Xunit;
namespace Pulumi.Tests
{
public static class AssertEx
{
public static void SequenceEqual<T>(IEnumerable<T> expected, IEnumerable<T> actual)
=> Assert.Equal(expected, actual);
public static void MapEqual<TKey, TValue>(IDictionary<TKey, TValue> expected, IDictionary<TKey, TValue> actual) where TKey : notnull
{
Assert.Equal(expected.Count, actual.Count);
foreach (var (key, actualValue) in actual)
{
#pragma warning disable CS8717 // A member returning a [MaybeNull] value introduces a null value for a type parameter.
Assert.True(expected.TryGetValue(key, out var expectedValue));
#pragma warning restore CS8717 // A member returning a [MaybeNull] value introduces a null value for a type parameter.
Assert.Equal(expectedValue, actualValue);
}
}
}
}

View file

@ -0,0 +1,114 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Immutable;
using System.Threading.Tasks;
using Pulumi.Serialization;
using Xunit;
namespace Pulumi.Tests.Core
{
public partial class OutputTests : PulumiTest
{
private static Output<T> CreateOutput<T>(T value, bool isKnown, bool isSecret = false)
=> new Output<T>(ImmutableHashSet<Resource>.Empty,
Task.FromResult(OutputData.Create(value, isKnown, isSecret)));
public class PreviewTests
{
[Fact]
public Task ApplyCanRunOnKnownValue()
=> RunInPreview(async () =>
{
var o1 = CreateOutput(0, isKnown: true);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.True(data.IsKnown);
Assert.Equal(1, data.Value);
});
[Fact]
public Task ApplyProducesUnknownDefaultOnUnknown()
=> RunInPreview(async () =>
{
var o1 = CreateOutput(0, isKnown: false);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.False(data.IsKnown);
Assert.Equal(0, data.Value);
});
[Fact]
public Task ApplyPreservesSecretOnKnown()
=> RunInPreview(async () =>
{
var o1 = CreateOutput(0, isKnown: true, isSecret: true);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.True(data.IsKnown);
Assert.True(data.IsSecret);
Assert.Equal(1, data.Value);
});
[Fact]
public Task ApplyPreservesSecretOnUnknown()
=> RunInPreview(async () =>
{
var o1 = CreateOutput(0, isKnown: false, isSecret: true);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.False(data.IsKnown);
Assert.True(data.IsSecret);
Assert.Equal(0, data.Value);
});
}
public class NormalTests
{
[Fact]
public Task ApplyCanRunOnKnownValue()
=> RunInNormal(async () =>
{
var o1 = CreateOutput(0, isKnown: true);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.True(data.IsKnown);
Assert.Equal(1, data.Value);
});
[Fact]
public Task ApplyProducesKnownOnUnknown()
=> RunInNormal(async () =>
{
var o1 = CreateOutput(0, isKnown: false);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.False(data.IsKnown);
Assert.Equal(1, data.Value);
});
[Fact]
public Task ApplyPreservesSecretOnKnown()
=> RunInNormal(async () =>
{
var o1 = CreateOutput(0, isKnown: true, isSecret: true);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.True(data.IsKnown);
Assert.True(data.IsSecret);
Assert.Equal(1, data.Value);
});
[Fact]
public Task ApplyPreservesSecretOnUnknown()
=> RunInNormal(async () =>
{
var o1 = CreateOutput(0, isKnown: false, isSecret: true);
var o2 = o1.Apply(a => a + 1);
var data = await o2.DataTask.ConfigureAwait(false);
Assert.False(data.IsKnown);
Assert.True(data.IsSecret);
Assert.Equal(1, data.Value);
});
}
}
}

View file

@ -0,0 +1,134 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Immutable;
using System.Threading.Tasks;
using Pulumi.Serialization;
using Xunit;
namespace Pulumi.Tests.Core
{
public class ResourceArgsTests : PulumiTest
{
#region ComplexResourceArgs1
public class ComplexResourceArgs1 : ResourceArgs
{
[Input("s")] public Input<string> S { get; set; } = null!;
[Input("array")] private InputList<bool> _array = null!;
public InputList<bool> Array
{
get => _array ?? (_array = new InputList<bool>());
set => _array = value;
}
}
[Fact]
public async Task TestComplexResourceArgs1_NullValues()
{
var args = new ComplexResourceArgs1();
var dictionary = await args.ToDictionaryAsync();
Assert.True(dictionary.TryGetValue("s", out var sValue));
Assert.True(dictionary.TryGetValue("array", out var arrayValue));
Assert.Null(sValue);
Assert.Null(arrayValue);
}
[Fact]
public async Task TestComplexResourceArgs1_SetField()
{
var args = new ComplexResourceArgs1
{
S = "val",
};
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
Assert.True(dictionary.TryGetValue("s", out var sValue));
Assert.True(dictionary.TryGetValue("array", out var arrayValue));
Assert.NotNull(sValue);
Assert.Null(arrayValue);
var output = sValue!.ToOutput();
var data = await output.GetDataAsync();
Assert.Equal("val", data.Value);
}
[Fact]
public Task TestComplexResourceArgs1_SetProperty()
{
return RunInNormal(async () =>
{
var args = new ComplexResourceArgs1
{
Array = { true },
};
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
Assert.True(dictionary.TryGetValue("s", out var sValue));
Assert.True(dictionary.TryGetValue("array", out var arrayValue));
Assert.Null(sValue);
Assert.NotNull(arrayValue);
var output = arrayValue!.ToOutput();
var data = await output.GetDataAsync();
AssertEx.SequenceEqual(
ImmutableArray<bool>.Empty.Add(true), (ImmutableArray<bool>)data.Value!);
});
}
#endregion
#region JsonResourceArgs1
public class JsonResourceArgs1 : ResourceArgs
{
[Input("array", json: true)] private InputList<bool> _array = null!;
public InputList<bool> Array
{
get => _array ?? (_array = new InputList<bool>());
set => _array = value;
}
[Input("map", json: true)] private InputMap<int> _map = null!;
public InputMap<int> Map
{
get => _map ?? (_map = new InputMap<int>());
set => _map = value;
}
}
[Fact]
public async Task TestJsonMap()
{
var args = new JsonResourceArgs1
{
Array = { true, false },
Map =
{
{ "k1", 1 },
{ "k2", 2 },
},
};
var dictionary = await args.ToDictionaryAsync();
Assert.True(dictionary.TryGetValue("array", out var arrayValue));
Assert.True(dictionary.TryGetValue("map", out var mapValue));
Assert.NotNull(arrayValue);
Assert.NotNull(mapValue);
var arrayVal = (await arrayValue!.ToOutput().GetDataAsync()).Value;
Assert.Equal("[ true, false ]", arrayVal);
var mapVal = (await mapValue!.ToOutput().GetDataAsync()).Value;
Assert.Equal("{ \"k1\": 1, \"k2\": 2 }", mapVal);
}
#endregion
}
}

View file

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="Google.Protobuf" Version="3.10.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pulumi\Pulumi.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,39 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Moq;
namespace Pulumi.Tests
{
public abstract class PulumiTest
{
private static Task Run(Action action, bool dryRun)
=> Run(() =>
{
action();
return Task.CompletedTask;
}, dryRun);
private static async Task Run(Func<Task> func, bool dryRun)
{
var mock = new Mock<IDeployment>(MockBehavior.Strict);
mock.Setup(d => d.IsDryRun).Returns(dryRun);
Deployment.Instance = mock.Object;
await func().ConfigureAwait(false);
}
protected static Task RunInPreview(Action action)
=> Run(action, dryRun: true);
protected static Task RunInNormal(Action action)
=> Run(action, dryRun: false);
protected static Task RunInPreview(Func<Task> func)
=> Run(func, dryRun: true);
protected static Task RunInNormal(Func<Task> func)
=> Run(func, dryRun: false);
}
}

View file

@ -0,0 +1,127 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
using Xunit;
namespace Pulumi.Tests.Serialization
{
public class BooleanConverterTests : ConverterTests
{
[Fact]
public void True()
{
var data = Converter.ConvertValue<bool>("", new Value { BoolValue = true });
Assert.True(data.Value);
Assert.True(data.IsKnown);
}
[Fact]
public void False()
{
var data = Converter.ConvertValue<bool>("", new Value { BoolValue = false });
Assert.False(data.Value);
Assert.True(data.IsKnown);
}
[Fact]
public void SecretTrue()
{
var data = Converter.ConvertValue<bool>("", CreateSecretValue(new Value { BoolValue = true }));
Assert.True(data.Value);
Assert.True(data.IsKnown);
Assert.True(data.IsSecret);
}
[Fact]
public void SecretFalse()
{
var data = Converter.ConvertValue<bool>("", CreateSecretValue(new Value { BoolValue = false }));
Assert.False(data.Value);
Assert.True(data.IsKnown);
Assert.True(data.IsSecret);
}
[Fact]
public void NonBooleanThrows()
{
Assert.Throws<InvalidOperationException>(() =>
{
var data = Converter.ConvertValue<bool>("", new Value { StringValue = "" });
});
}
[Fact]
public Task NullInPreviewProducesFalseKnown()
{
return RunInPreview(() =>
{
var data = Converter.ConvertValue<bool>("", new Value { NullValue = NullValue.NullValue });
Assert.False(data.Value);
Assert.True(data.IsKnown);
});
}
[Fact]
public Task NullInNormalProducesFalseKnown()
{
return RunInNormal(() =>
{
var data = Converter.ConvertValue<bool>("", new Value { NullValue = NullValue.NullValue });
Assert.False(data.Value);
Assert.True(data.IsKnown);
});
}
[Fact]
public void UnknownProducesFalseUnknown()
{
var data = Converter.ConvertValue<bool>("", UnknownValue);
Assert.False(data.Value);
Assert.False(data.IsKnown);
}
[Fact]
public void StringTest()
{
Assert.Throws<InvalidOperationException>(() =>
{
var data = Converter.ConvertValue<bool>("", new Value { StringValue = "" });
});
}
[Fact]
public void NullableTrue()
{
var data = Converter.ConvertValue<bool?>("", new Value { BoolValue = true });
Assert.True(data.Value);
Assert.True(data.IsKnown);
}
[Fact]
public void NullableFalse()
{
var data = Converter.ConvertValue<bool?>("", new Value { BoolValue = false });
Assert.False(data.Value);
Assert.True(data.IsKnown);
}
[Fact]
public void NullableNull()
{
var data = Converter.ConvertValue<bool?>("", new Value { NullValue = NullValue.NullValue });
Assert.Null(data.Value);
Assert.True(data.IsKnown);
}
}
}

View file

@ -0,0 +1,170 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Pulumi.Serialization;
using Xunit;
namespace Pulumi.Tests.Serialization
{
public class ComplexTypeConverterTests : ConverterTests
{
#region Simple case
[OutputType]
public class ComplexType1
{
public readonly string S;
public readonly bool B;
public readonly int I;
public readonly double D;
public readonly ImmutableArray<bool> Array;
public readonly ImmutableDictionary<string, int> Dict;
[OutputConstructor]
public ComplexType1(
string s, bool b, int i, double d,
ImmutableArray<bool> array, ImmutableDictionary<string, int> dict)
{
S = s;
B = b;
I = i;
D = d;
Array = array;
Dict = dict;
}
}
[Fact]
public async Task TestComplexType1()
{
var data = Converter.ConvertValue<ComplexType1>("", await SerializeToValueAsync(new Dictionary<string, object>
{
{ "s", "str" },
{ "b", true },
{ "i", 42 },
{ "d", 1.5 },
{ "array", new List<object> { true, false } },
{ "dict", new Dictionary<object, object> { { "k", 10 } } },
}));
Assert.Equal("str", data.Value.S);
Assert.Equal((object)true, data.Value.B);
Assert.Equal(42, data.Value.I);
Assert.Equal(1.5, data.Value.D);
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true).Add(false), data.Value.Array);
AssertEx.MapEqual(ImmutableDictionary<string, int>.Empty.Add("k", 10), data.Value.Dict);
Assert.True(data.IsKnown);
}
#endregion
#region Nested case
[OutputType]
public class ComplexType2
{
public readonly ComplexType1 C;
public readonly ImmutableArray<ComplexType1> C2Array;
public readonly ImmutableDictionary<string, ComplexType1> C2Map;
[OutputConstructor]
public ComplexType2(
ComplexType1 c,
ImmutableArray<ComplexType1> c2Array,
ImmutableDictionary<string, ComplexType1> c2Map)
{
C = c;
C2Array = c2Array;
C2Map = c2Map;
}
}
[Fact]
public async Task TestComplexType2()
{
var data = Converter.ConvertValue<ComplexType2>("", await SerializeToValueAsync(new Dictionary<string, object>
{
{
"c",
new Dictionary<string, object>
{
{ "s", "str1" },
{ "b", false },
{ "i", 1 },
{ "d", 1.1 },
{ "array", new List<object> { false, false } },
{ "dict", new Dictionary<object, object> { { "k", 1 } } },
}
},
{
"c2Array",
new List<object>
{
new Dictionary<string, object>
{
{ "s", "str2" },
{ "b", true },
{ "i", 2 },
{ "d", 2.2 },
{ "array", new List<object> { false, true } },
{ "dict", new Dictionary<object, object> { { "k", 2 } } },
}
}
},
{
"c2Map",
new Dictionary<string, object>
{
{
"someKey",
new Dictionary<string, object>
{
{ "s", "str3" },
{ "b", false },
{ "i", 3 },
{ "d", 3.3 },
{ "array", new List<object> { true, false } },
{ "dict", new Dictionary<object, object> { { "k", 3 } } },
}
}
}
}
})).Value;
var value = data.C;
Assert.Equal("str1", value.S);
Assert.Equal((object)false, value.B);
Assert.Equal(1, value.I);
Assert.Equal(1.1, value.D);
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(false).Add(false), value.Array);
AssertEx.MapEqual(ImmutableDictionary<string, int>.Empty.Add("k", 1), value.Dict);
Assert.Single(data.C2Array);
value = data.C2Array[0];
Assert.Equal("str2", value.S);
Assert.Equal((object)true, value.B);
Assert.Equal(2, value.I);
Assert.Equal(2.2, value.D);
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(false).Add(true), value.Array);
AssertEx.MapEqual(ImmutableDictionary<string, int>.Empty.Add("k", 2), value.Dict);
Assert.Single(data.C2Map);
var (key, val) = data.C2Map.Single();
Assert.Equal("someKey", key);
value = val;
Assert.Equal("str3", value.S);
Assert.Equal((object)false, value.B);
Assert.Equal(3, value.I);
Assert.Equal(3.3, value.D);
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true).Add(false), value.Array);
AssertEx.MapEqual(ImmutableDictionary<string, int>.Empty.Add("k", 3), value.Dict);
}
#endregion
}
}

View file

@ -0,0 +1,37 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Immutable;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
namespace Pulumi.Tests.Serialization
{
public abstract class ConverterTests : PulumiTest
{
protected static readonly Value UnknownValue = new Value { StringValue = Constants.UnknownValue };
protected static Value CreateSecretValue(Value value)
=> new Value
{
StructValue = new Struct
{
Fields =
{
{ Constants.SpecialSigKey, new Value { StringValue = Constants.SpecialSecretSig } },
{ Constants.SecretValueName, value },
}
}
};
protected Output<T> CreateUnknownOutput<T>(T value)
=> new Output<T>(ImmutableHashSet<Resource>.Empty, Task.FromResult(new OutputData<T>(value, isKnown: false, isSecret: false)));
protected async Task<Value> SerializeToValueAsync(object? value)
{
var serializer = new Serializer(excessiveDebugOutput: false);
return Serializer.CreateValue(
await serializer.SerializeAsync(ctx: "", value).ConfigureAwait(false));
}
}
}

View file

@ -0,0 +1,61 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Pulumi.Serialization;
using Xunit;
namespace Pulumi.Tests.Serialization
{
public class ListConverterTests : ConverterTests
{
[Fact]
public async Task EmptyList()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<bool>()));
Assert.Equal(ImmutableArray<bool>.Empty, data.Value);
Assert.True(data.IsKnown);
}
[Fact]
public async Task ListWithElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<bool> { true }));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true), data.Value);
Assert.True(data.IsKnown);
}
[Fact]
public async Task SecretListWithElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(Output.CreateSecret(new List<object> { true })));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true), data.Value);
Assert.True(data.IsKnown);
Assert.True(data.IsSecret);
}
[Fact]
public async Task ListWithSecretElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<object> { Output.CreateSecret(true) }));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(true), data.Value);
Assert.True(data.IsKnown);
Assert.True(data.IsSecret);
}
[Fact]
public async Task ListWithUnknownElement()
{
var data = Converter.ConvertValue<ImmutableArray<bool>>("", await SerializeToValueAsync(new List<object> { CreateUnknownOutput(true) }));
AssertEx.SequenceEqual(ImmutableArray<bool>.Empty.Add(false), data.Value);
Assert.False(data.IsKnown);
Assert.False(data.IsSecret);
}
}
}

View file

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Pulumi.Tests")]

217
sdk/dotnet/Pulumi/Config.cs Normal file
View file

@ -0,0 +1,217 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
namespace Pulumi
{
/// <summary>
/// <see cref="Config"/> is a bag of related configuration state. Each bag contains any number
/// of configuration variables, indexed by simple keys, and each has a name that uniquely
/// identifies it; two bags with different names do not share values for variables that
/// otherwise share the same key. For example, a bag whose name is <c>pulumi:foo</c>, with keys
/// <c>a</c>, <c>b</c>, and <c>c</c>, is entirely separate from a bag whose name is
/// <c>pulumi:bar</c> with the same simple key names. Each key has a fully qualified names,
/// such as <c>pulumi:foo:a</c>, ..., and <c>pulumi:bar:a</c>, respectively.
/// </summary>
public sealed partial class Config
{
/// <summary>
/// <see cref="_name"/> is the configuration bag's logical name and uniquely identifies it.
/// The default is the name of the current project.
/// </summary>
private readonly string _name;
/// <summary>
/// Creates a new <see cref="Config"/> instance. <paramref name="name"/> is the
/// configuration bag's logical name and uniquely identifies it. The default is the name of
/// the current project.
/// </summary>
public Config(string? name = null)
{
if (name == null)
{
name = Deployment.Instance.ProjectName;
}
if (name.EndsWith(":config", StringComparison.Ordinal))
{
name = name[0..^":config".Length];
}
_name = name;
}
[return: NotNullIfNotNull("value")]
private static Output<T>? MakeClassSecret<T>(T? value) where T : class
=> value == null ? null : Output.CreateSecret(value);
private static Output<T>? MakeStructSecret<T>(T? value) where T : struct
=> value == null ? null : MakeStructSecret(value.Value);
private static Output<T> MakeStructSecret<T>(T value) where T : struct
=> Output.CreateSecret(value);
/// <summary>
/// Loads an optional configuration value by its key, or <see langword="null"/> if it doesn't exist.
/// </summary>
public string? Get(string key)
=> Deployment.InternalInstance.GetConfig(FullKey(key));
/// <summary>
/// Loads an optional configuration value by its key, marking it as a secret, or <see
/// langword="null"/> if it doesn't exist.
/// </summary>
public Output<string>? GetSecret(string key)
=> MakeClassSecret(Get(key));
/// <summary>
/// Loads an optional configuration value, as a boolean, by its key, or null if it doesn't exist.
/// If the configuration value isn't a legal boolean, this function will throw an error.
/// </summary>
public bool? GetBoolean(string key)
{
var v = Get(key);
return v == null ? default(bool?) :
v == "true" ? true :
v == "false" ? false : throw new ConfigTypeException(FullKey(key), v, nameof(Boolean));
}
/// <summary>
/// Loads an optional configuration value, as a boolean, by its key, making it as a secret or
/// null if it doesn't exist. If the configuration value isn't a legal boolean, this
/// function will throw an error.
/// </summary>
public Output<bool>? GetSecretBoolean(string key)
=> MakeStructSecret(GetBoolean(key));
/// <summary>
/// Loads an optional configuration value, as a number, by its key, or null if it doesn't exist.
/// If the configuration value isn't a legal number, this function will throw an error.
/// </summary>
public int? GetInt32(string key)
{
var v = Get(key);
return v == null
? default(int?)
: int.TryParse(v, out var result)
? result
: throw new ConfigTypeException(FullKey(key), v, nameof(Int32));
}
/// <summary>
/// Loads an optional configuration value, as a number, by its key, marking it as a secret
/// or null if it doesn't exist.
/// If the configuration value isn't a legal number, this function will throw an error.
/// </summary>
public Output<int>? GetSecretInt32(string key)
=> MakeStructSecret(GetInt32(key));
/// <summary>
/// Loads an optional configuration value, as an object, by its key, or null if it doesn't
/// exist. This works by taking the value associated with <paramref name="key"/> and passing
/// it to <see cref="JsonSerializer.Deserialize{TValue}(string, JsonSerializerOptions)"/>.
/// </summary>
[return: MaybeNull]
public T GetObject<T>(string key)
{
var v = Get(key);
try
{
return v == null ? default : JsonSerializer.Deserialize<T>(v);
}
catch (JsonException ex)
{
throw new ConfigTypeException(FullKey(key), v, typeof(T).FullName!, ex);
}
}
/// <summary>
/// Loads an optional configuration value, as an object, by its key, marking it as a secret
/// or null if it doesn't exist. This works by taking the value associated with <paramref
/// name="key"/> and passing it to <see cref="JsonSerializer.Deserialize{TValue}(string,
/// JsonSerializerOptions)"/>.
/// </summary>
public Output<T>? GetSecretObject<T>(string key)
{
var v = Get(key);
if (v == null)
return null;
return Output.CreateSecret(GetObject<T>(key)!);
}
/// <summary>
/// Loads a configuration value by its given key. If it doesn't exist, an error is thrown.
/// </summary>
public string Require(string key)
=> Get(key) ?? throw new ConfigMissingException(FullKey(key));
/// <summary>
/// Loads a configuration value by its given key, marking it as a secret. If it doesn't exist, an error
/// is thrown.
/// </summary>
public Output<string> RequireSecret(string key)
=> MakeClassSecret(Require(key));
/// <summary>
/// Loads a configuration value, as a boolean, by its given key. If it doesn't exist, or the
/// configuration value is not a legal boolean, an error is thrown.
/// </summary>
public bool RequireBoolean(string key)
=> GetBoolean(key) ?? throw new ConfigMissingException(FullKey(key));
/// <summary>
/// Loads a configuration value, as a boolean, by its given key, marking it as a secret.
/// If it doesn't exist, or the configuration value is not a legal boolean, an error is thrown.
/// </summary>
public Output<bool> RequireSecretBoolean(string key)
=> MakeStructSecret(RequireBoolean(key));
/// <summary>
/// Loads a configuration value, as a number, by its given key. If it doesn't exist, or the
/// configuration value is not a legal number, an error is thrown.
/// </summary>
public int RequireInt32(string key)
=> GetInt32(key) ?? throw new ConfigMissingException(FullKey(key));
/// <summary>
/// Loads a configuration value, as a number, by its given key, marking it as a secret.
/// If it doesn't exist, or the configuration value is not a legal number, an error is thrown.
/// </summary>
public Output<int> RequireSecretInt32(string key)
=> MakeStructSecret(RequireInt32(key));
/// <summary>
/// Loads a configuration value as a JSON string and deserializes the JSON into an object.
/// object. If it doesn't exist, or the configuration value cannot be converted using <see
/// cref="JsonSerializer.Deserialize{TValue}(string, JsonSerializerOptions)"/>, an error is
/// thrown.
/// </summary>
public T RequireObject<T>(string key)
{
var v = Get(key);
if (v == null)
throw new ConfigMissingException(FullKey(key));
return GetObject<T>(key)!;
}
/// <summary>
/// Loads a configuration value as a JSON string and deserializes the JSON into a JavaScript
/// object, marking it as a secret. If it doesn't exist, or the configuration value cannot
/// be converted using <see cref="JsonSerializer.Deserialize{TValue}(string,
/// JsonSerializerOptions)"/>. an error is thrown.
/// </summary>
public Output<T> RequireSecretObject<T>(string key)
=> Output.CreateSecret(RequireObject<T>(key));
/// <summary>
/// Turns a simple configuration key into a fully resolved one, by prepending the bag's name.
/// </summary>
private string FullKey(string key)
=> $"{_name}:{key}";
}
}

View file

@ -0,0 +1,37 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
namespace Pulumi
{
public partial class Config
{
/// <summary>
/// ConfigTypeException is used when a configuration value is of the wrong type.
/// </summary>
private class ConfigTypeException : RunException
{
public ConfigTypeException(string key, object? v, string expectedType)
: this(key, v, expectedType, innerException: null)
{
}
public ConfigTypeException(string key, object? v, string expectedType, Exception? innerException)
: base($"Configuration '{key}' value '{v}' is not a valid {expectedType}", innerException)
{
}
}
/// <summary>
/// ConfigMissingException is used when a configuration value is completely missing.
/// </summary>
private class ConfigMissingException : RunException
{
public ConfigMissingException(string key)
: base($"Missing Required configuration variable '{key}'\n" +
$"\tplease set a value using the command `pulumi config set {key} <value>`")
{
}
}
}
}

View file

@ -0,0 +1,85 @@
// Copyright 2016-2019, Pulumi Corporation
namespace Pulumi
{
/// <summary>
/// Alias is a description of prior named used for a resource. It can be processed in the
/// context of a resource creation to determine what the full aliased URN would be.
/// <para/>
/// Use <see cref="Urn"/> in the case where a prior URN is known and can just be specified in
/// full. Otherwise, provide some subset of the other properties in this type to generate an
/// appropriate urn from the pre-existing values of the <see cref="Resource"/> with certain
/// parts overridden.
/// <para/>
/// The presence of a property indicates if its value should be used. If absent (i.e.
/// <see langword="null"/>), then the value is not used.
/// <para/>
/// Note: because of the above, there needs to be special handling to indicate that the previous
/// <see cref="Parent"/> of a <see cref="Resource"/> was <see langword="null"/>. Specifically,
/// pass in:
/// <para/>
/// <c>Aliases = { new Alias { NoParent = true } }</c>
/// </summary>
public sealed class Alias
{
/// <summary>
/// The previous urn to alias to. If this is provided, no other properties in this type
/// should be provided.
/// </summary>
public string? Urn { get; set; }
/// <summary>
/// The previous name of the resource. If <see langword="null"/>, the current name of the
/// resource is used.
/// </summary>
public Input<string>? Name { get; set; }
/// <summary>
/// The previous type of the resource. If <see langword="null"/>, the current type of the
/// resource is used.
/// </summary>
public Input<string>? Type { get; set; }
/// <summary>
/// The previous stack of the resource. If <see langword="null"/>, defaults to the value of
/// <see cref="IDeployment.StackName"/>.
/// </summary>
public Input<string>? Stack { get; set; }
/// <summary>
/// The previous project of the resource. If <see langword="null"/>, defaults to the value
/// of <see cref="IDeployment.ProjectName"/>.
/// </summary>
public Input<string>? Project { get; set; }
/// <summary>
/// The previous parent of the resource. If <see langword="null"/>, the current parent of
/// the resource is used.
/// <para/>
/// To specify no original parent, use <c>new Alias { NoParent = true }</c>.
/// <para/>
/// Only specify one of <see cref="Parent"/> or <see cref="ParentUrn"/> or <see cref="NoParent"/>.
/// </summary>
public Resource? Parent { get; set; }
/// <summary>
/// The previous parent of the resource. If <see langword="null"/>, the current parent of
/// the resource is used.
/// <para/>
/// To specify no original parent, use <c>new Alias { NoParent = true }</c>.
/// <para/>
/// Only specify one of <see cref="Parent"/> or <see cref="ParentUrn"/> or <see cref="NoParent"/>.
/// </summary>
public Input<string>? ParentUrn { get; set; }
/// <summary>
/// Used to indicate the resource previously had no parent. If <see langword="false"/> this
/// property is ignored.
/// <para/>
/// To specify no original parent, use <c>new Alias { NoParent = true }</c>.
/// <para/>
/// Only specify one of <see cref="Parent"/> or <see cref="ParentUrn"/> or <see cref="NoParent"/>.
/// </summary>
public bool NoParent { get; set; }
}
}

View file

@ -0,0 +1,55 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Immutable;
using Pulumi.Serialization;
namespace Pulumi
{
/// <summary>
/// An Archive represents a collection of named assets.
/// </summary>
public abstract class Archive : AssetOrArchive
{
private protected Archive(string propName, object value)
: base(Constants.SpecialArchiveSig, propName, value)
{
}
}
/// <summary>
/// An AssetArchive is an archive created from an in-memory collection of named assets or other
/// archives.
/// </summary>
public sealed class AssetArchive : Archive
{
public AssetArchive(ImmutableDictionary<string, AssetOrArchive> assets)
: base(Constants.ArchiveAssetsName, assets)
{
}
}
/// <summary>
/// A FileArchive is a file-based archive, or a collection of file-based assets. This can be a
/// raw directory or a single archive file in one of the supported formats(.tar, .tar.gz,
/// or.zip).
/// </summary>
public sealed class FileArchive : Archive
{
public FileArchive(string path) : base(Constants.AssetOrArchivePathName, path)
{
}
}
/// <summary>
/// A RemoteArchive is a file-based archive fetched from a remote location. The URI's scheme
/// dictates the protocol for fetching the archive's contents: <c>file://</c> is a local file
/// (just like a FileArchive), <c>http://</c> and <c>https://</c> specify HTTP and HTTPS,
/// respectively, and specific providers may recognize custom schemes.
/// </summary>
public sealed class RemoteArchive : Archive
{
public RemoteArchive(string uri) : base(Constants.AssetOrArchiveUriName, uri)
{
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright 2016-2019, Pulumi Corporation
using Pulumi.Serialization;
namespace Pulumi
{
/// <summary>
/// Asset represents a single blob of text or data that is managed as a first class entity.
/// </summary>
public abstract class Asset : AssetOrArchive
{
private protected Asset(string propName, object value)
: base(Constants.SpecialAssetSig, propName, value)
{
}
}
/// <summary>
/// FileAsset is a kind of asset produced from a given path to a file on the local filesystem.
/// </summary>
public sealed class FileAsset : Asset
{
public FileAsset(string path) : base(Constants.AssetOrArchivePathName, path)
{
}
}
/// <summary>
/// StringAsset is a kind of asset produced from an in-memory UTF8-encoded string.
/// </summary>
public sealed class StringAsset : Asset
{
public StringAsset(string text) : base(Constants.AssetTextName, text)
{
}
}
/// <summary>
/// RemoteAsset is a kind of asset produced from a given URI string. The URI's scheme dictates
/// the protocol for fetching contents: <c>file://</c> specifies a local file, <c>http://</c>
/// and <c>https://</c> specify HTTP and HTTPS, respectively. Note that specific providers may
/// recognize alternative schemes; this is merely the base-most set that all providers support.
/// </summary>
public sealed class RemoteAsset : Asset
{
public RemoteAsset(string uri) : base(Constants.AssetOrArchiveUriName, uri)
{
}
}
}

View file

@ -0,0 +1,21 @@
// Copyright 2016-2019, Pulumi Corporation
namespace Pulumi
{
/// <summary>
/// Base class of <see cref="Asset"/>s and <see cref="Archive"/>s.
/// </summary>
public abstract class AssetOrArchive
{
internal string SigKey { get; }
internal string PropName { get; }
internal object Value { get; }
private protected AssetOrArchive(string sigKey, string propName, object value)
{
SigKey = sigKey ?? throw new System.ArgumentNullException(nameof(sigKey));
PropName = propName ?? throw new System.ArgumentNullException(nameof(propName));
Value = value ?? throw new System.ArgumentNullException(nameof(value));
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Diagnostics.CodeAnalysis;
namespace Pulumi
{
/// <summary>
/// Internal interface to allow our code to operate on inputs in an untyped manner. Necessary as
/// there is no reasonable way to write algorithms over heterogeneous instantiations of generic
/// types.
/// </summary>
internal interface IInput
{
IOutput ToOutput();
}
/// <summary>
/// <see cref="Input{T}"/> is a property input for a <see cref="Resource"/>. It may be a promptly
/// available T, or the output from a existing <see cref="Resource"/>.
/// </summary>
public class Input<T> : IInput
{
/// <summary>
/// Technically, in .net we can represent Inputs entirely using the Output type (since
/// Outputs can wrap values and promises). However, it would look very weird to state that
/// the inputs to a resource *had* to be Outputs. So we basically just come up with this
/// wrapper type so things look sensible, even though under the covers we implement things
/// using the exact same type
/// </summary>
private protected Output<T> _outputValue;
private protected Input(Output<T> outputValue)
=> _outputValue = outputValue ?? throw new ArgumentNullException(nameof(outputValue));
public static implicit operator Input<T>([MaybeNull]T value)
=> Output.Create(value);
public static implicit operator Input<T>(Output<T> value)
=> new Input<T>(value);
public static implicit operator Output<T>(Input<T> input)
=> input._outputValue;
public Output<T> ToOutput()
=> this;
IOutput IInput.ToOutput()
=> ToOutput();
}
}

View file

@ -0,0 +1,159 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
namespace Pulumi
{
/// <summary>
/// A list of values that can be passed in as the arguments to a <see cref="Resource"/>.
/// The individual values are themselves <see cref="Input{T}"/>s. i.e. the individual values
/// can be concrete values or <see cref="Output{T}"/>s.
/// <para/>
/// <see cref="InputList{T}"/> differs from a normal <see cref="IList{T}"/> in that it is itself
/// an <see cref="Input{T}"/>. For example, a <see cref="Resource"/> that accepts an <see
/// cref="InputList{T}"/> will accept not just a list but an <see cref="Output{T}"/>
/// of a list. This is important for cases where the <see cref="Output{T}"/>
/// list from some <see cref="Resource"/> needs to be passed into another <see
/// cref="Resource"/>. Or for cases where creating the list invariably produces an <see
/// cref="Output{T}"/> because its resultant value is dependent on other <see
/// cref="Output{T}"/>s.
/// <para/>
/// This benefit of <see cref="InputList{T}"/> is also a limitation. Because it represents a
/// list of values that may eventually be created, there is no way to simply iterate over, or
/// access the elements of the list synchronously.
/// <para/>
/// <see cref="InputList{T}"/> is designed to be easily used in object and collection
/// initializers. For example, a resource that accepts a list of inputs can be written in
/// either of these forms:
/// <para/>
/// <code>
/// new SomeResource("name", new SomeResourceArgs {
/// ListProperty = { Value1, Value2, Value3 },
/// });
/// </code>
/// <para/>
/// or
/// <code>
/// new SomeResource("name", new SomeResourceArgs {
/// ListProperty = new [] { Value1, Value2, Value3 },
/// });
/// </code>
/// </summary>
public sealed class InputList<T> : Input<ImmutableArray<T>>, IEnumerable, IAsyncEnumerable<Input<T>>
{
public InputList() : this(Output.Create(ImmutableArray<T>.Empty))
{
}
private InputList(Output<ImmutableArray<T>> values)
: base(values)
{
}
public void Add(params Input<T>[] inputs)
{
// Make an Output from the values passed in, mix in with our own Output, and combine
// both to produce the final array that we will now point at.
_outputValue = Output.Concat(_outputValue, Output.All(inputs));
}
/// <summary>
/// Concatenates the values in this list with the values in <paramref name="other"/>,
/// returning the concatenated sequence in a new <see cref="InputList{T}"/>.
/// </summary>
public InputList<T> Concat(InputList<T> other)
=> Output.Concat(_outputValue, other._outputValue);
internal InputList<T> Clone()
=> new InputList<T>(_outputValue);
#region construct from unary
public static implicit operator InputList<T>(T value)
=> ImmutableArray.Create<Input<T>>(value);
public static implicit operator InputList<T>(Output<T> value)
=> ImmutableArray.Create<Input<T>>(value);
public static implicit operator InputList<T>(Input<T> value)
=> ImmutableArray.Create(value);
#endregion
#region construct from array
public static implicit operator InputList<T>(T[] values)
=> ImmutableArray.CreateRange(values.Select(v => (Input<T>)v));
public static implicit operator InputList<T>(Output<T>[] values)
=> ImmutableArray.CreateRange(values.Select(v => (Input<T>)v));
public static implicit operator InputList<T>(Input<T>[] values)
=> ImmutableArray.CreateRange(values);
#endregion
#region construct from list
public static implicit operator InputList<T>(List<T> values)
=> ImmutableArray.CreateRange(values);
public static implicit operator InputList<T>(List<Output<T>> values)
=> ImmutableArray.CreateRange(values);
public static implicit operator InputList<T>(List<Input<T>> values)
=> ImmutableArray.CreateRange(values);
#endregion
#region construct from immutable array
public static implicit operator InputList<T>(ImmutableArray<T> values)
=> values.SelectAsArray(v => (Input<T>)v);
public static implicit operator InputList<T>(ImmutableArray<Output<T>> values)
=> values.SelectAsArray(v => (Input<T>)v);
public static implicit operator InputList<T>(ImmutableArray<Input<T>> values)
=> Output.All(values);
#endregion
#region construct from Output of some list type.
public static implicit operator InputList<T>(Output<T[]> values)
=> values.Apply(a => ImmutableArray.CreateRange(a));
public static implicit operator InputList<T>(Output<List<T>> values)
=> values.Apply(a => ImmutableArray.CreateRange(a));
public static implicit operator InputList<T>(Output<IEnumerable<T>> values)
=> values.Apply(a => ImmutableArray.CreateRange(a));
public static implicit operator InputList<T>(Output<ImmutableArray<T>> values)
=> new InputList<T>(values);
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator()
=> throw new NotSupportedException($"A {GetType().FullName} cannot be synchronously enumerated. Use {nameof(GetAsyncEnumerator)} instead.");
public async IAsyncEnumerator<Input<T>> GetAsyncEnumerator(CancellationToken cancellationToken)
{
var data = await _outputValue.GetValueAsync().ConfigureAwait(false);
foreach (var value in data)
{
yield return value;
}
}
#endregion
}
}

View file

@ -0,0 +1,100 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
namespace Pulumi
{
/// <summary>
/// A mapping of <see cref="string"/>s to values that can be passed in as the arguments to a
/// <see cref="Resource"/>. The individual values are themselves <see cref="Input{T}"/>s. i.e.
/// the individual values can be concrete values or <see cref="Output{T}"/>s.
/// <para/>
/// <see cref="InputMap{V}"/> differs from a normal <see cref="IDictionary{K,V}"/> in that it is
/// itself an <see cref="Input{T}"/>. For example, a <see cref="Resource"/> that accepts an
/// <see cref="InputMap{V}"/> will accept not just a dictionary but an <see cref="Output{T}"/>
/// of a dictionary as well. This is important for cases where the <see cref="Output{T}"/>
/// map from some <see cref="Resource"/> needs to be passed into another <see cref="Resource"/>.
/// Or for cases where creating the map invariably produces an <see cref="Output{T}"/> because
/// its resultant value is dependent on other <see cref="Output{T}"/>s.
/// <para/>
/// This benefit of <see cref="InputMap{V}"/> is also a limitation. Because it represents a
/// list of values that may eventually be created, there is no way to simply iterate over, or
/// access the elements of the map synchronously.
/// <para/>
/// <see cref="InputMap{V}"/> is designed to be easily used in object and collection
/// initializers. For example, a resource that accepts a map of values can be written easily in
/// this form:
/// <para/>
/// <code>
/// new SomeResource("name", new SomeResourceArgs {
/// MapProperty = {
/// { Key1, Value1 },
/// { Key2, Value2 },
/// { Key3, Value3 },
/// },
/// });
/// </code>
/// </summary>
public sealed class InputMap<V> : Input<ImmutableDictionary<string, V>>, IEnumerable, IAsyncEnumerable<Input<KeyValuePair<string, V>>>
{
public InputMap() : this(Output.Create(ImmutableDictionary<string, V>.Empty))
{
}
private InputMap(Output<ImmutableDictionary<string, V>> values)
: base(values)
{
}
public void Add(string key, Input<V> value)
{
var inputDictionary = (Input<ImmutableDictionary<string, V>>)_outputValue;
_outputValue = Output.Tuple(inputDictionary, value)
.Apply(x => x.Item1.Add(key, x.Item2));
}
public Input<V> this[string key]
{
set => Add(key, value);
}
#region construct from dictionary types
public static implicit operator InputMap<V>(Dictionary<string, V> values)
=> Output.Create(values);
public static implicit operator InputMap<V>(ImmutableDictionary<string, V> values)
=> Output.Create(values);
public static implicit operator InputMap<V>(Output<Dictionary<string, V>> values)
=> values.Apply(d => ImmutableDictionary.CreateRange(d));
public static implicit operator InputMap<V>(Output<IDictionary<string, V>> values)
=> values.Apply(d => ImmutableDictionary.CreateRange(d));
public static implicit operator InputMap<V>(Output<ImmutableDictionary<string, V>> values)
=> new InputMap<V>(values);
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator()
=> throw new NotSupportedException($"A {GetType().FullName} cannot be synchronously enumerated. Use {nameof(GetAsyncEnumerator)} instead.");
public async IAsyncEnumerator<Input<KeyValuePair<string, V>>> GetAsyncEnumerator(CancellationToken cancellationToken)
{
var data = await _outputValue.GetValueAsync().ConfigureAwait(false);
foreach (var value in data)
{
yield return value;
}
}
#endregion
}
}

View file

@ -0,0 +1,24 @@
// Copyright 2016-2019, Pulumi Corporation
namespace Pulumi
{
internal class Options
{
public readonly bool QueryMode;
public readonly int Parallel;
public readonly string? Pwd;
public readonly string Monitor;
public readonly string Engine;
public readonly string? Tracing;
public Options(bool queryMode, int parallel, string? pwd, string monitor, string engine, string? tracing)
{
QueryMode = queryMode;
Parallel = parallel;
Pwd = pwd;
Monitor = monitor;
Engine = engine;
Tracing = tracing;
}
}
}

View file

@ -0,0 +1,289 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Pulumi.Serialization;
namespace Pulumi
{
/// <summary>
/// Useful static utility methods for both creating and working wit <see cref="Output{T}"/>s.
/// </summary>
public static class Output
{
public static Output<T> Create<T>([MaybeNull]T value)
=> Create(Task.FromResult(value));
public static Output<T> Create<T>(Task<T> value)
=> Output<T>.Create(value);
public static Output<T> CreateSecret<T>([MaybeNull]T value)
=> CreateSecret(Task.FromResult(value));
public static Output<T> CreateSecret<T>(Task<T> value)
=> Output<T>.CreateSecret(value);
/// <summary>
/// Combines all the <see cref="Input{T}"/> values in <paramref name="inputs"/> and combines
/// them all into a single <see cref="Output{T}"/> with an <see cref="ImmutableArray{T}"/>
/// containing all their underlying values. If any of the <see cref="Input{T}"/>s are not
/// known, the final result will be not known. Similarly, if any of the <see
/// cref="Input{T}"/>s are secrets, then the final result will be a secret.
/// </summary>
public static Output<ImmutableArray<T>> All<T>(params Input<T>[] inputs)
=> All(ImmutableArray.CreateRange(inputs));
/// <summary>
/// <see cref="All{T}(Input{T}[])"/> for more details.
/// </summary>
public static Output<ImmutableArray<T>> All<T>(ImmutableArray<Input<T>> inputs)
=> Output<T>.All(inputs);
/// <summary>
/// <see cref="Tuple{X, Y, Z}(Input{X}, Input{Y}, Input{Z})"/> for more details.
/// </summary>
public static Output<(X, Y)> Tuple<X, Y>(Output<X> item1, Output<Y> item2)
=> Tuple((Input<X>)item1, (Input<Y>)item2);
/// <summary>
/// <see cref="Tuple{X, Y, Z}(Input{X}, Input{Y}, Input{Z})"/> for more details.
/// </summary>
public static Output<(X, Y)> Tuple<X, Y>(Input<X> item1, Input<Y> item2)
=> Tuple<X, Y, int>(item1, item2, 0).Apply(v => (v.Item1, v.Item2));
/// <summary>
/// Combines all the <see cref="Input{T}"/> values in the provided parameters and combines
/// them all into a single tuple containing each of their underlying values. If any of the
/// <see cref="Input{T}"/>s are not known, the final result will be not known. Similarly,
/// if any of the <see cref="Input{T}"/>s are secrets, then the final result will be a
/// secret.
/// </summary>
public static Output<(X, Y, Z)> Tuple<X, Y, Z>(Input<X> item1, Input<Y> item2, Input<Z> item3)
=> Output<(X, Y, Z)>.Tuple(item1, item2, item3);
/// <summary>
/// Takes in a <see cref="FormattableString"/> with potential <see cref="Input{T}"/>s or
/// <see cref="Output{T}"/> in the 'placeholder holes'. Conceptually, this method unwraps
/// all the underlying values in the holes, combines them appropriately with the <see
/// cref="FormattableString.Format"/> string, and produces an <see cref="Output{T}"/>
/// containing the final result.
/// <para/>
/// If any of the <see cref="Input{T}"/>s or <see cref="Output{T}"/>s are not known, the
/// final result will be not known. Similarly, if any of the <see cref="Input{T}"/>s or
/// <see cref="Output{T}"/>s are secrets, then the final result will be a secret.
/// </summary>
public static Output<string> Format(FormattableString formattableString)
{
var arguments = formattableString.GetArguments();
var inputs = new Input<object?>[arguments.Length];
for (var i = 0; i < arguments.Length; i++)
{
var arg = arguments[i];
inputs[i] = arg.ToObjectOutput();
}
return All(inputs).Apply(objs =>
string.Format(formattableString.Format, objs.ToArray()));
}
internal static Output<ImmutableArray<T>> Concat<T>(Output<ImmutableArray<T>> values1, Output<ImmutableArray<T>> values2)
=> Tuple(values1, values2).Apply(a => a.Item1.AddRange(a.Item2));
}
/// <summary>
/// Internal interface to allow our code to operate on outputs in an untyped manner. Necessary
/// as there is no reasonable way to write algorithms over heterogeneous instantiations of
/// generic types.
/// </summary>
internal interface IOutput
{
ImmutableHashSet<Resource> Resources { get; }
/// <summary>
/// Returns an <see cref="Output{T}"/> equivalent to this, except with our
/// <see cref="OutputData{X}.Value"/> casted to an object.
/// </summary>
Task<OutputData<object?>> GetDataAsync();
}
/// <summary>
/// <see cref="Output{T}"/>s are a key part of how Pulumi tracks dependencies between <see
/// cref="Resource"/>s. Because the values of outputs are not available until resources are
/// created, these are represented using the special <see cref="Output{T}"/>s type, which
/// internally represents two things:
/// <list type="number">
/// <item>An eventually available value of the output</item>
/// <item>The dependency on the source(s) of the output value</item>
/// </list>
/// In fact, <see cref="Output{T}"/>s is quite similar to <see cref="Task{TResult}"/>.
/// Additionally, they carry along dependency information.
/// <para/>
/// The output properties of all resource objects in Pulumi have type <see cref="Output{T}"/>.
/// </summary>
public sealed class Output<T> : IOutput
{
internal ImmutableHashSet<Resource> Resources { get; private set; }
internal Task<OutputData<T>> DataTask { get; private set; }
internal Output(ImmutableHashSet<Resource> resources, Task<OutputData<T>> dataTask)
{
Resources = resources;
DataTask = dataTask;
}
internal async Task<T> GetValueAsync()
{
var data = await DataTask.ConfigureAwait(false);
return data.Value;
}
ImmutableHashSet<Resource> IOutput.Resources => this.Resources;
async Task<OutputData<object?>> IOutput.GetDataAsync()
=> await DataTask.ConfigureAwait(false);
public static Output<T> Create(Task<T> value)
=> Create(value, isSecret: false);
internal static Output<T> CreateSecret(Task<T> value)
=> Create(value, isSecret: true);
private static Output<T> Create(Task<T> value, bool isSecret)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
var tcs = new TaskCompletionSource<OutputData<T>>();
value.Assign(tcs, t => OutputData.Create(t, isKnown: true, isSecret: isSecret));
return new Output<T>(ImmutableHashSet<Resource>.Empty, tcs.Task);
}
/// <summary>
/// <see cref="Apply{U}(Func{T, Output{U}})"/> for more details.
/// </summary>
public Output<U> Apply<U>(Func<T, U> func)
=> Apply(t => Output.Create(func(t)));
/// <summary>
/// <see cref="Apply{U}(Func{T, Output{U}})"/> for more details.
/// </summary>
public Output<U> Apply<U>(Func<T, Task<U>> func)
=> Apply(t => Output.Create(func(t)));
/// <summary>
/// Transforms the data of this <see cref="Output{T}"/> with the provided <paramref
/// name="func"/>. The result remains an <see cref="Output{T}"/> so that dependent resources
/// can be properly tracked.
/// <para/>
/// <paramref name="func"/> is not allowed to make resources.
/// <para/>
/// <paramref name="func"/> can return other <see cref="Output{T}"/>s. This can be handy if
/// you have an <c>Output&lt;SomeType&gt;</c> and you want to get a transitive dependency of
/// it. i.e.:
///
/// <code>
/// Output&lt;SomeType&gt; d1 = ...;
/// Output&lt;OtherType&gt; d2 = d1.Apply(v => v.OtherOutput); // getting an output off of 'v'.
/// </code>
///
/// In this example, taking a dependency on d2 means a resource will depend on all the resources
/// of d1. It will <b>not</b> depend on the resources of v.x.y.OtherDep.
/// <para/>
/// Importantly, the Resources that d2 feels like it will depend on are the same resources
/// as d1. If you need have multiple <see cref="Output{T}"/>s and a single <see
/// cref="Output{T}"/> is needed that combines both set of resources, then <see
/// cref="Output.All{T}(Input{T}[])"/> or <see cref="Output.Tuple{X, Y, Z}(Input{X}, Input{Y}, Input{Z})"/>
/// should be used instead.
/// <para/>
/// This function will only be called execution of a <c>pulumi up</c> request. It will not
/// run during <c>pulumi preview</c> (as the values of resources are of course not known
/// then).
/// </summary>
public Output<U> Apply<U>(Func<T, Output<U>> func)
=> new Output<U>(Resources, ApplyHelperAsync(DataTask, func));
private static async Task<OutputData<U>> ApplyHelperAsync<U>(
Task<OutputData<T>> dataTask, Func<T, Output<U>> func)
{
var data = await dataTask.ConfigureAwait(false);
// During previews only perform the apply if the engine was able to
// give us an actual value for this Output.
if (!data.IsKnown && Deployment.Instance.IsDryRun)
{
return new OutputData<U>(default!, isKnown: false, data.IsSecret);
}
var inner = func(data.Value);
var innerData = await inner.DataTask.ConfigureAwait(false);
return OutputData.Create(
innerData.Value, data.IsKnown && innerData.IsKnown, data.IsSecret || innerData.IsSecret);
}
internal static Output<ImmutableArray<T>> All(ImmutableArray<Input<T>> inputs)
=> new Output<ImmutableArray<T>>(GetAllResources(inputs), AllHelperAsync(inputs));
private static async Task<OutputData<ImmutableArray<T>>> AllHelperAsync(ImmutableArray<Input<T>> inputs)
{
var values = ImmutableArray.CreateBuilder<T>(inputs.Length);
var isKnown = true;
var isSecret = false;
foreach (var input in inputs)
{
var output = (Output<T>)input;
var data = await output.DataTask.ConfigureAwait(false);
values.Add(data.Value);
(isKnown, isSecret) = OutputData.Combine(data, isKnown, isSecret);
}
return OutputData.Create(values.MoveToImmutable(), isKnown, isSecret);
}
internal static Output<(X, Y, Z)> Tuple<X, Y, Z>(Input<X> item1, Input<Y> item2, Input<Z> item3)
=> new Output<(X, Y, Z)>(
GetAllResources(new IInput[] { item1, item2, item3 }),
TupleHelperAsync(item1, item2, item3));
private static ImmutableHashSet<Resource> GetAllResources(IEnumerable<IInput> inputs)
=> ImmutableHashSet.CreateRange(inputs.SelectMany(i => i.ToOutput().Resources));
private static async Task<OutputData<(X, Y, Z)>> TupleHelperAsync<X, Y, Z>(Input<X> item1, Input<Y> item2, Input<Z> item3)
{
(X, Y, Z) tuple;
var isKnown = true;
var isSecret = false;
{
var output = (Output<X>)item1;
var data = await output.DataTask.ConfigureAwait(false);
tuple.Item1 = data.Value;
(isKnown, isSecret) = OutputData.Combine(data, isKnown, isSecret);
}
{
var output = (Output<Y>)item2;
var data = await output.DataTask.ConfigureAwait(false);
tuple.Item2 = data.Value;
(isKnown, isSecret) = OutputData.Combine(data, isKnown, isSecret);
}
{
var output = (Output<Z>)item3;
var data = await output.DataTask.ConfigureAwait(false);
tuple.Item3 = data.Value;
(isKnown, isSecret) = OutputData.Combine(data, isKnown, isSecret);
}
return OutputData.Create(tuple, isKnown, isSecret);
}
}
}

View file

@ -0,0 +1,81 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
namespace Pulumi
{
/// <summary>
/// An automatically generated logical URN, used to stably identify resources. These are created
/// automatically by Pulumi to identify resources. They cannot be manually constructed.
/// </summary>
internal static class Urn
{
/// <summary>
/// Computes a URN from the combination of a resource name, resource type, optional parent,
/// optional project and optional stack.
/// </summary>
/// <returns></returns>
internal static Output<string> Create(
Input<string> name, Input<string> type,
Resource? parent, Input<string>? parentUrn,
Input<string>? project, Input<string>? stack)
{
if (parent != null && parentUrn != null)
throw new ArgumentException("Only one of 'parent' and 'parentUrn' can be non-null.");
Output<string> parentPrefix;
if (parent != null || parentUrn != null)
{
var parentUrnOutput = parent != null
? parent.Urn
: parentUrn!.ToOutput();
parentPrefix = parentUrnOutput.Apply(
parentUrnString => parentUrnString.Substring(
0, parentUrnString.LastIndexOf("::", StringComparison.Ordinal)) + "$");
}
else
{
parentPrefix = Output.Create($"urn:pulumi:{stack ?? Deployment.Instance.StackName}::{project ?? Deployment.Instance.ProjectName}::");
}
return Output.Format($"{parentPrefix}{type}::{name}");
}
/// <summary>
/// <see cref="InheritedChildAlias"/> computes the alias that should be applied to a child
/// based on an alias applied to it's parent. This may involve changing the name of the
/// resource in cases where the resource has a named derived from the name of the parent,
/// and the parent name changed.
/// </summary>
internal static Output<Alias> InheritedChildAlias(string childName, string parentName, Input<string> parentAlias, string childType)
{
// If the child name has the parent name as a prefix, then we make the assumption that
// it was constructed from the convention of using '{name}-details' as the name of the
// child resource. To ensure this is aliased correctly, we must then also replace the
// parent aliases name in the prefix of the child resource name.
//
// For example:
// * name: "newapp-function"
// * options.parent.__name: "newapp"
// * parentAlias: "urn:pulumi:stackname::projectname::awsx:ec2:Vpc::app"
// * parentAliasName: "app"
// * aliasName: "app-function"
// * childAlias: "urn:pulumi:stackname::projectname::aws:s3/bucket:Bucket::app-function"
var aliasName = Output.Create(childName);
if (childName!.StartsWith(parentName, StringComparison.Ordinal))
{
aliasName = parentAlias.ToOutput().Apply<string>(parentAliasUrn =>
{
return parentAliasUrn.Substring(parentAliasUrn.LastIndexOf("::", StringComparison.Ordinal) + 2)
+ childName.Substring(parentName.Length);
});
}
var urn = Create(
aliasName, childType, parent: null,
parentUrn: parentAlias, project: null, stack: null);
return urn.Apply(u => new Alias { Urn = u });
}
}
}

View file

@ -0,0 +1,155 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Threading;
using System.Threading.Tasks;
using Pulumirpc;
namespace Pulumi
{
public sealed partial class Deployment
{
private class Logger : ILogger
{
private readonly object _logGate = new object();
private readonly IDeploymentInternal _deployment;
private readonly Engine.EngineClient _engine;
// We serialize all logging tasks so that the engine doesn't hear about them out of order.
// This is necessary for streaming logs to be maintained in the right order.
private Task _lastLogTask = Task.CompletedTask;
private int _errorCount;
public Logger(IDeploymentInternal deployment, Engine.EngineClient engine)
{
_deployment = deployment;
_engine = engine;
}
public bool LoggedErrors
{
get
{
lock (_logGate)
{
return _errorCount > 0;
}
}
}
/// <summary>
/// Logs a debug-level message that is generally hidden from end-users.
/// </summary>
Task ILogger.DebugAsync(string message, Resource? resource, int? streamId, bool? ephemeral)
{
Serilog.Log.Debug(message);
return LogImplAsync(LogSeverity.Debug, message, resource, streamId, ephemeral);
}
/// <summary>
/// Logs an informational message that is generally printed to stdout during resource
/// operations.
/// </summary>
Task ILogger.InfoAsync(string message, Resource? resource, int? streamId, bool? ephemeral)
{
Serilog.Log.Information(message);
return LogImplAsync(LogSeverity.Info, message, resource, streamId, ephemeral);
}
/// <summary>
/// Warn logs a warning to indicate that something went wrong, but not catastrophically so.
/// </summary>
Task ILogger.WarnAsync(string message, Resource? resource, int? streamId, bool? ephemeral)
{
Serilog.Log.Warning(message);
return LogImplAsync(LogSeverity.Warning, message, resource, streamId, ephemeral);
}
/// <summary>
/// Error logs a fatal error to indicate that the tool should stop processing resource
/// operations immediately.
/// </summary>
Task ILogger.ErrorAsync(string message, Resource? resource, int? streamId, bool? ephemeral)
=> ErrorAsync(message, resource, streamId, ephemeral);
private Task ErrorAsync(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null)
{
Serilog.Log.Error(message);
return LogImplAsync(LogSeverity.Error, message, resource, streamId, ephemeral);
}
private Task LogImplAsync(LogSeverity severity, string message, Resource? resource, int? streamId, bool? ephemeral)
{
// Serialize our logging tasks so that streaming logs appear in order.
Task task;
lock (_logGate)
{
if (severity == LogSeverity.Error)
_errorCount++;
// Use a Task.Run here so that we don't end up aggressively running the actual
// logging while holding this lock.
_lastLogTask = _lastLogTask.ContinueWith(
_ => Task.Run(() => LogAsync(severity, message, resource, streamId, ephemeral)),
CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default).Unwrap();
task = _lastLogTask;
}
_deployment.Runner.RegisterTask($"Log: {severity}: {message}", task);
return task;
}
private async Task LogAsync(LogSeverity severity, string message, Resource? resource, int? streamId, bool? ephemeral)
{
try
{
var urn = await TryGetResourceUrnAsync(resource).ConfigureAwait(false);
await _engine.LogAsync(new LogRequest
{
Severity = severity,
Message = message,
Urn = urn,
StreamId = streamId ?? 0,
Ephemeral = ephemeral ?? false,
});
}
catch (Exception e)
{
lock (_logGate)
{
// mark that we had an error so that our top level process quits with an error
// code.
_errorCount++;
}
// we have a potential pathological case with logging. Consider if logging a
// message itself throws an error. If we then allow the error to bubble up, our top
// level handler will try to log that error, which can potentially lead to an error
// repeating unendingly. So, to prevent that from happening, we report a very specific
// exception that the top level can know about and handle specially.
throw new LogException(e);
}
}
private static async Task<string> TryGetResourceUrnAsync(Resource? resource)
{
if (resource != null)
{
try
{
return await resource.Urn.GetValueAsync().ConfigureAwait(false);
}
catch
{
// getting the urn for a resource may itself fail. in that case we don't want to
// fail to send an logging message. we'll just send the logging message unassociated
// with any resource.
}
}
return "";
}
}
}
}

View file

@ -0,0 +1,132 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Pulumi
{
public partial class Deployment
{
private class Runner : IRunner
{
private readonly IDeploymentInternal _deployment;
public Runner(IDeploymentInternal deployment)
=> _deployment = deployment;
public Task<int> RunAsync(Func<Task<IDictionary<string, object>>> func)
{
var stack = new Stack(func);
RegisterTask("User program code.", stack.Outputs.DataTask);
return WhileRunningAsync();
}
public void RegisterTask(string description, Task task)
{
lock (_taskToDescription)
{
_taskToDescription.Add(task, description);
}
}
// Keep track if we already logged the information about an unhandled error to the user. If
// so, we end with a different exit code. The language host recognizes this and will not print
// any further messages to the user since we already took care of it.
//
// 32 was picked so as to be very unlikely to collide with any other error codes.
private const int _processExitedAfterLoggingUserActionableMessage = 32;
private readonly Dictionary<Task, string> _taskToDescription = new Dictionary<Task, string>();
private async Task<int> WhileRunningAsync()
{
var tasks = new List<Task>();
// Keep looping as long as there are outstanding tasks that are still running.
while (true)
{
tasks.Clear();
lock (_taskToDescription)
{
if (_taskToDescription.Count == 0)
{
break;
}
// grab all the tasks we currently have running.
tasks.AddRange(_taskToDescription.Keys);
}
// Now, wait for one of them to finish.
var task = await Task.WhenAny(tasks).ConfigureAwait(false);
string description;
lock (_taskToDescription)
{
// once finished, remove it from the set of tasks that are running.
description = _taskToDescription[task];
_taskToDescription.Remove(task);
}
try
{
// Now actually await that completed task so that we will realize any exceptions
// is may have thrown.
await task.ConfigureAwait(false);
}
catch (Exception e)
{
// if it threw, report it as necessary, then quit.
return await HandleExceptionAsync(e).ConfigureAwait(false);
}
}
// there were no more tasks we were waiting on. Quit out, reporting if we had any
// errors or not.
return _deployment.Logger.LoggedErrors ? 1 : 0;
}
private async Task<int> HandleExceptionAsync(Exception exception)
{
if (exception is LogException)
{
// We got an error while logging itself. Nothing to do here but print some errors
// and fail entirely.
Serilog.Log.Error(exception, "Error occurred trying to send logging message to engine.");
await Console.Error.WriteLineAsync("Error occurred trying to send logging message to engine:\n" + exception).ConfigureAwait(false);
return 1;
}
// For the rest of the issue we encounter log the problem to the error stream. if we
// successfully do this, then return with a special error code stating as such so that
// our host doesn't print out another set of errors.
//
// Note: if these logging calls fail, they will just end up bubbling up an exception
// that will be caught by nothing. This will tear down the actual process with a
// non-zero error which our host will handle properly.
if (exception is RunException)
{
// Always hide the stack for RunErrors.
await _deployment.Logger.ErrorAsync(exception.Message).ConfigureAwait(false);
}
else if (exception is ResourceException resourceEx)
{
var message = resourceEx.HideStack
? resourceEx.Message
: resourceEx.ToString();
await _deployment.Logger.ErrorAsync(message, resourceEx.Resource).ConfigureAwait(false);
}
else
{
var location = System.Reflection.Assembly.GetEntryAssembly()?.Location;
await _deployment.Logger.ErrorAsync(
$@"Running program '{location}' failed with an unhandled exception:
{exception.ToString()}").ConfigureAwait(false);
}
Serilog.Log.Debug("Wrote last error. Returning from program.");
return _processExitedAfterLoggingUserActionableMessage;
}
}
}
}

View file

@ -0,0 +1,136 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Win32.SafeHandles;
using Pulumirpc;
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&lt;int&gt; 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
{
private static IDeployment? _instance;
/// <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>
public static IDeployment Instance
{
get => _instance ?? throw new InvalidOperationException("Trying to acquire Deployment.Instance before 'Run' was called.");
internal set => _instance = (value ?? throw new ArgumentNullException(nameof(value)));
}
internal static IDeploymentInternal InternalInstance
=> (IDeploymentInternal)Instance;
private readonly Options _options;
private readonly string _projectName;
private readonly string _stackName;
private readonly bool _isDryRun;
private readonly ILogger _logger;
private readonly IRunner _runner;
internal Engine.EngineClient Engine { get; }
internal ResourceMonitor.ResourceMonitorClient Monitor { get; }
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");
if (string.IsNullOrEmpty(monitor))
throw new InvalidOperationException("Environment did not contain: PULUMI_MONITOR");
if (string.IsNullOrEmpty(engine))
throw new InvalidOperationException("Environment did not contain: PULUMI_ENGINE");
if (string.IsNullOrEmpty(project))
throw new InvalidOperationException("Environment did not contain: PULUMI_PROJECT");
if (string.IsNullOrEmpty(stack))
throw new InvalidOperationException("Environment did not contain: PULUMI_STACK");
if (!bool.TryParse(dryRun, out var dryRunValue))
throw new InvalidOperationException("Environment did not contain a valid bool value for: PULUMI_DRY_RUN");
if (!bool.TryParse(queryMode, out var queryModeValue))
throw new InvalidOperationException("Environment did not contain a valid bool value for: PULUMI_QUERY_MODE");
if (!int.TryParse(parallel, out var parallelValue))
throw new InvalidOperationException("Environment did not contain a valid int value for: PULUMI_PARALLEL");
_isDryRun = dryRunValue;
_stackName = stack;
_projectName = project;
_options = new Options(
queryMode: queryModeValue, parallel: parallelValue, pwd: pwd,
monitor: monitor, engine: engine, tracing: tracing);
Serilog.Log.Debug("Creating Deployment Engine.");
this.Engine = new Engine.EngineClient(new Channel(engine, ChannelCredentials.Insecure));
Serilog.Log.Debug("Created Deployment Engine.");
Serilog.Log.Debug("Creating Deployment Monitor.");
this.Monitor = new ResourceMonitor.ResourceMonitorClient(new Channel(monitor, ChannelCredentials.Insecure));
Serilog.Log.Debug("Created Deployment Monitor.");
_runner = new Runner(this);
_logger = new Logger(this, this.Engine);
}
string IDeployment.ProjectName => _projectName;
string IDeployment.StackName => _stackName;
bool IDeployment.IsDryRun => _isDryRun;
Options IDeploymentInternal.Options => _options;
ILogger IDeploymentInternal.Logger => _logger;
IRunner IDeploymentInternal.Runner => _runner;
Stack IDeploymentInternal.Stack
{
get => Stack;
set => Stack = value;
}
}
}

View file

@ -0,0 +1,95 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
namespace Pulumi
{
public partial class Deployment
{
/// <summary>
/// Executes the provided <paramref name="action"/> and then completes all the
/// <see cref="IOutputCompletionSource"/> sources on the <paramref name="resource"/> with
/// the results of it.
/// </summary>
private Task CompleteResourceAsync(
Resource resource, Func<Task<(string urn, string id, Struct data)>> action)
{
// IMPORTANT! This function is Task-returning, but must not actually be `async` itself.
// We have to make sure we run 'OutputCompletionSource.GetSources' synchronously
// directly when `resource`'s constructor runs since this will set all of the
// `[Output(...)] Output<T>` properties. We need those properties assigned by the time
// the base 'Resource' constructor finishes so that both derived classes and external
// consumers can use the Output properties of `resource`.
var completionSources = OutputCompletionSource.GetSources(resource);
return CompleteResourceAsync(resource, action, completionSources);
}
private async Task CompleteResourceAsync(
Resource resource, Func<Task<(string urn, string id, Struct data)>> action,
ImmutableDictionary<string, IOutputCompletionSource> completionSources)
{
// Run in a try/catch/finally so that we always resolve all the outputs of the resource
// regardless of whether we encounter an errors computing the action.
try
{
var response = await action().ConfigureAwait(false);
completionSources[Constants.UrnPropertyName].SetStringValue(response.urn, isKnown: true);
if (resource is CustomResource customResource)
{
var id = response.id ?? "";
completionSources[Constants.IdPropertyName].SetStringValue(id, isKnown: id != "");
}
// Go through all our output fields and lookup a corresponding value in the response
// object. Allow the output field to deserialize the response.
foreach (var (fieldName, completionSource) in completionSources)
{
if (fieldName == Constants.UrnPropertyName || fieldName == Constants.IdPropertyName)
{
// Already handled specially above.
continue;
}
// We process and deserialize each field (instead of bulk processing
// 'response.data' so that each field can have independent isKnown/isSecret
// values. We do not want to bubble up isKnown/isSecret from one field to the
// rest.
if (response.data.Fields.TryGetValue(fieldName, out var value))
{
var converted = Converter.ConvertValue(
$"{resource.GetType().FullName}.{fieldName}", value, completionSource.TargetType);
completionSource.SetValue(converted);
}
}
}
catch (Exception e)
{
// Mark any unresolved output properties with this exception. That way we don't
// leave any outstanding tasks sitting around which might cause hangs.
foreach (var source in completionSources.Values)
{
source.TrySetException(e);
}
throw;
}
finally
{
// ensure that we've at least resolved all our completion sources. That way we
// don't leave any outstanding tasks sitting around which might cause hangs.
foreach (var source in completionSources.Values)
{
// Didn't get a value for this field. Resolve it with a default value.
// If we're in preview, we'll consider this unknown and in a normal
// update we'll consider it known.
source.TrySetDefaultResult(isKnown: !_isDryRun);
}
}
}
}
}

View file

@ -0,0 +1,70 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Immutable;
using System.Text.Json;
namespace Pulumi
{
public partial class Deployment
{
/// <summary>
/// The environment variable key that the language plugin uses to set configuration values.
/// </summary>
private const string _configEnvKey = "PULUMI_CONFIG";
/// <summary>
/// Returns a copy of the full config map.
/// </summary>
internal ImmutableDictionary<string, string> AllConfig { get; private set; } = ParseConfig();
/// <summary>
/// Sets a configuration variable.
/// </summary>
internal void SetConfig(string key, string value)
=> AllConfig = AllConfig.Add(key, value);
/// <summary>
/// Returns a configuration variable's value or <see langword="null"/> if it is unset.
/// </summary>
string? IDeploymentInternal.GetConfig(string key)
=> AllConfig.TryGetValue(key, out var value) ? value : null;
private static ImmutableDictionary<string, string> ParseConfig()
{
var parsedConfig = ImmutableDictionary.CreateBuilder<string, string>();
var envConfig = Environment.GetEnvironmentVariable(_configEnvKey);
if (envConfig != null)
{
var envObject = JsonDocument.Parse(envConfig);
foreach (var prop in envObject.RootElement.EnumerateObject())
{
parsedConfig[CleanKey(prop.Name)] = prop.Value.ToString();
}
}
return parsedConfig.ToImmutable();
}
/// <summary>
/// CleanKey takes a configuration key, and if it is of the form "(string):config:(string)"
/// removes the ":config:" portion. Previously, our keys always had the string ":config:" in
/// them, and we'd like to remove it. However, the language host needs to continue to set it
/// so we can be compatible with older versions of our packages. Once we stop supporting
/// older packages, we can change the language host to not add this :config: thing and
/// remove this function.
/// </summary>
private static string CleanKey(string key)
{
var idx = key.IndexOf(":", StringComparison.Ordinal);
if (idx > 0 && key.Substring(idx + 1).StartsWith("config:", StringComparison.Ordinal))
{
return key.Substring(0, idx) + ":" + key.Substring(idx + 1 + "config:".Length);
}
return key;
}
}
}

View file

@ -0,0 +1,77 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
using Pulumirpc;
namespace Pulumi
{
public sealed partial class Deployment
{
Task IDeployment.InvokeAsync(string token, ResourceArgs args, InvokeOptions? options)
=> InvokeAsync<object>(token, args, options, convertResult: false);
Task<T> IDeployment.InvokeAsync<T>(string token, ResourceArgs args, InvokeOptions? options)
=> InvokeAsync<T>(token, args, options, convertResult: true);
private async Task<T> InvokeAsync<T>(
string token, ResourceArgs args, InvokeOptions? options, bool convertResult)
{
var label = $"Invoking function: token={token} asynchronously";
Log.Debug(label);
// Wait for all values to be available, and then perform the RPC.
var argsDict = await args.ToDictionaryAsync().ConfigureAwait(false);
var serialized = await SerializeAllPropertiesAsync($"invoke:{token}", argsDict);
Log.Debug($"Invoke RPC prepared: token={token}" +
(_excessiveDebugOutput ? $", obj={serialized}" : ""));
var provider = await ProviderResource.RegisterAsync(GetProvider(token, options)).ConfigureAwait(false);
var result = await this.Monitor.InvokeAsync(new InvokeRequest
{
Tok = token,
Provider = provider ?? "",
Version = options?.Version ?? "",
Args = serialized,
});
if (result.Failures.Count > 0)
{
var reasons = "";
foreach (var reason in result.Failures)
{
if (reasons != "")
{
reasons += "; ";
}
reasons += $"{reason.Reason} ({reason.Property})";
}
throw new InvokeException($"Invoke of '{token}' failed: {reasons}");
}
if (!convertResult)
{
return default!;
}
var data = Converter.ConvertValue<T>($"{token} result", new Value { StructValue = result.Return });
return data.Value;
}
private static ProviderResource? GetProvider(string token, InvokeOptions? options)
=> options?.Provider ?? options?.Parent?.GetProvider(token);
private class InvokeException : Exception
{
public InvokeException(string error)
: base(error)
{
}
}
}
}

View file

@ -0,0 +1,173 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
namespace Pulumi
{
public partial class Deployment
{
private async Task<PrepareResult> PrepareResourceAsync(
string label, Resource res, bool custom,
ResourceArgs args, ResourceOptions options)
{
/* IMPORTANT! We should never await prior to this line, otherwise the Resource will be partly uninitialized. */
// Before we can proceed, all our dependencies must be finished.
var type = res.GetResourceType();
var name = res.GetResourceName();
Log.Debug($"Gathering explicit dependencies: t={type}, name={name}, custom={custom}");
var explicitDirectDependencies = new HashSet<Resource>(
await GatherExplicitDependenciesAsync(options.DependsOn).ConfigureAwait(false));
Log.Debug($"Gathered explicit dependencies: t={type}, name={name}, custom={custom}");
// Serialize out all our props to their final values. In doing so, we'll also collect all
// the Resources pointed to by any Dependency objects we encounter, adding them to 'propertyDependencies'.
Log.Debug($"Serializing properties: t={type}, name={name}, custom={custom}");
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
var (serializedProps, propertyToDirectDependencies) =
await SerializeResourcePropertiesAsync(label, dictionary).ConfigureAwait(false);
Log.Debug($"Serialized properties: t={type}, name={name}, custom={custom}");
// Wait for the parent to complete.
// If no parent was provided, parent to the root resource.
Log.Debug($"Getting parent urn: t={type}, name={name}, custom={custom}");
var parentURN = options.Parent != null
? await options.Parent.Urn.GetValueAsync().ConfigureAwait(false)
: await GetRootResourceAsync(type).ConfigureAwait(false);
Log.Debug($"Got parent urn: t={type}, name={name}, custom={custom}");
string? providerRef = null;
if (custom)
{
var customOpts = options as CustomResourceOptions;
providerRef = await ProviderResource.RegisterAsync(customOpts?.Provider).ConfigureAwait(false);
}
// Collect the URNs for explicit/implicit dependencies for the engine so that it can understand
// the dependency graph and optimize operations accordingly.
// The list of all dependencies (implicit or explicit).
var allDirectDependencies = new HashSet<Resource>(explicitDirectDependencies);
var allDirectDependencyURNs = await GetAllTransitivelyReferencedCustomResourceURNsAsync(explicitDirectDependencies).ConfigureAwait(false);
var propertyToDirectDependencyURNs = new Dictionary<string, HashSet<string>>();
foreach (var (propertyName, directDependencies) in propertyToDirectDependencies)
{
allDirectDependencies.AddRange(directDependencies);
var urns = await GetAllTransitivelyReferencedCustomResourceURNsAsync(directDependencies).ConfigureAwait(false);
allDirectDependencyURNs.AddRange(urns);
propertyToDirectDependencyURNs[propertyName] = urns;
}
// Wait for all aliases. Note that we use 'res._aliases' instead of 'options.aliases' as
// the former has been processed in the Resource constructor prior to calling
// 'registerResource' - both adding new inherited aliases and simplifying aliases down
// to URNs.
var aliases = new List<string>();
var uniqueAliases = new HashSet<string>();
foreach (var alias in res._aliases)
{
var aliasVal = await alias.ToOutput().GetValueAsync().ConfigureAwait(false);
if (!uniqueAliases.Add(aliasVal))
{
aliases.Add(aliasVal);
}
}
return new PrepareResult(
serializedProps,
parentURN,
providerRef,
allDirectDependencyURNs,
propertyToDirectDependencyURNs,
aliases);
}
private static Task<ImmutableArray<Resource>> GatherExplicitDependenciesAsync(InputList<Resource> resources)
=> resources.ToOutput().GetValueAsync();
private static async Task<HashSet<string>> GetAllTransitivelyReferencedCustomResourceURNsAsync(
HashSet<Resource> resources)
{
// Go through 'resources', but transitively walk through **Component** resources,
// collecting any of their child resources. This way, a Component acts as an
// aggregation really of all the reachable custom resources it parents. This walking
// will transitively walk through other child ComponentResources, but will stop when it
// hits custom resources. in other words, if we had:
//
// Comp1
// / \
// Cust1 Comp2
// / \
// Cust2 Cust3
// /
// Cust4
//
// Then the transitively reachable custom resources of Comp1 will be [Cust1, Cust2,
// Cust3]. It will *not* include 'Cust4'.
// To do this, first we just get the transitively reachable set of resources (not diving
// into custom resources). In the above picture, if we start with 'Comp1', this will be
// [Comp1, Cust1, Comp2, Cust2, Cust3]
var transitivelyReachableResources = GetTransitivelyReferencedChildResourcesOfComponentResources(resources);
var transitivelyReachableCustomResources = transitivelyReachableResources.OfType<CustomResource>();
var tasks = transitivelyReachableCustomResources.Select(r => r.Urn.GetValueAsync());
var urns = await Task.WhenAll(tasks).ConfigureAwait(false);
return new HashSet<string>(urns);
}
/// <summary>
/// Recursively walk the resources passed in, returning them and all resources reachable from
/// <see cref="Resource.ChildResources"/> through any **Component** resources we encounter.
/// </summary>
private static HashSet<Resource> GetTransitivelyReferencedChildResourcesOfComponentResources(HashSet<Resource> resources)
{
// Recursively walk the dependent resources through their children, adding them to the result set.
var result = new HashSet<Resource>();
AddTransitivelyReferencedChildResourcesOfComponentResources(resources, result);
return result;
}
private static void AddTransitivelyReferencedChildResourcesOfComponentResources(HashSet<Resource> resources, HashSet<Resource> result)
{
foreach (var resource in resources)
{
if (result.Add(resource))
{
if (resource is ComponentResource)
{
AddTransitivelyReferencedChildResourcesOfComponentResources(resource.ChildResources, result);
}
}
}
}
private struct PrepareResult
{
public readonly Struct SerializedProps;
public readonly string? ParentUrn;
public readonly string? ProviderRef;
public readonly HashSet<string> AllDirectDependencyURNs;
public readonly Dictionary<string, HashSet<string>> PropertyToDirectDependencyURNs;
public readonly List<string> Aliases;
public PrepareResult(Struct serializedProps, string? parentUrn, string? providerRef, HashSet<string> allDirectDependencyURNs, Dictionary<string, HashSet<string>> propertyToDirectDependencyURNs, List<string> aliases)
{
SerializedProps = serializedProps;
ParentUrn = parentUrn;
ProviderRef = providerRef;
AllDirectDependencyURNs = allDirectDependencyURNs;
PropertyToDirectDependencyURNs = propertyToDirectDependencyURNs;
Aliases = aliases;
}
}
}
}

View file

@ -0,0 +1,70 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
using Pulumirpc;
namespace Pulumi
{
public partial class Deployment
{
void IDeploymentInternal.ReadResource(
Resource resource, ResourceArgs args, ResourceOptions options)
{
// ReadResource is called in a fire-and-forget manner. Make sure we keep track of
// this task so that the application will not quit until this async work completes.
//
// Also, we can only do our work once the constructor for the resource has actually
// finished. Otherwise, we might actually read and get the result back *prior* to
// the object finishing initializing. Note: this is not a speculative concern. This is
// something that does happen and has to be accounted for.
_runner.RegisterTask(
$"{nameof(IDeploymentInternal.ReadResource)}: {resource.GetResourceType()}-{resource.GetResourceName()}",
CompleteResourceAsync(resource, () => ReadResourceAsync(resource, args, options)));
}
private async Task<(string urn, string id, Struct data)> ReadResourceAsync(Resource resource, ResourceArgs args, ResourceOptions options)
{
var id = options.Id;
if (options.Id == null)
{
throw new InvalidOperationException("Cannot read resource whose options are lacking an ID value");
}
var name = resource.GetResourceName();
var type = resource.GetResourceType();
var label = $"resource:{name}[{type}]#...";
Log.Debug($"Reading resource: id={(id is IOutput ? "Output<T>" : id)}, t=${type}, name=${name}");
var prepareResult = await this.PrepareResourceAsync(
label, resource, custom: true, args, options).ConfigureAwait(false);
var serializer = new Serializer(_excessiveDebugOutput);
var resolvedID = (string)(await serializer.SerializeAsync(label, id).ConfigureAwait(false))!;
Log.Debug($"ReadResource RPC prepared: id={resolvedID}, t={type}, name={name}" +
(_excessiveDebugOutput ? $", obj={prepareResult.SerializedProps}" : ""));
// Create a resource request and do the RPC.
var request = new ReadResourceRequest
{
Type = type,
Name = name,
Id = resolvedID,
Parent = prepareResult.ParentUrn,
Provider = prepareResult.ProviderRef,
Properties = prepareResult.SerializedProps,
Version = options?.Version ?? "",
AcceptSecrets = true,
};
request.Dependencies.AddRange(prepareResult.AllDirectDependencyURNs);
// Now run the operation, serializing the invocation if necessary.
var response = await this.Monitor.ReadResourceAsync(request);
return (response.Urn, resolvedID, response.Properties);
}
}
}

View file

@ -0,0 +1,118 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumirpc;
namespace Pulumi
{
public partial class Deployment
{
void IDeploymentInternal.RegisterResource(
Resource resource, bool custom, ResourceArgs args, ResourceOptions options)
{
// RegisterResource is called in a fire-and-forget manner. Make sure we keep track of
// this task so that the application will not quit until this async work completes.
//
// Also, we can only do our work once the constructor for the resource has actually
// finished. Otherwise, we might actually register and get the result back *prior* to
// the object finishing initializing. Note: this is not a speculative concern. This is
// something that does happen and has to be accounted for.
this._runner.RegisterTask(
$"{nameof(IDeploymentInternal.RegisterResource)}: {resource.GetResourceType()}-{resource.GetResourceName()}",
CompleteResourceAsync(resource, () => RegisterResourceAsync(resource, custom, args, options)));
}
private async Task<(string urn, string id, Struct data)> RegisterResourceAsync(
Resource resource, bool custom,
ResourceArgs args, ResourceOptions options)
{
var name = resource.GetResourceName();
var type = resource.GetResourceType();
var label = $"resource:{name}[{type}]";
Log.Debug($"Registering resource start: t={type}, name={name}, custom={custom}");
var request = CreateRegisterResourceRequest(type, name, custom, options);
Log.Debug($"Preparing resource: t={type}, name={name}, custom={custom}");
var prepareResult = await PrepareResourceAsync(label, resource, custom, args, options).ConfigureAwait(false);
Log.Debug($"Prepared resource: t={type}, name={name}, custom={custom}");
PopulateRequest(request, prepareResult);
Log.Debug($"Registering resource monitor start: t={type}, name={name}, custom={custom}");
var result = await this.Monitor.RegisterResourceAsync(request);
Log.Debug($"Registering resource monitor end: t={type}, name={name}, custom={custom}");
return (result.Urn, result.Id, result.Object);
}
private static void PopulateRequest(RegisterResourceRequest request, PrepareResult prepareResult)
{
request.Object = prepareResult.SerializedProps;
request.Parent = prepareResult.ParentUrn ?? "";
request.Provider = prepareResult.ProviderRef ?? "";
request.Aliases.AddRange(prepareResult.Aliases);
request.Dependencies.AddRange(prepareResult.AllDirectDependencyURNs);
foreach (var (key, resourceURNs) in prepareResult.PropertyToDirectDependencyURNs)
{
var deps = new RegisterResourceRequest.Types.PropertyDependencies();
deps.Urns.AddRange(resourceURNs);
request.PropertyDependencies.Add(key, deps);
}
}
private static RegisterResourceRequest CreateRegisterResourceRequest(string type, string name, bool custom, ResourceOptions options)
{
var customOpts = options as CustomResourceOptions;
var deleteBeforeReplace = customOpts?.DeleteBeforeReplace;
var request = new RegisterResourceRequest()
{
Type = type,
Name = name,
Custom = custom,
Protect = options.Protect ?? false,
Version = options.Version ?? "",
ImportId = customOpts?.ImportId ?? "",
AcceptSecrets = true,
DeleteBeforeReplace = deleteBeforeReplace ?? false,
DeleteBeforeReplaceDefined = deleteBeforeReplace != null,
CustomTimeouts = new RegisterResourceRequest.Types.CustomTimeouts
{
Create = TimeoutString(options.CustomTimeouts?.Create),
Delete = TimeoutString(options.CustomTimeouts?.Delete),
Update = TimeoutString(options.CustomTimeouts?.Update),
},
};
if (customOpts != null)
request.AdditionalSecretOutputs.AddRange(customOpts.AdditionalSecretOutputs);
request.IgnoreChanges.AddRange(options.IgnoreChanges);
return request;
}
private static string TimeoutString(TimeSpan? timeSpan)
{
if (timeSpan == null)
return "";
// This will eventually be parsed by go's ParseDuration function here:
// https://github.com/pulumi/pulumi/blob/06d4dde8898b2a0de2c3c7ff8e45f97495b89d82/pkg/resource/deploy/source_eval.go#L967
//
// So we generate a legal duration as allowed by
// https://golang.org/pkg/time/#ParseDuration.
//
// Simply put, we simply convert our ticks to the integral number of nanoseconds
// corresponding to it. Since each tick is 100ns, this can trivialy be done just by
// appending "00" to it.
return timeSpan.Value.Ticks.ToString() + "00ns";
}
}
}

View file

@ -0,0 +1,44 @@
// Copyright 2016-2018, Pulumi Corporation
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf;
using Pulumirpc;
namespace Pulumi
{
public partial class Deployment
{
void IDeploymentInternal.RegisterResourceOutputs(Resource resource, Output<IDictionary<string, object>> outputs)
{
// RegisterResourceOutputs is called in a fire-and-forget manner. Make sure we keep track of
// this task so that the application will not quit until this async work completes.
_runner.RegisterTask(
$"{nameof(IDeploymentInternal.RegisterResourceOutputs)}: {resource.GetResourceType()}-{resource.GetResourceName()}",
RegisterResourceOutputsAsync(resource, outputs));
}
private async Task RegisterResourceOutputsAsync(
Resource resource, Output<IDictionary<string, object>> outputs)
{
var opLabel = $"monitor.registerResourceOutputs(...)";
// The registration could very well still be taking place, so we will need to wait for its URN.
// Additionally, the output properties might have come from other resources, so we must await those too.
var urn = await resource.Urn.GetValueAsync().ConfigureAwait(false);
var props = await outputs.GetValueAsync().ConfigureAwait(false);
var propInputs = props.ToDictionary(kvp => kvp.Key, kvp => (IInput?)(Input<object?>)kvp.Value.ToObjectOutput());
var serialized = await SerializeAllPropertiesAsync(opLabel, propInputs).ConfigureAwait(false);
Log.Debug($"RegisterResourceOutputs RPC prepared: urn={urn}" +
(_excessiveDebugOutput ? $", outputs ={JsonFormatter.Default.Format(serialized)}" : ""));
await Monitor.RegisterResourceOutputsAsync(new RegisterResourceOutputsRequest()
{
Urn = urn,
Outputs = serialized,
});
}
}
}

View file

@ -0,0 +1,53 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Threading.Tasks;
using Pulumirpc;
namespace Pulumi
{
public partial class Deployment
{
private Task<string>? _rootResource;
/// <summary>
/// Returns a root resource URN that will automatically become the default parent of all
/// resources. This can be used to ensure that all resources without explicit parents are
/// parented to a common parent resource.
/// </summary>
/// <returns></returns>
internal async Task<string?> GetRootResourceAsync(string type)
{
// If we're calling this while creating the stack itself. No way to know its urn at
// this point.
if (type == Stack._rootPulumiStackTypeName)
return null;
if (_rootResource == null)
throw new InvalidOperationException($"Calling {nameof(GetRootResourceAsync)} before the root resource was registered!");
return await _rootResource.ConfigureAwait(false);
}
Task IDeploymentInternal.SetRootResourceAsync(Stack stack)
{
if (_rootResource != null)
throw new InvalidOperationException("Tried to set the root resource more than once!");
_rootResource = SetRootResourceWorkerAsync(stack);
return _rootResource;
}
private async Task<string> SetRootResourceWorkerAsync(Stack stack)
{
var resUrn = await stack.Urn.GetValueAsync().ConfigureAwait(false);
await this.Engine.SetRootResourceAsync(new SetRootResourceRequest
{
Urn = resUrn,
});
var getResponse = await this.Engine.GetRootResourceAsync(new GetRootResourceRequest());
return getResponse.Urn;
}
}
}

View file

@ -0,0 +1,74 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace Pulumi
{
public partial class Deployment
{
/// <summary>
/// <see cref="RunAsync(Func{Task{IDictionary{string, object}}})"/> for more details.
/// </summary>
public static Task<int> RunAsync(Action action)
=> RunAsync(() =>
{
action();
return ImmutableDictionary<string, object>.Empty;
});
/// <summary>
/// <see cref="RunAsync(Func{Task{IDictionary{string, object}}})"/> for more details.
/// </summary>
/// <param name="func"></param>
/// <returns></returns>
public static Task<int> RunAsync(Func<IDictionary<string, object>> func)
=> RunAsync(() => Task.FromResult(func()));
/// <summary>
/// <see cref="RunAsync(Func{Task{IDictionary{string, object}}})"/> 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&lt;int&gt; 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.
/// <para/>
/// The function passed to <see cref="RunAsync(Func{Task{IDictionary{string, object}}})"/>
/// can optionally return an <see cref="IDictionary{TKey, TValue}"/>. The keys and values
/// in this dictionary will become the outputs for the Pulumi Stack that is created.
/// </summary>
public static Task<int> RunAsync(Func<Task<IDictionary<string, object>>> func)
{
// Serilog.Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger();
Serilog.Log.Debug("Deployment.Run called.");
if (_instance != null)
{
throw new NotSupportedException("Deployment.Run can only be called a single time.");
}
Serilog.Log.Debug("Creating new Deployment.");
var deployment = new Deployment();
Instance = deployment;
return deployment._runner.RunAsync(func);
}
}
}

View file

@ -0,0 +1,92 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Pulumi.Serialization;
namespace Pulumi
{
public partial class Deployment
{
internal static bool _excessiveDebugOutput = true;
/// <summary>
/// <see cref="SerializeResourcePropertiesAsync"/> walks the props object passed in,
/// awaiting all interior promises besides those for <see cref="Resource.Urn"/> and <see
/// cref="CustomResource.Id"/>, creating a reasonable POCO object that can be remoted over
/// to registerResource.
/// </summary>
private static Task<SerializationResult> SerializeResourcePropertiesAsync(
string label, IDictionary<string, IInput?> args)
{
return SerializeFilteredPropertiesAsync(
label, Output.Create(args), key => key != Constants.IdPropertyName && key != Constants.UrnPropertyName);
}
private static async Task<Struct> SerializeAllPropertiesAsync(
string label, Input<IDictionary<string, IInput?>> args)
{
var result = await SerializeFilteredPropertiesAsync(label, args, _ => true).ConfigureAwait(false);
return result.Serialized;
}
/// <summary>
/// <see cref="SerializeFilteredPropertiesAsync"/> walks the props object passed in,
/// awaiting all interior promises for properties with keys that match the provided filter,
/// creating a reasonable POCO object that can be remoted over to registerResource.
/// </summary>
private static async Task<SerializationResult> SerializeFilteredPropertiesAsync(
string label, Input<IDictionary<string, IInput?>> args, Predicate<string> acceptKey)
{
var props = await args.ToOutput().GetValueAsync().ConfigureAwait(false);
var propertyToDependentResources = ImmutableDictionary.CreateBuilder<string, HashSet<Resource>>();
var result = ImmutableDictionary.CreateBuilder<string, object>();
foreach (var (key, input) in props)
{
if (acceptKey(key))
{
// We treat properties with null values as if they do not exist.
var serializer = new Serializer(_excessiveDebugOutput);
var v = await serializer.SerializeAsync($"{label}.{key}", input).ConfigureAwait(false);
if (v != null)
{
result[key] = v;
propertyToDependentResources[key] = serializer.DependentResources;
}
}
}
return new SerializationResult(
Serializer.CreateStruct(result.ToImmutable()),
propertyToDependentResources.ToImmutable());
}
private struct SerializationResult
{
public readonly Struct Serialized;
public readonly ImmutableDictionary<string, HashSet<Resource>> PropertyToDependentResources;
public SerializationResult(
Struct result,
ImmutableDictionary<string, HashSet<Resource>> propertyToDependentResources)
{
Serialized = result;
PropertyToDependentResources = propertyToDependentResources;
}
public void Deconstruct(
out Struct serialized,
out ImmutableDictionary<string, HashSet<Resource>> propertyToDependentResources)
{
serialized = Serialized;
propertyToDependentResources = PropertyToDependentResources;
}
}
}
}

View file

@ -0,0 +1,42 @@
// Copyright 2016-2018, Pulumi Corporation
using System.Threading.Tasks;
namespace Pulumi
{
public interface IDeployment
{
/// <summary>
/// Returns the current stack name.
/// </summary>
string StackName { get; }
/// <summary>
/// Returns the current project name.
/// </summary>
string ProjectName { get; }
/// <summary>
/// Whether or not the application is currently being previewed or actually applied.
/// </summary>
bool IsDryRun { get; }
/// <summary>
/// Dynamically invokes the function '<paramref name="token"/>', which is offered by a
/// provider plugin.
/// <para/>
/// The result of <see cref="InvokeAsync"/> will be a <see cref="Task"/> resolved to the
/// result value of the provider plugin.
/// <para/>
/// The <paramref name="args"/> inputs can be a bag of computed values(including, `T`s,
/// <see cref="Task{TResult}"/>s, <see cref="Output{T}"/>s etc.).
/// </summary>
Task<T> InvokeAsync<T>(string token, ResourceArgs args, InvokeOptions? options = null);
/// <summary>
/// Same as <see cref="InvokeAsync{T}(string, ResourceArgs, InvokeOptions)"/>, however the
/// return value is ignored.
/// </summary>
Task InvokeAsync(string token, ResourceArgs args, InvokeOptions? options = null);
}
}

View file

@ -0,0 +1,25 @@
// Copyright 2016-2018, Pulumi Corporation
using System.Collections.Generic;
using System.Threading.Tasks;
using Pulumirpc;
namespace Pulumi
{
internal interface IDeploymentInternal : IDeployment
{
Options Options { get; }
string? GetConfig(string fullKey);
Stack Stack { get; set; }
ILogger Logger { get; }
IRunner Runner { get; }
Task SetRootResourceAsync(Stack stack);
void ReadResource(Resource resource, ResourceArgs args, ResourceOptions opts);
void RegisterResource(Resource resource, bool custom, ResourceArgs args, ResourceOptions opts);
void RegisterResourceOutputs(Resource resource, Output<IDictionary<string, object>> outputs);
}
}

View file

@ -0,0 +1,16 @@
// Copyright 2016-2018, Pulumi Corporation
using System.Threading.Tasks;
namespace Pulumi
{
internal interface ILogger
{
bool LoggedErrors { get; }
Task DebugAsync(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null);
Task InfoAsync(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null);
Task WarnAsync(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null);
Task ErrorAsync(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null);
}
}

View file

@ -0,0 +1,14 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Pulumi
{
internal interface IRunner
{
void RegisterTask(string description, Task task);
Task<int> RunAsync(Func<Task<IDictionary<string, object>>> func);
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2016-2018, Pulumi Corporation
namespace Pulumi
{
/// <summary>
/// Options to help control the behavior of <see cref="IDeployment.InvokeAsync{T}(string,
/// ResourceArgs, InvokeOptions)"/>.
/// </summary>
public class InvokeOptions
{
/// <summary>
/// An optional parent to use for default options for this invoke (e.g. the default provider
/// to use).
/// </summary>
public Resource? Parent { get; set; }
/// <summary>
/// An optional provider to use for this invocation. If no provider is supplied, the default
/// provider for the invoked function's package will be used.
/// </summary>
public ProviderResource? Provider { get; set; }
/// <summary>
/// An optional version, corresponding to the version of the provider plugin that should be
/// used when performing this invoke.
/// </summary>
public string? Version { get; set; }
}
}

View file

@ -0,0 +1,19 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
namespace Pulumi
{
/// <summary>
/// Special exception we throw if we had a problem actually logging a message to the engine
/// error rpc endpoint. In this case, we have no choice but to tear ourselves down reporting
/// whatever we can to the console instead.
/// </summary>
internal class LogException : Exception
{
public LogException(Exception exception)
: base("Error occurred during logging", exception)
{
}
}
}

View file

@ -0,0 +1,24 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
namespace Pulumi
{
/// <summary>
/// ResourceException can be used for terminating a program abruptly, specifically associating the
/// problem with a Resource.Depending on the nature of the problem, clients can choose whether
/// or not a call stack should be returned as well. This should be very rare, and would only
/// indicate no usefulness of presenting that stack to the user.
/// </summary>
public class ResourceException : Exception
{
internal readonly Resource? Resource;
internal readonly bool HideStack;
public ResourceException(string message, Resource? resource, bool hideStack = false) : base(message)
{
this.Resource = resource;
this.HideStack = hideStack;
}
}
}

View file

@ -0,0 +1,26 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
namespace Pulumi
{
/// <summary>
/// RunException can be used for terminating a program abruptly, but resulting in a clean exit
/// rather than the usual verbose unhandled error logic which emits the source program text and
/// complete stack trace. This type should be rarely used. Ideally <see
/// cref="ResourceException"/> should always be used so that as many errors as possible can be
/// associated with a Resource.
/// </summary>
internal class RunException : Exception
{
public RunException(string message)
: base(message)
{
}
public RunException(string message, Exception? innerException)
: base(message, innerException)
{
}
}
}

View file

@ -0,0 +1,65 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
namespace Pulumi
{
internal static class Extensions
{
public static bool AddRange<T>(this HashSet<T> to, IEnumerable<T> values)
{
var result = false;
foreach (var value in values)
{
result |= to.Add(value);
}
return result;
}
public static void Deconstruct<K, V>(this KeyValuePair<K, V> pair, out K key, out V value)
{
key = pair.Key;
value = pair.Value;
}
public static Output<object?> ToObjectOutput(this object? obj)
{
var output = obj is IInput input ? input.ToOutput() : obj as IOutput;
return output != null
? new Output<object?>(output.Resources, output.GetDataAsync())
: Output.Create(obj);
}
public static ImmutableArray<TResult> SelectAsArray<TItem, TResult>(this ImmutableArray<TItem> items, Func<TItem, TResult> map)
=> ImmutableArray.CreateRange(items, map);
public static void Assign<X, Y>(
this Task<X> response, TaskCompletionSource<Y> tcs, Func<X, Y> extract)
{
_ = response.ContinueWith(t =>
{
switch (t.Status)
{
default: throw new InvalidOperationException("Task was not complete: " + t.Status);
case TaskStatus.Canceled: tcs.SetCanceled(); return;
case TaskStatus.Faulted: tcs.SetException(t.Exception!.InnerExceptions); return;
case TaskStatus.RanToCompletion:
try
{
tcs.SetResult(extract(t.Result));
}
catch (Exception e)
{
tcs.TrySetException(e);
}
return;
}
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
}
}
}

38
sdk/dotnet/Pulumi/Log.cs Normal file
View file

@ -0,0 +1,38 @@
// Copyright 2016-2019, Pulumi Corporation
namespace Pulumi
{
/// <summary>
/// Logging functions that can be called from a .NET application that will be logged to the
/// <c>Pulumi</c> log stream. These events will be printed in the terminal while the Pulumi app
/// runs, and will be available from the Web console afterwards.
/// </summary>
public static class Log
{
/// <summary>
/// Logs a debug-level message that is generally hidden from end-users.
/// </summary>
public static void Debug(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null)
=> Deployment.InternalInstance.Logger.DebugAsync(message, resource, streamId, ephemeral);
/// <summary>
/// Logs an informational message that is generally printed to stdout during resource
/// operations.
/// </summary>
public static void Info(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null)
=> Deployment.InternalInstance.Logger.InfoAsync(message, resource, streamId, ephemeral);
/// <summary>
/// Warn logs a warning to indicate that something went wrong, but not catastrophically so.
/// </summary>
public static void Warn(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null)
=> Deployment.InternalInstance.Logger.WarnAsync(message, resource, streamId, ephemeral);
/// <summary>
/// Error logs a fatal error to indicate that the tool should stop processing resource
/// operations immediately.
/// </summary>
public static void Error(string message, Resource? resource = null, int? streamId = null, bool? ephemeral = null)
=> Deployment.InternalInstance.Logger.ErrorAsync(message, resource, streamId, ephemeral);
}
}

View file

@ -0,0 +1 @@


View file

@ -0,0 +1,209 @@
Pulumi.Alias
Pulumi.Alias.Alias() -> void
Pulumi.Alias.Name.get -> Pulumi.Input<string>
Pulumi.Alias.Name.set -> void
Pulumi.Alias.NoParent.get -> bool
Pulumi.Alias.NoParent.set -> void
Pulumi.Alias.Parent.get -> Pulumi.Resource
Pulumi.Alias.Parent.set -> void
Pulumi.Alias.ParentUrn.get -> Pulumi.Input<string>
Pulumi.Alias.ParentUrn.set -> void
Pulumi.Alias.Project.get -> Pulumi.Input<string>
Pulumi.Alias.Project.set -> void
Pulumi.Alias.Stack.get -> Pulumi.Input<string>
Pulumi.Alias.Stack.set -> void
Pulumi.Alias.Type.get -> Pulumi.Input<string>
Pulumi.Alias.Type.set -> void
Pulumi.Alias.Urn.get -> string
Pulumi.Alias.Urn.set -> void
Pulumi.Archive
Pulumi.Asset
Pulumi.AssetArchive
Pulumi.AssetArchive.AssetArchive(System.Collections.Immutable.ImmutableDictionary<string, Pulumi.AssetOrArchive> assets) -> void
Pulumi.AssetOrArchive
Pulumi.ComponentResource
Pulumi.ComponentResource.ComponentResource(string type, string name, Pulumi.ResourceOptions options = null) -> void
Pulumi.ComponentResource.RegisterOutputs() -> void
Pulumi.ComponentResource.RegisterOutputs(Pulumi.Output<System.Collections.Generic.IDictionary<string, object>> outputs) -> void
Pulumi.ComponentResource.RegisterOutputs(System.Collections.Generic.IDictionary<string, object> outputs) -> void
Pulumi.ComponentResource.RegisterOutputs(System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, object>> outputs) -> void
Pulumi.ComponentResourceOptions
Pulumi.ComponentResourceOptions.ComponentResourceOptions() -> void
Pulumi.ComponentResourceOptions.Providers.get -> System.Collections.Generic.List<Pulumi.ProviderResource>
Pulumi.ComponentResourceOptions.Providers.set -> void
Pulumi.Config
Pulumi.Config.Config(string name = null) -> void
Pulumi.Config.Get(string key) -> string
Pulumi.Config.GetBoolean(string key) -> bool?
Pulumi.Config.GetInt32(string key) -> int?
Pulumi.Config.GetObject<T>(string key) -> T
Pulumi.Config.GetSecret(string key) -> Pulumi.Output<string>
Pulumi.Config.GetSecretBoolean(string key) -> Pulumi.Output<bool>
Pulumi.Config.GetSecretInt32(string key) -> Pulumi.Output<int>
Pulumi.Config.GetSecretObject<T>(string key) -> Pulumi.Output<T>
Pulumi.Config.Require(string key) -> string
Pulumi.Config.RequireBoolean(string key) -> bool
Pulumi.Config.RequireInt32(string key) -> int
Pulumi.Config.RequireObject<T>(string key) -> T
Pulumi.Config.RequireSecret(string key) -> Pulumi.Output<string>
Pulumi.Config.RequireSecretBoolean(string key) -> Pulumi.Output<bool>
Pulumi.Config.RequireSecretInt32(string key) -> Pulumi.Output<int>
Pulumi.Config.RequireSecretObject<T>(string key) -> Pulumi.Output<T>
Pulumi.CustomResource
Pulumi.CustomResource.CustomResource(string type, string name, Pulumi.ResourceArgs args, Pulumi.ResourceOptions options = null) -> void
Pulumi.CustomResource.Id.get -> Pulumi.Output<string>
Pulumi.CustomResourceOptions
Pulumi.CustomResourceOptions.AdditionalSecretOutputs.get -> System.Collections.Generic.List<string>
Pulumi.CustomResourceOptions.AdditionalSecretOutputs.set -> void
Pulumi.CustomResourceOptions.CustomResourceOptions() -> void
Pulumi.CustomResourceOptions.DeleteBeforeReplace.get -> bool?
Pulumi.CustomResourceOptions.DeleteBeforeReplace.set -> void
Pulumi.CustomResourceOptions.ImportId.get -> string
Pulumi.CustomResourceOptions.ImportId.set -> void
Pulumi.CustomTimeouts
Pulumi.CustomTimeouts.Create.get -> System.TimeSpan?
Pulumi.CustomTimeouts.Create.set -> void
Pulumi.CustomTimeouts.CustomTimeouts() -> void
Pulumi.CustomTimeouts.Delete.get -> System.TimeSpan?
Pulumi.CustomTimeouts.Delete.set -> void
Pulumi.CustomTimeouts.Update.get -> System.TimeSpan?
Pulumi.CustomTimeouts.Update.set -> void
Pulumi.Deployment
Pulumi.FileArchive
Pulumi.FileArchive.FileArchive(string path) -> void
Pulumi.FileAsset
Pulumi.FileAsset.FileAsset(string path) -> void
Pulumi.IDeployment
Pulumi.IDeployment.InvokeAsync(string token, Pulumi.ResourceArgs args, Pulumi.InvokeOptions options = null) -> System.Threading.Tasks.Task
Pulumi.IDeployment.InvokeAsync<T>(string token, Pulumi.ResourceArgs args, Pulumi.InvokeOptions options = null) -> System.Threading.Tasks.Task<T>
Pulumi.IDeployment.IsDryRun.get -> bool
Pulumi.IDeployment.ProjectName.get -> string
Pulumi.IDeployment.StackName.get -> string
Pulumi.Input<T>
Pulumi.Input<T>.ToOutput() -> Pulumi.Output<T>
Pulumi.InputList<T>
Pulumi.InputList<T>.Add(params Pulumi.Input<T>[] inputs) -> void
Pulumi.InputList<T>.Concat(Pulumi.InputList<T> other) -> Pulumi.InputList<T>
Pulumi.InputList<T>.GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken) -> System.Collections.Generic.IAsyncEnumerator<Pulumi.Input<T>>
Pulumi.InputList<T>.InputList() -> void
Pulumi.InputMap<V>
Pulumi.InputMap<V>.Add(string key, Pulumi.Input<V> value) -> void
Pulumi.InputMap<V>.GetAsyncEnumerator(System.Threading.CancellationToken cancellationToken) -> System.Collections.Generic.IAsyncEnumerator<Pulumi.Input<System.Collections.Generic.KeyValuePair<string, V>>>
Pulumi.InputMap<V>.InputMap() -> void
Pulumi.InputMap<V>.this[string key].set -> void
Pulumi.InvokeOptions
Pulumi.InvokeOptions.InvokeOptions() -> void
Pulumi.InvokeOptions.Parent.get -> Pulumi.Resource
Pulumi.InvokeOptions.Parent.set -> void
Pulumi.InvokeOptions.Provider.get -> Pulumi.ProviderResource
Pulumi.InvokeOptions.Provider.set -> void
Pulumi.InvokeOptions.Version.get -> string
Pulumi.InvokeOptions.Version.set -> void
Pulumi.Log
Pulumi.Output
Pulumi.Output<T>
Pulumi.Output<T>.Apply<U>(System.Func<T, Pulumi.Output<U>> func) -> Pulumi.Output<U>
Pulumi.Output<T>.Apply<U>(System.Func<T, System.Threading.Tasks.Task<U>> func) -> Pulumi.Output<U>
Pulumi.Output<T>.Apply<U>(System.Func<T, U> func) -> Pulumi.Output<U>
Pulumi.ProviderResource
Pulumi.ProviderResource.ProviderResource(string package, string name, Pulumi.ResourceArgs args, Pulumi.ResourceOptions options = null) -> void
Pulumi.RemoteArchive
Pulumi.RemoteArchive.RemoteArchive(string uri) -> void
Pulumi.RemoteAsset
Pulumi.RemoteAsset.RemoteAsset(string uri) -> void
Pulumi.Resource
Pulumi.Resource.GetResourceName() -> string
Pulumi.Resource.GetResourceType() -> string
Pulumi.Resource.Urn.get -> Pulumi.Output<string>
Pulumi.ResourceArgs
Pulumi.ResourceArgs.ResourceArgs() -> void
Pulumi.ResourceException
Pulumi.ResourceException.ResourceException(string message, Pulumi.Resource resource, bool hideStack = false) -> void
Pulumi.ResourceOptions
Pulumi.ResourceOptions.Aliases.get -> System.Collections.Generic.List<Pulumi.Input<Pulumi.Alias>>
Pulumi.ResourceOptions.Aliases.set -> void
Pulumi.ResourceOptions.CustomTimeouts.get -> Pulumi.CustomTimeouts
Pulumi.ResourceOptions.CustomTimeouts.set -> void
Pulumi.ResourceOptions.DependsOn.get -> Pulumi.InputList<Pulumi.Resource>
Pulumi.ResourceOptions.DependsOn.set -> void
Pulumi.ResourceOptions.Id.get -> Pulumi.Input<string>
Pulumi.ResourceOptions.Id.set -> void
Pulumi.ResourceOptions.IgnoreChanges.get -> System.Collections.Generic.List<string>
Pulumi.ResourceOptions.IgnoreChanges.set -> void
Pulumi.ResourceOptions.Parent.get -> Pulumi.Resource
Pulumi.ResourceOptions.Parent.set -> void
Pulumi.ResourceOptions.Protect.get -> bool?
Pulumi.ResourceOptions.Protect.set -> void
Pulumi.ResourceOptions.Provider.get -> Pulumi.ProviderResource
Pulumi.ResourceOptions.Provider.set -> void
Pulumi.ResourceOptions.ResourceOptions() -> void
Pulumi.ResourceOptions.ResourceTransformations.get -> System.Collections.Generic.List<Pulumi.ResourceTransformation>
Pulumi.ResourceOptions.ResourceTransformations.set -> void
Pulumi.ResourceOptions.Version.get -> string
Pulumi.ResourceOptions.Version.set -> void
Pulumi.ResourceTransformation
Pulumi.ResourceTransformationArgs
Pulumi.ResourceTransformationArgs.Args.get -> Pulumi.ResourceArgs
Pulumi.ResourceTransformationArgs.Options.get -> Pulumi.ResourceOptions
Pulumi.ResourceTransformationArgs.Resource.get -> Pulumi.Resource
Pulumi.ResourceTransformationArgs.ResourceTransformationArgs(Pulumi.Resource resource, Pulumi.ResourceArgs args, Pulumi.ResourceOptions options) -> void
Pulumi.ResourceTransformationResult
Pulumi.ResourceTransformationResult.Args.get -> Pulumi.ResourceArgs
Pulumi.ResourceTransformationResult.Options.get -> Pulumi.ResourceOptions
Pulumi.ResourceTransformationResult.ResourceTransformationResult(Pulumi.ResourceArgs args, Pulumi.ResourceOptions options) -> void
Pulumi.Serialization.InputAttribute
Pulumi.Serialization.InputAttribute.InputAttribute(string name, bool required = false, bool json = false) -> void
Pulumi.Serialization.OutputAttribute
Pulumi.Serialization.OutputAttribute.Name.get -> string
Pulumi.Serialization.OutputAttribute.OutputAttribute(string name) -> void
Pulumi.Serialization.OutputConstructorAttribute
Pulumi.Serialization.OutputConstructorAttribute.OutputConstructorAttribute() -> void
Pulumi.Serialization.OutputTypeAttribute
Pulumi.Serialization.OutputTypeAttribute.OutputTypeAttribute() -> void
Pulumi.StringAsset
Pulumi.StringAsset.StringAsset(string text) -> void
static Pulumi.Deployment.Instance.get -> Pulumi.IDeployment
static Pulumi.Deployment.RunAsync(System.Action action) -> System.Threading.Tasks.Task<int>
static Pulumi.Deployment.RunAsync(System.Func<System.Collections.Generic.IDictionary<string, object>> func) -> System.Threading.Tasks.Task<int>
static Pulumi.Deployment.RunAsync(System.Func<System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<string, object>>> func) -> System.Threading.Tasks.Task<int>
static Pulumi.Input<T>.implicit operator Pulumi.Input<T>(Pulumi.Output<T> value) -> Pulumi.Input<T>
static Pulumi.Input<T>.implicit operator Pulumi.Input<T>(T value) -> Pulumi.Input<T>
static Pulumi.Input<T>.implicit operator Pulumi.Output<T>(Pulumi.Input<T> input) -> Pulumi.Output<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Input<T> value) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Input<T>[] values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Output<System.Collections.Generic.IEnumerable<T>> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Output<System.Collections.Generic.List<T>> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Output<System.Collections.Immutable.ImmutableArray<T>> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Output<T> value) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Output<T>[] values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(Pulumi.Output<T[]> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(System.Collections.Generic.List<Pulumi.Input<T>> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(System.Collections.Generic.List<Pulumi.Output<T>> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(System.Collections.Generic.List<T> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(System.Collections.Immutable.ImmutableArray<Pulumi.Input<T>> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(System.Collections.Immutable.ImmutableArray<Pulumi.Output<T>> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(System.Collections.Immutable.ImmutableArray<T> values) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(T value) -> Pulumi.InputList<T>
static Pulumi.InputList<T>.implicit operator Pulumi.InputList<T>(T[] values) -> Pulumi.InputList<T>
static Pulumi.InputMap<V>.implicit operator Pulumi.InputMap<V>(Pulumi.Output<System.Collections.Generic.Dictionary<string, V>> values) -> Pulumi.InputMap<V>
static Pulumi.InputMap<V>.implicit operator Pulumi.InputMap<V>(Pulumi.Output<System.Collections.Generic.IDictionary<string, V>> values) -> Pulumi.InputMap<V>
static Pulumi.InputMap<V>.implicit operator Pulumi.InputMap<V>(Pulumi.Output<System.Collections.Immutable.ImmutableDictionary<string, V>> values) -> Pulumi.InputMap<V>
static Pulumi.InputMap<V>.implicit operator Pulumi.InputMap<V>(System.Collections.Generic.Dictionary<string, V> values) -> Pulumi.InputMap<V>
static Pulumi.InputMap<V>.implicit operator Pulumi.InputMap<V>(System.Collections.Immutable.ImmutableDictionary<string, V> values) -> Pulumi.InputMap<V>
static Pulumi.Log.Debug(string message, Pulumi.Resource resource = null, int? streamId = null, bool? ephemeral = null) -> void
static Pulumi.Log.Error(string message, Pulumi.Resource resource = null, int? streamId = null, bool? ephemeral = null) -> void
static Pulumi.Log.Info(string message, Pulumi.Resource resource = null, int? streamId = null, bool? ephemeral = null) -> void
static Pulumi.Log.Warn(string message, Pulumi.Resource resource = null, int? streamId = null, bool? ephemeral = null) -> void
static Pulumi.Output.All<T>(System.Collections.Immutable.ImmutableArray<Pulumi.Input<T>> inputs) -> Pulumi.Output<System.Collections.Immutable.ImmutableArray<T>>
static Pulumi.Output.All<T>(params Pulumi.Input<T>[] inputs) -> Pulumi.Output<System.Collections.Immutable.ImmutableArray<T>>
static Pulumi.Output.Create<T>(System.Threading.Tasks.Task<T> value) -> Pulumi.Output<T>
static Pulumi.Output.Create<T>(T value) -> Pulumi.Output<T>
static Pulumi.Output.CreateSecret<T>(System.Threading.Tasks.Task<T> value) -> Pulumi.Output<T>
static Pulumi.Output.CreateSecret<T>(T value) -> Pulumi.Output<T>
static Pulumi.Output.Format(System.FormattableString formattableString) -> Pulumi.Output<string>
static Pulumi.Output.Tuple<X, Y, Z>(Pulumi.Input<X> item1, Pulumi.Input<Y> item2, Pulumi.Input<Z> item3) -> Pulumi.Output<(X, Y, Z)>
static Pulumi.Output.Tuple<X, Y>(Pulumi.Input<X> item1, Pulumi.Input<Y> item2) -> Pulumi.Output<(X, Y)>
static Pulumi.Output.Tuple<X, Y>(Pulumi.Output<X> item1, Pulumi.Output<Y> item2) -> Pulumi.Output<(X, Y)>
static Pulumi.Output<T>.Create(System.Threading.Tasks.Task<T> value) -> Pulumi.Output<T>
static Pulumi.ResourceOptions.Merge(Pulumi.ResourceOptions options1, Pulumi.ResourceOptions options2) -> Pulumi.ResourceOptions
static readonly Pulumi.ResourceArgs.Empty -> Pulumi.ResourceArgs

View file

@ -0,0 +1,62 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Pulumi</Authors>
<Company>Pulumi Corp.</Company>
<Description>The Pulumi .NET SDK lets you write cloud programs in C#, F#, and VB.NET.</Description>
<PackageProjectUrl>https://www.pulumi.com</PackageProjectUrl>
<RepositoryUrl>https://github.com/pulumi/pulumi</RepositoryUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageIcon>pulumi_logo_64x64.png</PackageIcon>
<TargetFramework>netcoreapp3.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>.\Pulumi.xml</DocumentationFile>
<NoWarn>1701;1702;1591;NU5105</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="2.9.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.6.0" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Google.Protobuf" Version="3.10.0" />
<PackageReference Include="Grpc" Version="2.24.0" />
<PackageReference Include="Grpc.Tools" Version="2.24.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="..\..\proto\*.proto" Access="internal" />
</ItemGroup>
<ItemGroup>
<None Remove="PublicAPI.Shipped.txt" />
<None Remove="PublicAPI.Unshipped.txt" />
<None Remove="Pulumi.xml" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="PublicAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI.Unshipped.txt" />
</ItemGroup>
<ItemGroup>
<None Include="..\pulumi_logo_64x64.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,53 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace Pulumi
{
/// <summary>
/// A <see cref="Resource"/> that aggregates one or more other child resources into a higher
/// level abstraction.The component resource itself is a resource, but does not require custom
/// CRUD operations for provisioning.
/// </summary>
public class ComponentResource : Resource
{
/// <summary>
/// Creates and registers a new component resource. <paramref name="type"/> is the fully
/// qualified type token and <paramref name="name"/> is the "name" part to use in creating a
/// stable and globally unique URN for the object. <c>options.parent</c> is the optional parent
/// for this component, and [options.dependsOn] is an optional list of other resources that
/// this resource depends on, controlling the order in which we perform resource operations.
/// </summary>
#pragma warning disable RS0022 // Constructor make noninheritable base class inheritable
public ComponentResource(string type, string name, ResourceOptions? options = null)
: base(type, name, custom: false,
args: ResourceArgs.Empty,
options ?? new ComponentResourceOptions())
#pragma warning restore RS0022 // Constructor make noninheritable base class inheritable
{
}
/// <summary>
/// RegisterOutputs registers synthetic outputs that a component has initialized, usually by
/// allocating other child sub-resources and propagating their resulting property values.
/// ComponentResources should always call this at the end of their constructor to indicate
/// that they are done creating child resources. While not strictly necessary, this helps
/// the experience by ensuring the UI transitions the ComponentResource to the 'complete'
/// state as quickly as possible (instead of waiting until the entire application completes).
/// </summary>
protected void RegisterOutputs()
=> RegisterOutputs(ImmutableDictionary<string, object>.Empty);
protected void RegisterOutputs(IDictionary<string, object> outputs)
=> RegisterOutputs(Task.FromResult(outputs ?? throw new ArgumentNullException(nameof(outputs))));
protected void RegisterOutputs(Task<IDictionary<string, object>> outputs)
=> RegisterOutputs(Output.Create(outputs ?? throw new ArgumentNullException(nameof(outputs))));
protected void RegisterOutputs(Output<IDictionary<string, object>> outputs)
=> Deployment.InternalInstance.RegisterResourceOutputs(this, outputs ?? throw new ArgumentNullException(nameof(outputs)));
}
}

View file

@ -0,0 +1,29 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
namespace Pulumi
{
/// <summary>
/// A bag of optional settings that control a <see cref="ComponentResource"/>'s behavior.
/// </summary>
public sealed class ComponentResourceOptions : ResourceOptions
{
private List<ProviderResource>? _providers;
/// <summary>
/// An optional set of providers to use for child resources.
///
/// Note: do not provide both <see cref="ResourceOptions.Provider"/> and <see
/// cref="Providers"/>.
/// </summary>
public List<ProviderResource> Providers
{
get => _providers ?? (_providers = new List<ProviderResource>());
set => _providers = value;
}
internal override ResourceOptions Clone()
=> CreateComponentResourceOptionsCopy(this);
}
}

View file

@ -0,0 +1,43 @@
// Copyright 2016-2019, Pulumi Corporation
using Pulumi.Serialization;
namespace Pulumi
{
/// <summary>
/// CustomResource is a resource whose create, read, update, and delete(CRUD) operations are
/// managed by performing external operations on some physical entity. The engine understands
/// how to diff and perform partial updates of them, and these CRUD operations are implemented
/// in a dynamically loaded plugin for the defining package.
/// </summary>
public class CustomResource : Resource
{
/// <summary>
/// Id is the provider-assigned unique ID for this managed resource. It is set during
/// deployments and may be missing (unknown) during planning phases.
/// </summary>
// Set using reflection, so we silence the NRT warnings with `null!`.
[Output(Constants.IdPropertyName)]
public Output<string> Id { get; private set; } = null!;
/// <summary>
/// Creates and registers a new managed resource. t is the fully qualified type token and
/// name is the "name" part to use in creating a stable and globally unique URN for the
/// object. dependsOn is an optional list of other resources that this resource depends on,
/// controlling the order in which we perform resource operations.Creating an instance does
/// not necessarily perform a create on the physical entity which it represents, and
/// instead, this is dependent upon the diffing of the new goal state compared to the
/// current known resource state.
/// </summary>
#pragma warning disable RS0022 // Constructor make noninheritable base class inheritable
public CustomResource(string type, string name, ResourceArgs? args, ResourceOptions? options = null)
: base(type, name, custom: true, args ?? ResourceArgs.Empty, options ?? new ResourceOptions())
#pragma warning restore RS0022 // Constructor make noninheritable base class inheritable
{
if (options is ComponentResourceOptions componentOpts && componentOpts.Providers != null)
{
throw new ResourceException("Do not supply 'providers' option to a CustomResource. Did you mean 'provider' instead?", this);
}
}
}
}

View file

@ -0,0 +1,44 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
namespace Pulumi
{
/// <summary>
/// <see cref="CustomResourceOptions"/> is a bag of optional settings that control a <see
/// cref="CustomResource"/>'s behavior.
/// </summary>
public sealed class CustomResourceOptions : ResourceOptions
{
/// <summary>
/// When set to <c>true</c>, indicates that this resource should be deleted before its
/// replacement is created when replacement is necessary.
/// </summary>
public bool? DeleteBeforeReplace { get; set; }
private List<string>? _additionalSecretOutputs;
/// <summary>
/// The names of outputs for this resource that should be treated as secrets. This augments
/// the list that the resource provider and pulumi engine already determine based on inputs
/// to your resource. It can be used to mark certain outputs as a secrets on a per resource
/// basis.
/// </summary>
public List<string> AdditionalSecretOutputs
{
get => _additionalSecretOutputs ?? (_additionalSecretOutputs = new List<string>());
set => _additionalSecretOutputs = value;
}
/// <summary>
/// When provided with a resource ID, import indicates that this resource's provider should
/// import its state from the cloud resource with the given ID.The inputs to the resource's
/// constructor must align with the resource's current state.Once a resource has been
/// imported, the import property must be removed from the resource's options.
/// </summary>
public string? ImportId { get; set; }
internal override ResourceOptions Clone()
=> CreateCustomResourceOptionsCopy(this);
}
}

View file

@ -0,0 +1,35 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
namespace Pulumi
{
/// <summary>
/// Optional timeouts to supply in <see cref="ResourceOptions.CustomTimeouts"/>.
/// </summary>
public sealed class CustomTimeouts
{
/// <summary>
/// The optional create timeout.
/// </summary>
public TimeSpan? Create { get; set; }
/// <summary>
/// The optional update timeout.
/// </summary>
public TimeSpan? Update { get; set; }
/// <summary>
/// The optional delete timeout.
/// </summary>
public TimeSpan? Delete { get; set; }
internal static CustomTimeouts? Clone(CustomTimeouts? timeouts)
=> timeouts == null ? null : new CustomTimeouts
{
Create = timeouts.Create,
Delete = timeouts.Delete,
Update = timeouts.Update,
};
}
}

View file

@ -0,0 +1,52 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Threading.Tasks;
using Pulumi.Serialization;
namespace Pulumi
{
/// <summary>
/// <see cref="ProviderResource"/> is a <see cref="Resource"/> that implements CRUD operations
/// for other custom resources. These resources are managed similarly to other resources,
/// including the usual diffing and update semantics.
/// </summary>
public class ProviderResource : CustomResource
{
internal readonly string Package;
private string? _registrationId;
/// <summary>
/// Creates and registers a new provider resource for a particular package.
/// </summary>
public ProviderResource(
string package, string name,
ResourceArgs args, ResourceOptions? options = null)
: base($"pulumi:providers:{package}", name, args, options)
{
this.Package = package;
}
internal static async Task<string?> RegisterAsync(ProviderResource? provider)
{
if (provider == null)
{
return null;
}
if (provider._registrationId == null)
{
var providerURN = await provider.Urn.GetValueAsync().ConfigureAwait(false);
var providerID = await provider.Id.GetValueAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(providerID))
{
providerID = Constants.UnknownValue;
}
provider._registrationId = $"{providerURN}::{providerID}";
}
return provider._registrationId;
}
}
}

View file

@ -0,0 +1,372 @@
// Copyright 2016-2018, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Pulumi.Serialization;
namespace Pulumi
{
/// <summary>
/// Resource represents a class whose CRUD operations are implemented by a provider plugin.
/// </summary>
public class Resource
{
private readonly string _type;
private readonly string _name;
/// <summary>
/// The optional parent of this resource.
/// </summary>
private readonly Resource? _parentResource;
/// <summary>
/// The child resources of this resource. We use these (only from a ComponentResource) to
/// allow code to dependOn a ComponentResource and have that effectively mean that it is
/// depending on all the CustomResource children of that component.
///
/// Important! We only walk through ComponentResources.They're the only resources that
/// serve as an aggregation of other primitive(i.e.custom) resources.While a custom resource
/// can be a parent of other resources, we don't want to ever depend on those child
/// resource. If we do, it's simple to end up in a situation where we end up depending on a
/// child resource that has a data cycle dependency due to the data passed into it. An
/// example of how this would be bad is:
///
/// <c>
/// var c1 = new CustomResource("c1");
/// var c2 = new CustomResource("c2", { parentId = c1.id }, { parent = c1 });
/// var c3 = new CustomResource("c3", { parentId = c1.id }, { parent = c1 });
/// </c>
///
/// The problem here is that 'c2' has a data dependency on 'c1'. If it tries to wait on
/// 'c1' it will walk to the children and wait on them.This will mean it will wait on 'c3'.
/// But 'c3' will be waiting in the same manner on 'c2', and a cycle forms. This normally
/// does not happen with ComponentResources as they do not have any data flowing into
/// them.The only way you would be able to have a problem is if you had this sort of coding
/// pattern:
///
/// <c>
/// var c1 = new ComponentResource("c1");
/// var c2 = new CustomResource("c2", { parentId = c1.urn }, { parent: c1 });
/// var c3 = new CustomResource("c3", { parentId = c1.urn }, { parent: c1 });
/// </c>
///
/// However, this would be pretty nonsensical as there is zero need for a custom resource to
/// ever need to reference the urn of a component resource. So it's acceptable if that sort
/// of pattern failed in practice.
/// </summary>
internal readonly HashSet<Resource> ChildResources = new HashSet<Resource>();
/// <summary>
/// Urn is the stable logical URN used to distinctly address a resource, both before and
/// after deployments.
/// </summary>
// Set using reflection, so we silence the NRT warnings with `null!`.
[Output(Constants.UrnPropertyName)]
public Output<string> Urn { get; private set; } = null!;
/// <summary>
/// When set to true, protect ensures this resource cannot be deleted.
/// </summary>
private readonly bool _protect;
/// <summary>
/// A collection of transformations to apply as part of resource registration.
/// </summary>
private readonly ImmutableArray<ResourceTransformation> _transformations;
/// <summary>
/// A list of aliases applied to this resource.
/// </summary>
internal readonly ImmutableArray<Input<string>> _aliases;
/// <summary>
/// The type assigned to the resource at construction.
/// </summary>
// This is a method and not a property to not collide with potential subclass property names.
public string GetResourceType() => _type;
/// <summary>
/// The name assigned to the resource at construction.
/// </summary>
// This is a method and not a property to not collide with potential subclass property names.
public string GetResourceName() => _name;
/// <summary>
/// The set of providers to use for child resources. Keyed by package name (e.g. "aws").
/// </summary>
private readonly ImmutableDictionary<string, ProviderResource> _providers;
/// <summary>
/// Creates and registers a new resource object. <paramref name="type"/> is the fully
/// qualified type token and <paramref name="name"/> is the "name" part to use in creating a
/// stable and globally unique URN for the object. dependsOn is an optional list of other
/// resources that this resource depends on, controlling the order in which we perform
/// resource operations.
/// </summary>
/// <param name="type">The type of the resource.</param>
/// <param name="name">The unique name of the resource.</param>
/// <param name="custom">True to indicate that this is a custom resource, managed by a plugin.</param>
/// <param name="args">The arguments to use to populate the new resource.</param>
/// <param name="options">A bag of options that control this resource's behavior.</param>
private protected Resource(
string type, string name, bool custom,
ResourceArgs args, ResourceOptions options)
{
if (string.IsNullOrEmpty(type))
throw new ArgumentException("'type' cannot be null or empty.", nameof(type));
if (string.IsNullOrEmpty(name))
throw new ArgumentException("'name' cannot be null or empty.", nameof(name));
// Before anything else - if there are transformations registered, invoke them in order
// to transform the properties and options assigned to this resource.
var parent = type == Stack._rootPulumiStackTypeName
? null
: (options.Parent ?? Deployment.InternalInstance.Stack);
_type = type;
_name = name;
var transformations = ImmutableArray.CreateBuilder<ResourceTransformation>();
transformations.AddRange(options.ResourceTransformations);
if (parent != null)
{
transformations.AddRange(parent._transformations);
}
this._transformations = transformations.ToImmutable();
foreach (var transformation in this._transformations)
{
var tres = transformation(new ResourceTransformationArgs(this, args, options));
if (tres != null)
{
if (tres.Value.Options.Parent != options.Parent)
{
// This is currently not allowed because the parent tree is needed to
// establish what transformation to apply in the first place, and to compute
// inheritance of other resource options in the Resource constructor before
// transformations are run (so modifying it here would only even partially
// take affect). It's theoretically possible this restriction could be
// lifted in the future, but for now just disallow re-parenting resources in
// transformations to be safe.
throw new ArgumentException("Transformations cannot currently be used to change the 'parent' of a resource.");
}
args = tres.Value.Args;
options = tres.Value.Options;
}
}
// Make a shallow clone of options to ensure we don't modify the value passed in.
options = options.Clone();
var componentOpts = options as ComponentResourceOptions;
var customOpts = options as CustomResourceOptions;
if (options.Provider != null &&
componentOpts?.Providers.Count > 0)
{
throw new ResourceException("Do not supply both 'provider' and 'providers' options to a ComponentResource.", options.Parent);
}
// Check the parent type if one exists and fill in any default options.
this._providers = ImmutableDictionary<string, ProviderResource>.Empty;
if (options.Parent != null)
{
this._parentResource = options.Parent;
this._parentResource.ChildResources.Add(this);
if (options.Protect == null)
options.Protect = options.Parent._protect;
// Make a copy of the aliases array, and add to it any implicit aliases inherited from its parent
options.Aliases = options.Aliases.ToList();
foreach (var parentAlias in options.Parent._aliases)
{
options.Aliases.Add(Pulumi.Urn.InheritedChildAlias(name, options.Parent.GetResourceName(), parentAlias, type));
}
this._providers = options.Parent._providers;
}
if (custom)
{
var provider = customOpts?.Provider;
if (provider == null)
{
if (options.Parent != null)
{
// If no provider was given, but we have a parent, then inherit the
// provider from our parent.
options.Provider = options.Parent.GetProvider(type);
}
}
else
{
// If a provider was specified, add it to the providers map under this type's package so that
// any children of this resource inherit its provider.
var typeComponents = type.Split(":");
if (typeComponents.Length == 3)
{
var pkg = typeComponents[0];
this._providers = this._providers.SetItem(pkg, provider);
}
}
}
else
{
// Note: we checked above that at most one of options.provider or options.providers
// is set.
// If options.provider is set, treat that as if we were given a array of provider
// with that single value in it. Otherwise, take the array of providers, convert it
// to a map and combine with any providers we've already set from our parent.
var providerList = options.Provider != null
? new List<ProviderResource> { options.Provider }
: componentOpts?.Providers;
this._providers = this._providers.AddRange(ConvertToProvidersMap(providerList));
}
this._protect = options.Protect == true;
// Collapse any 'Alias'es down to URNs. We have to wait until this point to do so
// because we do not know the default 'name' and 'type' to apply until we are inside the
// resource constructor.
var aliases = ImmutableArray.CreateBuilder<Input<string>>();
foreach (var alias in options.Aliases)
{
aliases.Add(CollapseAliasToUrn(alias, name, type, options.Parent));
}
this._aliases = aliases.ToImmutable();
if (options.Id != null)
{
// If this resource already exists, read its state rather than registering it anew.
if (!custom)
{
throw new ResourceException(
"Cannot read an existing resource unless it has a custom provider", options.Parent);
}
Deployment.InternalInstance.ReadResource(this, args, options);
}
else
{
// Kick off the resource registration. If we are actually performing a deployment,
// this resource's properties will be resolved asynchronously after the operation
// completes, so that dependent computations resolve normally. If we are just
// planning, on the other hand, values will never resolve.
Deployment.InternalInstance.RegisterResource(this, custom, args, options);
}
}
/// <summary>
/// Fetches the provider for the given module member, if any.
/// </summary>
internal ProviderResource? GetProvider(string moduleMember)
{
var memComponents = moduleMember.Split(":");
if (memComponents.Length != 3)
{
return null;
}
this._providers.TryGetValue(memComponents[0], out var result);
return result;
}
private static Output<string> CollapseAliasToUrn(
Input<Alias> alias,
string defaultName,
string defaultType,
Resource? defaultParent)
{
return alias.ToOutput().Apply(a =>
{
if (a.Urn != null)
{
CheckNull(a.Name, nameof(a.Name));
CheckNull(a.Type, nameof(a.Type));
CheckNull(a.Project, nameof(a.Project));
CheckNull(a.Stack, nameof(a.Stack));
CheckNull(a.Parent, nameof(a.Parent));
CheckNull(a.ParentUrn, nameof(a.ParentUrn));
if (a.NoParent)
ThrowAliasPropertyConflict(nameof(a.NoParent));
return Output.Create(a.Urn);
}
var name = a.Name ?? defaultName;
var type = a.Type ?? defaultType;
var project = a.Project ?? Deployment.Instance.ProjectName;
var stack = a.Stack ?? Deployment.Instance.StackName;
var parentCount =
(a.Parent != null ? 1 : 0) +
(a.ParentUrn != null ? 1 : 0) +
(a.NoParent ? 1 : 0);
if (parentCount >= 2)
{
throw new ArgumentException(
$"Only specify one of '{nameof(Alias.Parent)}', '{nameof(Alias.ParentUrn)}' or '{nameof(Alias.NoParent)}' in an {nameof(Alias)}");
}
var (parent, parentUrn) = GetParentInfo(defaultParent, a);
if (name == null)
throw new Exception("No valid 'Name' passed in for alias.");
if (type == null)
throw new Exception("No valid 'type' passed in for alias.");
return Pulumi.Urn.Create(name, type, parent, parentUrn, project, stack);
});
}
private static void CheckNull<T>(T? value, string name) where T : class
{
if (value != null)
{
ThrowAliasPropertyConflict(name);
return;
}
}
private static void ThrowAliasPropertyConflict(string name)
=> throw new ArgumentException($"{nameof(Alias)} should not specify both {nameof(Alias.Urn)} and {name}");
private static (Resource? parent, Input<string>? urn) GetParentInfo(Resource? defaultParent, Alias alias)
{
if (alias.Parent != null)
return (alias.Parent, null);
if (alias.ParentUrn != null)
return (null, alias.ParentUrn);
if (alias.NoParent)
return (null, null);
return (defaultParent, null);
}
private static ImmutableDictionary<string, ProviderResource> ConvertToProvidersMap(List<ProviderResource>? providers)
{
var result = ImmutableDictionary.CreateBuilder<string, ProviderResource>();
if (providers != null)
{
foreach (var provider in providers)
{
result[provider.Package] = provider;
}
}
return result.ToImmutable();
}
}
}

View file

@ -0,0 +1,108 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Google.Protobuf;
using Pulumi.Serialization;
namespace Pulumi
{
/// <summary>
/// Base type for all resource argument classes.
/// </summary>
public abstract class ResourceArgs
{
public static readonly ResourceArgs Empty = new EmptyResourceArgs();
private readonly ImmutableArray<InputInfo> _inputInfos;
protected ResourceArgs()
{
var fieldQuery =
from field in this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
let attr = field.GetCustomAttribute<InputAttribute>()
where attr != null
select (attr, memberName: field.Name, memberType: field.FieldType, getValue: (Func<object, object?>)field.GetValue);
var propQuery =
from prop in this.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
let attr = prop.GetCustomAttribute<InputAttribute>()
where attr != null
select (attr, memberName: prop.Name, memberType: prop.PropertyType, getValue: (Func<object, object?>)prop.GetValue);
var all = fieldQuery.Concat(propQuery).ToList();
foreach (var (attr, memberName, memberType, getValue) in all)
{
var fullName = $"[Input] {this.GetType().FullName}.{memberName}";
if (!typeof(IInput).IsAssignableFrom(memberType))
{
throw new InvalidOperationException($"{fullName} was not an Input<T>");
}
}
_inputInfos = all.Select(t =>
new InputInfo(t.attr, t.memberName, t.memberType, t.getValue)).ToImmutableArray();
}
internal async Task<ImmutableDictionary<string, IInput?>> ToDictionaryAsync()
{
var builder = ImmutableDictionary.CreateBuilder<string, IInput?>();
foreach (var info in _inputInfos)
{
var fullName = $"[Input] {this.GetType().FullName}.{info.MemberName}";
var value = (IInput?)info.GetValue(this);
if (info.Attribute.IsRequired && value == null)
{
throw new ArgumentNullException(info.MemberName, $"{fullName} is required but was not given a value");
}
if (info.Attribute.Json)
{
value = await ConvertToJsonAsync(fullName, value).ConfigureAwait(false);
}
builder.Add(info.Attribute.Name, value);
}
return builder.ToImmutable();
}
private async Task<IInput?> ConvertToJsonAsync(string context, IInput? input)
{
if (input == null)
return null;
var serializer = new Serializer(excessiveDebugOutput: false);
var obj = await serializer.SerializeAsync(context, input).ConfigureAwait(false);
var value = Serializer.CreateValue(obj);
var valueString = JsonFormatter.Default.Format(value);
return (Input<string>)valueString;
}
private class EmptyResourceArgs : ResourceArgs
{
}
private struct InputInfo
{
public readonly InputAttribute Attribute;
public readonly Type MemberType;
public readonly string MemberName;
public Func<object, object?> GetValue;
public InputInfo(InputAttribute attribute, string memberName, Type memberType, Func<object, object> getValue) : this()
{
Attribute = attribute;
MemberName = memberName;
MemberType = memberType;
GetValue = getValue;
}
}
}
}

View file

@ -0,0 +1,93 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
namespace Pulumi
{
/// <summary>
/// ResourceOptions is a bag of optional settings that control a resource's behavior.
/// </summary>
public partial class ResourceOptions
{
/// <summary>
/// An optional existing ID to load, rather than create.
/// </summary>
public Input<string>? Id { get; set; }
/// <summary>
/// An optional parent resource to which this resource belongs.
/// </summary>
public Resource? Parent { get; set; }
private InputList<Resource>? _dependsOn;
/// <summary>
/// Optional additional explicit dependencies on other resources.
/// </summary>
public InputList<Resource> DependsOn
{
get => _dependsOn ?? (_dependsOn = new InputList<Resource>());
set => _dependsOn = value;
}
/// <summary>
/// When set to true, protect ensures this resource cannot be deleted.
/// </summary>
public bool? Protect { get; set; }
private List<string>? _ignoreChanges;
/// <summary>
/// Ignore changes to any of the specified properties.
/// </summary>
public List<string> IgnoreChanges
{
get => _ignoreChanges ?? (_ignoreChanges = new List<string>());
set => _ignoreChanges = value;
}
/// <summary>
/// An optional version, corresponding to the version of the provider plugin that should be
/// used when operating on this resource. This version overrides the version information
/// inferred from the current package and should rarely be used.
/// </summary>
public string? Version { get; set; }
/// <summary>
/// An optional provider to use for this resource's CRUD operations. If no provider is
/// supplied, the default provider for the resource's package will be used. The default
/// provider is pulled from the parent's provider bag (see also
/// ComponentResourceOptions.providers).
///
/// If this is a <see cref="ComponentResourceOptions"/> do not provide both <see
/// cref="Provider"/> and <see cref="ComponentResourceOptions.Providers"/>.
/// </summary>
public ProviderResource? Provider { get; set; }
/// <summary>
/// An optional CustomTimeouts configuration block.
/// </summary>
public CustomTimeouts? CustomTimeouts { get; set; }
private List<ResourceTransformation>? _resourceTransformations;
/// <summary>
/// Optional list of transformations to apply to this resource during construction.The
/// transformations are applied in order, and are applied prior to transformation applied to
/// parents walking from the resource up to the stack.
/// </summary>
public List<ResourceTransformation> ResourceTransformations
{
get => _resourceTransformations ?? (_resourceTransformations = new List<ResourceTransformation>());
set => _resourceTransformations = value;
}
/// <summary>
/// An optional list of aliases to treat this resource as matching.
/// </summary>
public List<Input<Alias>> Aliases { get; set; } = new List<Input<Alias>>();
internal virtual ResourceOptions Clone()
=> CreateResourceOptionsCopy(this);
}
}

View file

@ -0,0 +1,75 @@
// Copyright 2016-2019, Pulumi Corporation
using System.Linq;
using System.Collections.Generic;
namespace Pulumi
{
public partial class ResourceOptions
{
internal static ResourceOptions CreateResourceOptionsCopy(ResourceOptions options)
=> new ResourceOptions
{
Aliases = options.Aliases.ToList(),
CustomTimeouts = CustomTimeouts.Clone(options.CustomTimeouts),
DependsOn = options.DependsOn.Clone(),
Id = options.Id,
Parent = options.Parent,
IgnoreChanges = options.IgnoreChanges.ToList(),
Protect = options.Protect,
Provider = options.Provider,
ResourceTransformations = options.ResourceTransformations.ToList(),
Version = options.Version,
};
internal static CustomResourceOptions CreateCustomResourceOptionsCopy(ResourceOptions options)
{
var customOptions = options as CustomResourceOptions;
var copied = CreateResourceOptionsCopy(options);
return new CustomResourceOptions
{
// Base properties
Aliases = copied.Aliases,
CustomTimeouts = copied.CustomTimeouts,
DependsOn = copied.DependsOn,
Id = copied.Id,
Parent = copied.Parent,
IgnoreChanges = copied.IgnoreChanges,
Protect = copied.Protect,
Provider = copied.Provider,
ResourceTransformations = copied.ResourceTransformations,
Version = copied.Version,
// Our properties
AdditionalSecretOutputs = customOptions?.AdditionalSecretOutputs.ToList() ?? new List<string>(),
DeleteBeforeReplace = customOptions?.DeleteBeforeReplace,
ImportId = customOptions?.ImportId,
};
}
internal static ComponentResourceOptions CreateComponentResourceOptionsCopy(ResourceOptions options)
{
var componentOptions = options as ComponentResourceOptions;
var cloned = CreateResourceOptionsCopy(options);
return new ComponentResourceOptions
{
// Base properties
Aliases = cloned.Aliases,
CustomTimeouts = cloned.CustomTimeouts,
DependsOn = cloned.DependsOn,
Id = cloned.Id,
Parent = cloned.Parent,
IgnoreChanges = cloned.IgnoreChanges,
Protect = cloned.Protect,
Provider = cloned.Provider,
ResourceTransformations = cloned.ResourceTransformations,
Version = cloned.Version,
// Our properties
Providers = componentOptions?.Providers.ToList() ?? new List<ProviderResource>(),
};
}
}
}

View file

@ -0,0 +1,125 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
namespace Pulumi
{
public partial class ResourceOptions
{
/// <summary>
/// Takes two ResourceOptions values and produces a new ResourceOptions with the respective
/// properties of <paramref name="options2"/> merged over the same properties in <paramref
/// name="options1"/>. The original options objects will be unchanged.
/// <para/>
/// A new instance will always be returned.
/// <para/>
/// Conceptually property merging follows these basic rules:
/// <list type="number">
/// <item>
/// If the property is a collection, the final value will be a collection containing the
/// values from each options object.
/// </item>
/// <item>
/// Simple scaler values from <paramref name="options2"/> (i.e. <see cref="string"/>s,
/// <see cref="int"/>s, <see cref="bool"/>s) will replace the values of <paramref
/// name="options1"/>.
/// </item>
/// <item>
/// <see langword="null"/> values in <paramref name="options2"/> will be ignored.
/// </item>
/// </list>
/// </summary>
public static ResourceOptions Merge(ResourceOptions? options1, ResourceOptions? options2)
{
options1 ??= new ResourceOptions();
options2 ??= new ResourceOptions();
if ((options1 is CustomResourceOptions && options2 is ComponentResourceOptions) ||
(options1 is ComponentResourceOptions && options2 is CustomResourceOptions))
{
throw new ArgumentException(
$"Cannot merge a {nameof(CustomResourceOptions)} and {nameof(ComponentResourceOptions)} together.");
}
// make an appropriate copy of both options bag, then the copy of options2 into the copy
// of options1 and return the copy of options1.
if (options1 is CustomResourceOptions || options2 is CustomResourceOptions)
{
return MergeCustomOptions(
CreateCustomResourceOptionsCopy(options1),
CreateCustomResourceOptionsCopy(options2));
}
else if (options1 is ComponentResourceOptions || options2 is ComponentResourceOptions)
{
return MergeComponentOptions(
CreateComponentResourceOptionsCopy(options1),
CreateComponentResourceOptionsCopy(options2));
}
else
{
return MergeNormalOptions(
CreateResourceOptionsCopy(options1),
CreateResourceOptionsCopy(options2));
}
static ResourceOptions MergeNormalOptions(ResourceOptions options1, ResourceOptions options2)
{
options1.Id = options2.Id ?? options1.Id;
options1.Parent = options2.Parent ?? options1.Parent;
options1.Protect = options2.Protect ?? options1.Protect;
options1.Version = options2.Version ?? options1.Version;
options1.Provider = options2.Provider ?? options1.Provider;
options1.CustomTimeouts = options2.CustomTimeouts ?? options1.CustomTimeouts;
options1.IgnoreChanges.AddRange(options2.IgnoreChanges);
options1.ResourceTransformations.AddRange(options2.ResourceTransformations);
options1.Aliases.AddRange(options2.Aliases);
options1.DependsOn = options1.DependsOn.Concat(options2.DependsOn);
return options1;
}
static CustomResourceOptions MergeCustomOptions(CustomResourceOptions options1, CustomResourceOptions options2)
{
// first, merge all the normal option values over
MergeNormalOptions(options1, options2);
options1.DeleteBeforeReplace = options2.DeleteBeforeReplace ?? options1.DeleteBeforeReplace;
options1.ImportId = options2.ImportId ?? options1.ImportId;
options1.AdditionalSecretOutputs.AddRange(options2.AdditionalSecretOutputs);
return options1;
}
static ComponentResourceOptions MergeComponentOptions(ComponentResourceOptions options1, ComponentResourceOptions options2)
{
ExpandProviders(options1);
ExpandProviders(options2);
// first, merge all the normal option values over
MergeNormalOptions(options1, options2);
options1.Providers.AddRange(options2.Providers);
if (options1.Providers.Count == 1)
{
options1.Provider = options1.Providers[0];
options1.Providers.Clear();
}
return options1;
}
static void ExpandProviders(ComponentResourceOptions options)
{
if (options.Provider != null)
{
options.Providers = new List<ProviderResource> { options.Provider };
options.Provider = null;
}
}
}
}
}

View file

@ -0,0 +1,54 @@
// Copyright 2016-2019, Pulumi Corporation
namespace Pulumi
{
/// <summary>
/// ResourceTransformation is the callback signature for <see
/// cref="ResourceOptions.ResourceTransformations"/>. A transformation is passed the same set of
/// inputs provided to the <see cref="Resource"/> constructor, and can optionally return back
/// alternate values for the <c>properties</c> and/or <c>options</c> prior to the resource
/// actually being created. The effect will be as though those <c>properties</c> and/or
/// <c>options</c> were passed in place of the original call to the <see cref="Resource"/>
/// constructor. If the transformation returns <see langword="null"/>, this indicates that the
/// resource will not be transformed.
/// </summary>
/// <returns>The new values to use for the <c>args</c> and <c>options</c> of the <see
/// cref="Resource"/> in place of the originally provided values.</returns>
public delegate ResourceTransformationResult? ResourceTransformation(ResourceTransformationArgs args);
public readonly struct ResourceTransformationArgs
{
/// <summary>
/// The Resource instance that is being transformed.
/// </summary>
public Resource Resource { get; }
/// <summary>
/// The original properties passed to the Resource constructor.
/// </summary>
public ResourceArgs Args { get; }
/// <summary>
/// The original resource options passed to the Resource constructor.
/// </summary>
public ResourceOptions Options { get; }
public ResourceTransformationArgs(
Resource resource, ResourceArgs args, ResourceOptions options)
{
Resource = resource;
Args = args;
Options = options;
}
}
public readonly struct ResourceTransformationResult
{
public ResourceArgs Args { get; }
public ResourceOptions Options { get; }
public ResourceTransformationResult(ResourceArgs args, ResourceOptions options)
{
Args = args;
Options = options;
}
}
}

View file

@ -0,0 +1,77 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using Google.Protobuf.WellKnownTypes;
namespace Pulumi.Serialization
{
/// <summary>
/// Attribute used by a Pulumi Cloud Provider Package to mark Resource output properties.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class OutputAttribute : Attribute
{
public string Name { get; }
public OutputAttribute(string name)
{
Name = name;
}
}
/// <summary>
/// Attribute used by a Pulumi Cloud Provider Package to mark Resource input fields and
/// properties.
/// <para/>
/// Note: for simple inputs (i.e. <see cref="Input{T}"/> this should just be placed on the
/// property itself. i.e. <c>[Input] Input&lt;string&gt; Acl</c>.
///
/// For collection inputs (i.e. <see cref="InputList{T}"/> this shuld be placed on the
/// backing field for the property. i.e.
///
/// <code>
/// [Input] private InputList&lt;string&gt; _acls;
/// public InputList&lt;string&gt; Acls
/// {
/// get => _acls ?? (_acls = new InputList&lt;string&gt;());
/// set => _acls = value;
/// }
/// </code>
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public sealed class InputAttribute : Attribute
{
internal string Name { get; }
internal bool IsRequired { get; }
internal bool Json { get; }
public InputAttribute(string name, bool required = false, bool json = false)
{
Name = name;
IsRequired = required;
Json = json;
}
}
/// <summary>
/// Attribute used by a Pulumi Cloud Provider Package to mark complex types used for a Resource
/// output property. A complex type must have a single constructor in it marked with the
/// <see cref="OutputConstructorAttribute"/> attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class OutputTypeAttribute : Attribute
{
}
/// <summary>
/// Attribute used by a Pulumi Cloud Provider Package to marks the constructor for a complex
/// property type so that it can be instantiated by the Pulumi runtime.
///
/// The constructor should contain parameters that map to the resultant <see
/// cref="Struct.Fields"/> returned by the engine.
/// </summary>
[AttributeUsage(AttributeTargets.Constructor)]
public sealed class OutputConstructorAttribute : Attribute
{
}
}

View file

@ -0,0 +1,43 @@
// Copyright 2016-2019, Pulumi Corporation
namespace Pulumi.Serialization
{
internal static class Constants
{
/// <summary>
/// Unknown values are encoded as a distinguished string value.
/// </summary>
public const string UnknownValue = "04da6b54-80e4-46f7-96ec-b56ff0331ba9";
/// <summary>
/// SpecialSigKey is sometimes used to encode type identity inside of a map. See pkg/resource/properties.go.
/// </summary>
public const string SpecialSigKey = "4dabf18193072939515e22adb298388d";
/// <summary>
/// SpecialAssetSig is a randomly assigned hash used to identify assets in maps. See pkg/resource/asset.go.
/// </summary>
public const string SpecialAssetSig = "c44067f5952c0a294b673a41bacd8c17";
/// <summary>
/// SpecialArchiveSig is a randomly assigned hash used to identify archives in maps. See pkg/resource/asset.go.
/// </summary>
public const string SpecialArchiveSig = "0def7320c3a5731c473e5ecbe6d01bc7";
/// <summary>
/// SpecialSecretSig is a randomly assigned hash used to identify secrets in maps. See pkg/resource/properties.go.
/// </summary>
public const string SpecialSecretSig = "1b47061264138c4ac30d75fd1eb44270";
public const string SecretValueName = "value";
public const string AssetTextName = "text";
public const string ArchiveAssetsName = "assets";
public const string AssetOrArchivePathName = "path";
public const string AssetOrArchiveUriName = "uri";
public const string IdPropertyName = "id";
public const string UrnPropertyName = "urn";
}
}

View file

@ -0,0 +1,264 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Google.Protobuf.WellKnownTypes;
namespace Pulumi.Serialization
{
internal static class Converter
{
public static OutputData<T> ConvertValue<T>(string context, Value value)
{
var (data, isKnown, isSecret) = ConvertValue(context, value, typeof(T));
return new OutputData<T>((T)data!, isKnown, isSecret);
}
public static OutputData<object?> ConvertValue(string context, Value value, System.Type targetType)
{
CheckTargetType(context, targetType);
var (deserialized, isKnown, isSecret) = Deserializer.Deserialize(value);
var converted = ConvertObject(context, deserialized, targetType);
return new OutputData<object?>(converted, isKnown, isSecret);
}
private static object? ConvertObject(string context, object? val, System.Type targetType)
{
var targetIsNullable = targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>);
// Note: 'null's can enter the system as the representation of an 'unknown' value.
// Before calling 'Convert' we will have already lifted the 'IsKnown' bit out, but we
// will be passing null around as a value.
if (val == null)
{
if (targetIsNullable)
// A 'null' value coerces to a nullable null.
return null;
if (targetType.IsValueType)
return Activator.CreateInstance(targetType);
// for all other types, can just return the null value right back out as a legal
// reference type value.
return null;
}
// We're not null and we're converting to Nullable<T>, just convert our value to be a T.
if (targetIsNullable)
return ConvertObject(context, val, targetType.GenericTypeArguments.Single());
if (targetType == typeof(string))
return EnsureType<string>(context, val);
if (targetType == typeof(bool))
return EnsureType<bool>(context, val);
if (targetType == typeof(double))
return EnsureType<double>(context, val);
if (targetType == typeof(int))
return (int)EnsureType<double>(context, val);
if (targetType == typeof(Asset))
return EnsureType<Asset>(context, val);
if (targetType == typeof(Archive))
return EnsureType<Archive>(context, val);
if (targetType == typeof(AssetOrArchive))
return EnsureType<AssetOrArchive>(context, val);
if (targetType.IsConstructedGenericType)
{
if (targetType.GetGenericTypeDefinition() == typeof(ImmutableArray<>))
return ConvertArray(context, val, targetType);
if (targetType.GetGenericTypeDefinition() == typeof(ImmutableDictionary<,>))
return ConvertDictionary(context, val, targetType);
throw new InvalidOperationException(
$"Unexpected generic target type {targetType.FullName} when deserializing {context}");
}
if (targetType.GetCustomAttribute<OutputTypeAttribute>() == null)
throw new InvalidOperationException(
$"Unexpected target type {targetType.FullName} when deserializing {context}");
var constructor = GetPropertyConstructor(targetType);
if (constructor == null)
throw new InvalidOperationException(
$"Expected target type {targetType.FullName} to have [{nameof(OutputConstructorAttribute)}] constructor when deserializing {context}");
var dictionary = EnsureType<ImmutableDictionary<string, object>>(context, val);
var constructorParameters = constructor.GetParameters();
var arguments = new object?[constructorParameters.Length];
for (int i = 0, n = constructorParameters.Length; i < n; i++)
{
var parameter = constructorParameters[i];
// Note: TryGetValue may not find a value here. That can happen for things like
// unknown vals. That's ok. We'll pass that through to 'Convert' and will get the
// default value needed for the parameter type.
dictionary.TryGetValue(parameter.Name!, out var argValue);
arguments[i] = ConvertObject($"{targetType.FullName}({parameter.Name})", argValue, parameter.ParameterType);
}
return constructor.Invoke(arguments);
}
private static T EnsureType<T>(string context, object val)
=> val is T t ? t : throw new InvalidOperationException($"Expected {typeof(T).FullName} but got {val.GetType().FullName} deserializing {context}");
private static object ConvertArray(string fieldName, object val, System.Type targetType)
{
if (!(val is ImmutableArray<object> array))
{
throw new InvalidOperationException(
$"Expected {typeof(ImmutableArray<object>).FullName} but got {val.GetType().FullName} deserializing {fieldName}");
}
var builder =
typeof(ImmutableArray).GetMethod(nameof(ImmutableArray.CreateBuilder), Array.Empty<System.Type>())!
.MakeGenericMethod(targetType.GenericTypeArguments)
.Invoke(obj: null, parameters: null)!;
var builderAdd = builder.GetType().GetMethod(nameof(ImmutableArray<int>.Builder.Add))!;
var builderToImmutable = builder.GetType().GetMethod(nameof(ImmutableArray<int>.Builder.ToImmutable))!;
var elementType = targetType.GenericTypeArguments.Single();
foreach (var element in array)
{
builderAdd.Invoke(builder, new[] { ConvertObject(fieldName, element, elementType) });
}
return builderToImmutable.Invoke(builder, null)!;
}
private static object ConvertDictionary(string fieldName, object val, System.Type targetType)
{
if (!(val is ImmutableDictionary<string, object> dictionary))
{
throw new InvalidOperationException(
$"Expected {typeof(ImmutableDictionary<string, object>).FullName} but got {val.GetType().FullName} deserializing {fieldName}");
}
if (targetType == typeof(ImmutableDictionary<string, object>))
{
// already in the form we need. no need to convert anything.
return val;
}
var keyType = targetType.GenericTypeArguments[0];
if (keyType != typeof(string))
{
throw new InvalidOperationException(
$"Unexpected type {targetType.FullName} when deserializing {fieldName}. ImmutableDictionary's TKey type was not {typeof(string).FullName}");
}
var builder =
typeof(ImmutableDictionary).GetMethod(nameof(ImmutableDictionary.CreateBuilder), Array.Empty<System.Type>())!
.MakeGenericMethod(targetType.GenericTypeArguments)
.Invoke(obj: null, parameters: null)!;
// var b = ImmutableDictionary.CreateBuilder<string, object>().Add()
var builderAdd = builder.GetType().GetMethod(nameof(ImmutableDictionary<string, object>.Builder.Add), targetType.GenericTypeArguments)!;
var builderToImmutable = builder.GetType().GetMethod(nameof(ImmutableDictionary<string, object>.Builder.ToImmutable))!;
var elementType = targetType.GenericTypeArguments[1];
foreach (var (key, element) in dictionary)
{
builderAdd.Invoke(builder, new[] { key, ConvertObject(fieldName, element, elementType) });
}
return builderToImmutable.Invoke(builder, null)!;
}
public static void CheckTargetType(string context, System.Type targetType)
{
if (targetType == typeof(bool) ||
targetType == typeof(int) ||
targetType == typeof(double) ||
targetType == typeof(string) ||
targetType == typeof(Asset) ||
targetType == typeof(Archive) ||
targetType == typeof(AssetOrArchive))
{
return;
}
if (targetType == typeof(ImmutableDictionary<string, object>))
{
// This type is what is generated for things like azure/aws tags. It's an untyped
// map in our original schema. This is the only place that `object` should appear
// as a legal value.
return;
}
if (targetType.IsConstructedGenericType)
{
if (targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
CheckTargetType(context, targetType.GenericTypeArguments.Single());
return;
}
else if (targetType.GetGenericTypeDefinition() == typeof(ImmutableArray<>))
{
CheckTargetType(context, targetType.GenericTypeArguments.Single());
return;
}
else if (targetType.GetGenericTypeDefinition() == typeof(ImmutableDictionary<,>))
{
var dictTypeArgs = targetType.GenericTypeArguments;
if (dictTypeArgs[0] != typeof(string))
{
throw new InvalidOperationException(
$@"{context} contains invalid type {targetType.FullName}:
The only allowed ImmutableDictionary 'TKey' type is 'String'.");
}
CheckTargetType(context, dictTypeArgs[1]);
return;
}
else
{
throw new InvalidOperationException(
$@"{context} contains invalid type {targetType.FullName}:
The only generic types allowed are ImmutableArray<...> and ImmutableDictionary<string, ...>");
}
}
var propertyTypeAttribute = targetType.GetCustomAttribute<OutputTypeAttribute>();
if (propertyTypeAttribute == null)
{
throw new InvalidOperationException(
$@"{context} contains invalid type {targetType.FullName}. Allowed types are:
String, Boolean, Int32, Double,
Nullable<...>, ImmutableArray<...> and ImmutableDictionary<string, ...> or
a class explicitly marked with the [{nameof(OutputTypeAttribute)}].");
}
var constructor = GetPropertyConstructor(targetType);
if (constructor == null)
{
throw new InvalidOperationException(
$@"{targetType.FullName} had [{nameof(OutputTypeAttribute)}], but did not contain constructor marked with [{nameof(OutputConstructorAttribute)}].");
}
foreach (var param in constructor.GetParameters())
{
CheckTargetType($@"{targetType.FullName}({param.Name})", param.ParameterType);
}
}
private static ConstructorInfo GetPropertyConstructor(System.Type outputTypeArg)
=> outputTypeArg.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(
c => c.GetCustomAttributes<OutputConstructorAttribute>() != null);
}
}

View file

@ -0,0 +1,209 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Google.Protobuf.Collections;
using Google.Protobuf.WellKnownTypes;
namespace Pulumi.Serialization
{
internal static class Deserializer
{
private static OutputData<T> DeserializeCore<T>(Value value, Func<Value, OutputData<T>> func)
{
var (innerVal, isSecret) = UnwrapSecret(value);
value = innerVal;
if (value.KindCase == Value.KindOneofCase.StringValue &&
value.StringValue == Constants.UnknownValue)
{
// always deserialize unknown as the null value.
return new OutputData<T>(default!, isKnown: false, isSecret);
}
if (TryDeserializeAssetOrArchive(value, out var assetOrArchive))
{
return new OutputData<T>((T)(object)assetOrArchive, isKnown: true, isSecret);
}
var innerData = func(value);
return OutputData.Create(innerData.Value, innerData.IsKnown, isSecret || innerData.IsSecret);
}
private static OutputData<T> DeserializeOneOf<T>(Value value, Value.KindOneofCase kind, Func<Value, OutputData<T>> func)
=> DeserializeCore(value, v =>
v.KindCase == kind ? func(v) : throw new InvalidOperationException($"Trying to deserialize {v.KindCase} as a {kind}"));
private static OutputData<T> DeserializePrimitive<T>(Value value, Value.KindOneofCase kind, Func<Value, T> func)
=> DeserializeOneOf(value, kind, v => OutputData.Create(func(v), isKnown: true, isSecret: false));
private static OutputData<bool> DeserializeBoolean(Value value)
=> DeserializePrimitive(value, Value.KindOneofCase.BoolValue, v => v.BoolValue);
private static OutputData<string> DeserializerString(Value value)
=> DeserializePrimitive(value, Value.KindOneofCase.StringValue, v => v.StringValue);
private static OutputData<double> DeserializerDouble(Value value)
=> DeserializePrimitive(value, Value.KindOneofCase.NumberValue, v => v.NumberValue);
private static OutputData<ImmutableArray<object?>> DeserializeList(Value value)
=> DeserializeOneOf(value, Value.KindOneofCase.ListValue,
v =>
{
var result = ImmutableArray.CreateBuilder<object?>();
var isKnown = true;
var isSecret = false;
foreach (var element in v.ListValue.Values)
{
var elementData = Deserialize(element);
(isKnown, isSecret) = OutputData.Combine(elementData, isKnown, isSecret);
result.Add(elementData.Value);
}
return OutputData.Create(result.ToImmutable(), isKnown, isSecret);
});
private static OutputData<ImmutableDictionary<string, object?>> DeserializeStruct(Value value)
=> DeserializeOneOf(value, Value.KindOneofCase.StructValue,
v =>
{
var result = ImmutableDictionary.CreateBuilder<string, object?>();
var isKnown = true;
var isSecret = false;
foreach (var (key, element) in v.StructValue.Fields)
{
var elementData = Deserialize(element);
(isKnown, isSecret) = OutputData.Combine(elementData, isKnown, isSecret);
result.Add(key, elementData.Value);
}
return OutputData.Create(result.ToImmutable(), isKnown, isSecret);
});
public static OutputData<object?> Deserialize(Value value)
=> DeserializeCore(value,
v => v.KindCase switch
{
Value.KindOneofCase.NumberValue => DeserializerDouble(v),
Value.KindOneofCase.StringValue => DeserializerString(v),
Value.KindOneofCase.BoolValue => DeserializeBoolean(v),
Value.KindOneofCase.StructValue => DeserializeStruct(v),
Value.KindOneofCase.ListValue => DeserializeList(v),
Value.KindOneofCase.NullValue => new OutputData<object?>(null, isKnown: true, isSecret: false),
Value.KindOneofCase.None => throw new InvalidOperationException("Should never get 'None' type when deserializing protobuf"),
_ => throw new InvalidOperationException("Unknown type when deserializing protobuf: " + v.KindCase),
});
private static (Value unwrapped, bool isSecret) UnwrapSecret(Value value)
{
var isSecret = false;
while (IsSpecialStruct(value, out var sig) &&
sig == Constants.SpecialSecretSig)
{
if (!value.StructValue.Fields.TryGetValue(Constants.SecretValueName, out var secretValue))
throw new InvalidOperationException("Secrets must have a field called 'value'");
isSecret = true;
value = secretValue;
}
return (value, isSecret);
}
private static bool IsSpecialStruct(
Value value, [NotNullWhen(true)] out string? sig)
{
if (value.KindCase == Value.KindOneofCase.StructValue &&
value.StructValue.Fields.TryGetValue(Constants.SpecialSigKey, out var sigVal) &&
sigVal.KindCase == Value.KindOneofCase.StringValue)
{
sig = sigVal.StringValue;
return true;
}
sig = null;
return false;
}
private static bool TryDeserializeAssetOrArchive(
Value value, [NotNullWhen(true)] out AssetOrArchive? assetOrArchive)
{
if (IsSpecialStruct(value, out var sig))
{
if (sig == Constants.SpecialAssetSig)
{
assetOrArchive = DeserializeAsset(value);
return true;
}
else if (sig == Constants.SpecialArchiveSig)
{
assetOrArchive = DeserializeArchive(value);
return true;
}
}
assetOrArchive = null;
return false;
}
private static Archive DeserializeArchive(Value value)
{
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchivePathName, out var path))
return new FileArchive(path);
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchiveUriName, out var uri))
return new RemoteArchive(uri);
if (value.StructValue.Fields.TryGetValue(Constants.ArchiveAssetsName, out var assetsValue))
{
if (assetsValue.KindCase == Value.KindOneofCase.StructValue)
{
var assets = ImmutableDictionary.CreateBuilder<string, AssetOrArchive>();
foreach (var (name, val) in assetsValue.StructValue.Fields)
{
if (!TryDeserializeAssetOrArchive(val, out var innerAssetOrArchive))
throw new InvalidOperationException("AssetArchive contained an element that wasn't itself an Asset or Archive.");
assets[name] = innerAssetOrArchive;
}
return new AssetArchive(assets.ToImmutable());
}
}
throw new InvalidOperationException("Value was marked as Archive, but did not conform to required shape.");
}
private static Asset DeserializeAsset(Value value)
{
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchivePathName, out var path))
return new FileAsset(path);
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchiveUriName, out var uri))
return new RemoteAsset(uri);
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetTextName, out var text))
return new StringAsset(text);
throw new InvalidOperationException("Value was marked as Asset, but did not conform to required shape.");
}
private static bool TryGetStringValue(
MapField<string, Value> fields, string keyName, [NotNullWhen(true)] out string? result)
{
if (fields.TryGetValue(keyName, out var value) &&
value.KindCase == Value.KindOneofCase.StringValue)
{
result = value.StringValue;
return true;
}
result = null;
return false;
}
}
}

View file

@ -0,0 +1,98 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
namespace Pulumi.Serialization
{
internal interface IOutputCompletionSource
{
Type TargetType { get; }
IOutput Output { get; }
void TrySetException(Exception exception);
void TrySetDefaultResult(bool isKnown);
void SetStringValue(string value, bool isKnown);
void SetValue(OutputData<object?> data);
}
internal class OutputCompletionSource<T> : IOutputCompletionSource
{
private readonly TaskCompletionSource<OutputData<T>> _taskCompletionSource;
public readonly Output<T> Output;
public OutputCompletionSource(Resource? resource)
{
_taskCompletionSource = new TaskCompletionSource<OutputData<T>>();
Output = new Output<T>(
resource == null ? ImmutableHashSet<Resource>.Empty : ImmutableHashSet.Create(resource),
_taskCompletionSource.Task);
}
public System.Type TargetType => typeof(T);
IOutput IOutputCompletionSource.Output => Output;
public void SetStringValue(string value, bool isKnown)
=> _taskCompletionSource.SetResult(new OutputData<T>((T)(object)value, isKnown, isSecret: false));
public void SetValue(OutputData<object?> data)
=> _taskCompletionSource.SetResult(new OutputData<T>((T)data.Value!, data.IsKnown, data.IsSecret));
public void TrySetDefaultResult(bool isKnown)
=> _taskCompletionSource.TrySetResult(new OutputData<T>(default!, isKnown, isSecret: false));
public void TrySetException(Exception exception)
=> _taskCompletionSource.TrySetException(exception);
}
internal static class OutputCompletionSource
{
public static ImmutableDictionary<string, IOutputCompletionSource> GetSources(Resource resource)
{
var name = resource.GetResourceName();
var type = resource.GetResourceType();
var query = from property in resource.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
let attr = property.GetCustomAttribute<OutputAttribute>()
where attr != null
select (property, attr);
var result = ImmutableDictionary.CreateBuilder<string, IOutputCompletionSource>();
foreach (var (prop, attr) in query.ToList())
{
var propType = prop.PropertyType;
var propFullName = $"[Output] {resource.GetType().FullName}.{prop.Name}";
if (!propType.IsConstructedGenericType &&
propType.GetGenericTypeDefinition() != typeof(Output<>))
{
throw new InvalidOperationException($"{propFullName} was not an Output<T>");
}
var setMethod = prop.DeclaringType!.GetMethod("set_" + prop.Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (setMethod == null)
{
throw new InvalidOperationException($"{propFullName} did not have a 'set' method");
}
var outputTypeArg = propType.GenericTypeArguments.Single();
Converter.CheckTargetType(propFullName, outputTypeArg);
var ocsType = typeof(OutputCompletionSource<>).MakeGenericType(outputTypeArg);
var ocsContructor = ocsType.GetConstructors().Single();
var completionSource = (IOutputCompletionSource)ocsContructor.Invoke(new[] { resource });
setMethod.Invoke(resource, new[] { completionSource.Output });
result.Add(attr.Name, completionSource);
}
Log.Debug("Fields to assign: " + JsonSerializer.Serialize(result.Keys), resource);
return result.ToImmutable();
}
}
}

View file

@ -0,0 +1,37 @@
// Copyright 2016-2019, Pulumi Corporation
namespace Pulumi.Serialization
{
internal static class OutputData
{
public static OutputData<X> Create<X>(X value, bool isKnown, bool isSecret)
=> new OutputData<X>(value, isKnown, isSecret);
public static (bool isKnown, bool isSecret) Combine<X>(OutputData<X> data, bool isKnown, bool isSecret)
=> (isKnown && data.IsKnown, isSecret || data.IsSecret);
}
internal struct OutputData<X>
{
public readonly X Value;
public readonly bool IsKnown;
public readonly bool IsSecret;
public OutputData(X value, bool isKnown, bool isSecret)
{
Value = value;
IsKnown = isKnown;
IsSecret = isSecret;
}
public static implicit operator OutputData<object?>(OutputData<X> data)
=> new OutputData<object?>(data.Value, data.IsKnown, data.IsSecret);
public void Deconstruct(out X value, out bool isKnown, out bool isSecret)
{
value = Value;
isKnown = IsKnown;
isSecret = IsSecret;
}
}
}

View file

@ -0,0 +1,297 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections;
using System.Collections.Immutable;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
namespace Pulumi.Serialization
{
internal struct Serializer
{
public readonly HashSet<Resource> DependentResources;
private readonly bool _excessiveDebugOutput;
public Serializer(bool excessiveDebugOutput)
{
this.DependentResources = new HashSet<Resource>();
_excessiveDebugOutput = excessiveDebugOutput;
}
/// <summary>
/// Takes in an arbitrary object and serializes it into a uniform form that can converted
/// trivially to a protobuf to be passed to the Pulumi engine.
/// <para/>
/// The allowed 'basis' forms that can be serialized are:
/// <list type="number">
/// <item><see langword="null"/>s</item>
/// <item><see cref="bool"/>s</item>
/// <item><see cref="int"/>s</item>
/// <item><see cref="double"/>s</item>
/// <item><see cref="string"/>s</item>
/// <item><see cref="Asset"/>s</item>
/// <item><see cref="Archive"/>s</item>
/// <item><see cref="Resource"/>s</item>
/// <item><see cref="ResourceArgs"/>s</item>
/// </list>
/// Additionally, other more complex objects can be serialized as long as they are built
/// out of serializable objects. These complex objects include:
/// <list type="number">
/// <item><see cref="Input{T}"/>s. As long as they are an Input of a serializable type.</item>
/// <item><see cref="Output{T}"/>s. As long as they are an Output of a serializable type.</item>
/// <item><see cref="IList"/>s. As long as all elements in the list are serializable.</item>
/// <item><see cref="IDictionary"/>. As long as the key of the dictionary are <see cref="string"/>s and as long as the value are all serializable.</item>
/// </list>
/// No other forms are allowed.
/// <para/>
/// This function will only return values of a very specific shape. Specifically, the
/// result values returned will *only* be one of:
/// <para/>
/// <list type="number">
/// <item><see langword="null"/></item>
/// <item><see cref="bool"/></item>
/// <item><see cref="int"/></item>
/// <item><see cref="double"/></item>
/// <item><see cref="string"/></item>
/// <item>An <see cref="ImmutableArray{T}"/> containing only these result value types.</item>
/// <item>An <see cref="IImmutableDictionary{TKey, TValue}"/> where the keys are strings and
/// the values are only these result value types.</item>
/// </list>
/// No other result type are allowed to be returned.
/// </summary>
public async Task<object?> SerializeAsync(string ctx, object? prop)
{
// IMPORTANT:
// IMPORTANT: Keep this in sync with serializesPropertiesSync in invoke.ts
// IMPORTANT:
if (prop == null ||
prop is bool ||
prop is int ||
prop is double ||
prop is string)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: primitive={prop}");
}
return prop;
}
if (prop is ResourceArgs args)
return await SerializeResourceArgsAsync(ctx, args).ConfigureAwait(false);
if (prop is AssetOrArchive assetOrArchive)
return await SerializeAssetOrArchiveAsync(ctx, assetOrArchive).ConfigureAwait(false);
if (prop is Task)
{
throw new InvalidOperationException(
$"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output:\n\t{ctx}");
}
if (prop is IInput input)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: Recursing into input");
}
return await SerializeAsync(ctx, input.ToOutput()).ConfigureAwait(false);
}
if (prop is IOutput output)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: Recursing into Output");
}
this.DependentResources.AddRange(output.Resources);
var data = await output.GetDataAsync().ConfigureAwait(false);
// When serializing an Output, we will either serialize it as its resolved value or the "unknown value"
// sentinel. We will do the former for all outputs created directly by user code (such outputs always
// resolve isKnown to true) and for any resource outputs that were resolved with known values.
var isKnown = data.IsKnown;
var isSecret = data.IsSecret;
if (!isKnown)
return Constants.UnknownValue;
var value = await SerializeAsync($"{ctx}.id", data.Value).ConfigureAwait(false);
if (isSecret)
{
var builder = ImmutableDictionary.CreateBuilder<string, object?>();
builder.Add(Constants.SpecialSigKey, Constants.SpecialSecretSig);
builder.Add(Constants.SecretValueName, value);
return builder.ToImmutable();
}
return value;
}
if (prop is CustomResource customResource)
{
// Resources aren't serializable; instead, we serialize them as references to the ID property.
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: Encountered CustomResource");
}
this.DependentResources.Add(customResource);
return await SerializeAsync($"{ctx}.id", customResource.Id).ConfigureAwait(false);
}
if (prop is ComponentResource componentResource)
{
// Component resources often can contain cycles in them. For example, an awsinfra
// SecurityGroupRule can point a the awsinfra SecurityGroup, which in turn can point
// back to its rules through its 'egressRules' and 'ingressRules' properties. If
// serializing out the 'SecurityGroup' resource ends up trying to serialize out
// those properties, a deadlock will happen, due to waiting on the child, which is
// waiting on the parent.
//
// Practically, there is no need to actually serialize out a component. It doesn't
// represent a real resource, nor does it have normal properties that need to be
// tracked for differences (since changes to its properties don't represent changes
// to resources in the real world).
//
// So, to avoid these problems, while allowing a flexible and simple programming
// model, we just serialize out the component as its urn. This allows the component
// to be identified and tracked in a reasonable manner, while not causing us to
// compute or embed information about it that is not needed, and which can lead to
// deadlocks.
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: Encountered ComponentResource");
}
return await SerializeAsync($"{ctx}.urn", componentResource.Urn).ConfigureAwait(false);
}
if (prop is IDictionary dictionary)
return await SerializeDictionaryAsync(ctx, dictionary).ConfigureAwait(false);
if (prop is IList list)
return await SerializeListAsync(ctx, list).ConfigureAwait(false);
throw new InvalidOperationException($"{prop.GetType().FullName} is not a supported argument type.\n\t{ctx}");
}
private async Task<ImmutableDictionary<string, object>> SerializeAssetOrArchiveAsync(string ctx, AssetOrArchive assetOrArchive)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: asset/archive={assetOrArchive.GetType().Name}");
}
var propName = assetOrArchive.PropName;
var value = await SerializeAsync(ctx + "." + propName, assetOrArchive.Value).ConfigureAwait(false);
var builder = ImmutableDictionary.CreateBuilder<string, object>();
builder.Add(Constants.SpecialSigKey, assetOrArchive.SigKey);
builder.Add(assetOrArchive.PropName, value!);
return builder.ToImmutable();
}
private async Task<ImmutableDictionary<string, object>> SerializeResourceArgsAsync(string ctx, ResourceArgs args)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: Recursing into ResourceArgs");
}
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
return await SerializeDictionaryAsync(ctx, dictionary).ConfigureAwait(false);
}
private async Task<ImmutableArray<object?>> SerializeListAsync(string ctx, IList list)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: Hit list");
}
var result = ImmutableArray.CreateBuilder<object?>(list.Count);
for (int i = 0, n = list.Count; i < n; i++)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: array[{i}] element");
}
result.Add(await SerializeAsync($"{ctx}[{i}]", list[i]).ConfigureAwait(false));
}
return result.MoveToImmutable();
}
private async Task<ImmutableDictionary<string, object>> SerializeDictionaryAsync(string ctx, IDictionary dictionary)
{
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: Hit dictionary");
}
var result = ImmutableDictionary.CreateBuilder<string, object>();
foreach (var key in dictionary.Keys)
{
if (!(key is string stringKey))
{
throw new InvalidOperationException(
$"Dictionaries are only supported with string keys:\n\t{ctx}");
}
if (_excessiveDebugOutput)
{
Log.Debug($"Serialize property[{ctx}]: object.{stringKey}");
}
// When serializing an object, we omit any keys with null values. This matches
// JSON semantics.
var v = await SerializeAsync($"{ctx}.{stringKey}", dictionary[stringKey]).ConfigureAwait(false);
if (v != null)
{
result[stringKey] = v;
}
}
return result.ToImmutable();
}
/// <summary>
/// Internal for testing purposes.
/// </summary>
internal static Value CreateValue(object? value)
=> value switch
{
null => Value.ForNull(),
int i => Value.ForNumber(i),
double d => Value.ForNumber(d),
bool b => Value.ForBool(b),
string s => Value.ForString(s),
ImmutableArray<object> list => Value.ForList(list.Select(v => CreateValue(v)).ToArray()),
ImmutableDictionary<string, object> dict => Value.ForStruct(CreateStruct(dict)),
_ => throw new InvalidOperationException("Unsupported value when converting to protobuf: " + value.GetType().FullName),
};
/// <summary>
/// Given a <see cref="ImmutableDictionary{TKey, TValue}"/> produced by <see cref="SerializeAsync"/>,
/// produces the equivalent <see cref="Struct"/> that can be passed to the Pulumi engine.
/// </summary>
public static Struct CreateStruct(ImmutableDictionary<string, object> serializedDictionary)
{
var result = new Struct();
foreach (var key in serializedDictionary.Keys.OrderBy(k => k))
{
result.Fields.Add(key, CreateValue(serializedDictionary[key]));
}
return result;
}
}
}

View file

@ -0,0 +1,77 @@
// Copyright 2016-2019, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
namespace Pulumi
{
/// <summary>
/// Stack is the root resource for a Pulumi stack. Before invoking the <c>init</c> callback, it
/// registers itself as the root resource with the Pulumi engine.
///
/// An instance of this will be automatically created when any <see
/// cref="Deployment.RunAsync(Action)"/> overload is called.
/// </summary>
internal sealed class Stack : ComponentResource
{
/// <summary>
/// Constant to represent the 'root stack' resource for a Pulumi application. The purpose
/// of this is solely to make it easy to write an <see cref="Alias"/> like so:
///
/// <c>aliases = { new Alias { Parent = Pulumi.Stack.Root } }</c>
///
/// 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:
///
/// <c>aliases = { new Alias { Parent = null } }</c>
///
/// 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.
/// </summary>
public static readonly Resource? Root = null;
/// <summary>
/// <see cref="_rootPulumiStackTypeName"/> 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
/// <c>github.com/pulumi/pulumi/pkg/resource/stack.RootPulumiStackTypeName</c>.
/// </summary>
internal const string _rootPulumiStackTypeName = "pulumi:pulumi:Stack";
/// <summary>
/// The outputs of this stack, if the <c>init</c> callback exited normally.
/// </summary>
public readonly Output<IDictionary<string, object>> Outputs =
Output.Create<IDictionary<string, object>>(ImmutableDictionary<string, object>.Empty);
internal Stack(Func<Task<IDictionary<string, object>>> init)
: base(_rootPulumiStackTypeName, $"{Deployment.Instance.ProjectName}-{Deployment.Instance.StackName}")
{
Deployment.InternalInstance.Stack = this;
try
{
this.Outputs = Output.Create(RunInitAsync(init));
}
finally
{
this.RegisterOutputs(this.Outputs);
}
}
private async Task<IDictionary<string, object>> RunInitAsync(Func<Task<IDictionary<string, object>>> init)
{
// Ensure we are known as the root resource. This is needed before we execute any user
// code as many codepaths will request the root resource.
await Deployment.InternalInstance.SetRootResourceAsync(this).ConfigureAwait(false);
var dictionary = await init().ConfigureAwait(false);
return dictionary == null
? ImmutableDictionary<string, object>.Empty
: dictionary.ToImmutableDictionary();
}
}
}

78
sdk/dotnet/README.md Normal file
View file

@ -0,0 +1,78 @@
# Experimental .NET Language Provider
An early prototype of a .NET language provider for Pulumi.
## Building and Running
To build, you'll want to install the .NET Core 3.0 SDK or greater, and ensure
`dotnet` is on your path. Once that it does, running `make` in either the root
directory or the `sdk/dotnet` directory will build and install the language
plugin.
Once this is done you can write a Pulumi app written on top of .NET. See the
`sdk/dotnet/examples` directory showing how this can be done with C#, F#, or VB.
Your application will need to reference the `Pulumi.dll` built above.
Here's a simple example of a Pulumi app written in C# that creates some simple
AWS resources:
```c#
// Copyright 2016-2019, Pulumi Corporation
using System.Collections.Generic;
using System.Threading.Tasks;
using Pulumi;
using Pulumi.Aws.S3;
class Program
{
static Task<int> Main()
=> Deployment.RunAsync(() =>
{
var config = new Config("hello-dotnet");
var name = config.Require("name");
// Create the bucket, and make it public.
var bucket = new Bucket(name, new BucketArgs { Acl = "public-read" });
// Add some content.
var content = new BucketObject($"{name}-content", new BucketObjectArgs
{
Acl = "public-read",
Bucket = bucket.Id,
ContentType = "text/plain; charset=utf8",
Key = "hello.txt",
Source = new StringAsset("Made with ❤, Pulumi, and .NET"),
});
// Return some values that will become the Outputs of the stack.
return new Dictionary<string, object>
{
{ "hello", "world" },
{ "bucket-id", bucket.Id },
{ "content-id", content.Id },
{ "object-url", Output.Format($"http://{bucket.BucketDomainName}/{content.Key}") },
};
});
}
```
Make a Pulumi.yaml file:
```
$ cat Pulumi.yaml
name: hello-dotnet
runtime: dotnet
```
Then, configure it:
```
$ pulumi stack init hello-dotnet
$ pulumi config set name hello-dotnet
$ pulumi config set aws:region us-west-2
```
And finally, preview and update as you would any other Pulumi project.

View file

@ -0,0 +1,355 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
// pulumi-language-dotnet serves as the "language host" for Pulumi programs written in .NET. It is ultimately
// responsible for spawning the language runtime that executes the program.
//
// The program being executed is executed by a shim exe called `pulumi-language-dotnet-exec`. This script is
// written in the hosted language (in this case, C#) and is responsible for initiating RPC links to the resource
// monitor and engine.
//
// It's therefore the responsibility of this program to implement the LanguageHostServer endpoint by spawning
// instances of `pulumi-language-dotnet-exec` and forwarding the RPC request arguments to the command-line.
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"math/rand"
"os"
"os/exec"
"strings"
"syscall"
"github.com/golang/glog"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pulumi/pulumi/pkg/util/logging"
"github.com/pulumi/pulumi/pkg/util/rpcutil"
"github.com/pulumi/pulumi/pkg/version"
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
"google.golang.org/grpc"
)
var (
// A exit-code we recognize when the nodejs process exits. If we see this error, there's no
// need for us to print any additional error messages since the user already got a a good
// one they can handle.
dotnetProcessExitedAfterShowingUserActionableMessage = 32
)
// Launches the language host RPC endpoint, which in turn fires up an RPC server implementing the
// LanguageRuntimeServer RPC endpoint.
func main() {
var tracing string
flag.StringVar(&tracing, "tracing", "", "Emit tracing to a Zipkin-compatible tracing endpoint")
// You can use the below flag to request that the language host load a specific executor instead of probing the
// PATH. This can be used during testing to override the default location.
var givenExecutor string
flag.StringVar(&givenExecutor, "use-executor", "",
"Use the given program as the executor instead of looking for one on PATH")
flag.Parse()
args := flag.Args()
logging.InitLogging(false, 0, false)
cmdutil.InitTracing("pulumi-language-dotnet", "pulumi-language-dotnet", tracing)
var dotnetExec string
if givenExecutor == "" {
pathExec, err := exec.LookPath("dotnet")
if err != nil {
err = errors.Wrap(err, "could not find `dotnet` on the $PATH")
cmdutil.Exit(err)
}
glog.V(3).Infof("language host identified executor from path: `%s`", pathExec)
dotnetExec = pathExec
} else {
glog.V(3).Infof("language host asked to use specific executor: `%s`", givenExecutor)
dotnetExec = givenExecutor
}
// Optionally pluck out the engine so we can do logging, etc.
var engineAddress string
if len(args) > 0 {
engineAddress = args[0]
}
// Fire up a gRPC server, letting the kernel choose a free port.
port, done, err := rpcutil.Serve(0, nil, []func(*grpc.Server) error{
func(srv *grpc.Server) error {
host := newLanguageHost(dotnetExec, engineAddress, tracing)
pulumirpc.RegisterLanguageRuntimeServer(srv, host)
return nil
},
}, nil)
if err != nil {
cmdutil.Exit(errors.Wrapf(err, "could not start language host RPC server"))
}
// Otherwise, print out the port so that the spawner knows how to reach us.
fmt.Printf("%d\n", port)
// And finally wait for the server to stop serving.
if err := <-done; err != nil {
cmdutil.Exit(errors.Wrapf(err, "language host RPC stopped serving"))
}
}
// dotnetLanguageHost implements the LanguageRuntimeServer interface
// for use as an API endpoint.
type dotnetLanguageHost struct {
exec string
engineAddress string
tracing string
}
func newLanguageHost(exec, engineAddress, tracing string) pulumirpc.LanguageRuntimeServer {
return &dotnetLanguageHost{
exec: exec,
engineAddress: engineAddress,
tracing: tracing,
}
}
// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
func (host *dotnetLanguageHost) GetRequiredPlugins(ctx context.Context,
req *pulumirpc.GetRequiredPluginsRequest) (*pulumirpc.GetRequiredPluginsResponse, error) {
// TODO: implement this.
return &pulumirpc.GetRequiredPluginsResponse{}, nil
}
// RPC endpoint for LanguageRuntimeServer::Run
func (host *dotnetLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) {
if err := host.DotnetBuild(ctx, req); err != nil {
return &pulumirpc.RunResponse{Error: err.Error()}, nil
}
return host.DotnetRun(ctx, req)
}
func (host *dotnetLanguageHost) DotnetBuild(ctx context.Context, req *pulumirpc.RunRequest) error {
args := []string{"build"}
if req.GetProgram() != "" {
args = append(args, req.GetProgram())
}
if glog.V(5) {
commandStr := strings.Join(args, " ")
glog.V(5).Infoln("Language host launching process: ", host.exec, commandStr)
}
// Make a connection to the real engine that we will log messages to.
conn, err := grpc.Dial(host.engineAddress, grpc.WithInsecure())
if err != nil {
return errors.Wrapf(err, "language host could not make connection to engine")
}
// Make a client around that connection. We can then make our own server that will act as a
// monitor for the sdk and forward to the real monitor.
engineClient := pulumirpc.NewEngineClient(conn)
// Buffer the writes we see from dotnet from its stdout and stderr streams. We will display
// these ephemerally as `dotnet build` runs. If the build does fail though, we will dump
// messages back to our own stdout/stderr so they get picked up and displayed to the user.
streamID := rand.Int31()
infoBuffer := &bytes.Buffer{}
errorBuffer := &bytes.Buffer{}
infoWriter := &logWriter{
ctx: ctx,
engineClient: engineClient,
streamID: streamID,
buffer: infoBuffer,
severity: pulumirpc.LogSeverity_INFO,
}
errorWriter := &logWriter{
ctx: ctx,
engineClient: engineClient,
streamID: streamID,
buffer: errorBuffer,
severity: pulumirpc.LogSeverity_ERROR,
}
// Now simply spawn a process to execute the requested program, wiring up stdout/stderr directly.
cmd := exec.Command(host.exec, args...) // nolint: gas, intentionally running dynamic program name.
cmd.Stdout = infoWriter
cmd.Stderr = errorWriter
_, err = engineClient.Log(ctx, &pulumirpc.LogRequest{
Message: "running 'dotnet build'",
Urn: "",
Ephemeral: true,
StreamId: streamID,
Severity: pulumirpc.LogSeverity_INFO,
})
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
// The command failed. Dump any data we collected to the actual stdout/stderr streams so
// they get displayed to the user.
os.Stdout.Write(infoBuffer.Bytes())
os.Stderr.Write(errorBuffer.Bytes())
if exiterr, ok := err.(*exec.ExitError); ok {
// If the program ran, but exited with a non-zero error code. This will happen often, since user
// errors will trigger this. So, the error message should look as nice as possible.
if status, stok := exiterr.Sys().(syscall.WaitStatus); stok {
return errors.Errorf("'dotnet build' exited with non-zero exit code: %d", status.ExitStatus())
}
return errors.Wrapf(exiterr, "'dotnet build' exited unexpectedly")
}
// Otherwise, we didn't even get to run the program. This ought to never happen unless there's
// a bug or system condition that prevented us from running the language exec. Issue a scarier error.
return errors.Wrapf(err, "Problem executing 'dotnet build'")
}
_, err = engineClient.Log(ctx, &pulumirpc.LogRequest{
Message: "'dotnet build' completed successfully",
Urn: "",
Ephemeral: true,
StreamId: streamID,
Severity: pulumirpc.LogSeverity_INFO,
})
return err
}
type logWriter struct {
ctx context.Context
engineClient pulumirpc.EngineClient
streamID int32
severity pulumirpc.LogSeverity
buffer *bytes.Buffer
}
func (w *logWriter) Write(p []byte) (n int, err error) {
n, err = w.buffer.Write(p)
if err != nil {
return
}
_, err = w.engineClient.Log(w.ctx, &pulumirpc.LogRequest{
Message: string(p),
Urn: "",
Ephemeral: true,
StreamId: w.streamID,
Severity: w.severity,
})
if err != nil {
return 0, err
}
return len(p), nil
}
func (host *dotnetLanguageHost) DotnetRun(
ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) {
config, err := host.constructConfig(req)
if err != nil {
err = errors.Wrap(err, "failed to serialize configuration")
return nil, err
}
args := []string{"run"}
if req.GetProgram() != "" {
args = append(args, req.GetProgram())
}
if glog.V(5) {
commandStr := strings.Join(args, " ")
glog.V(5).Infoln("Language host launching process: ", host.exec, commandStr)
}
// Now simply spawn a process to execute the requested program, wiring up stdout/stderr directly.
var errResult string
cmd := exec.Command(host.exec, args...) // nolint: gas, intentionally running dynamic program name.
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = host.constructEnv(req, config)
if err := cmd.Run(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// If the program ran, but exited with a non-zero error code. This will happen often, since user
// errors will trigger this. So, the error message should look as nice as possible.
if status, stok := exiterr.Sys().(syscall.WaitStatus); stok {
// Check if we got special exit code that means "we already gave the user an
// actionable message". In that case, we can simply bail out and terminate `pulumi`
// without showing any more messages.
if status.ExitStatus() == dotnetProcessExitedAfterShowingUserActionableMessage {
return &pulumirpc.RunResponse{Error: "", Bail: true}, nil
}
err = errors.Errorf("Program exited with non-zero exit code: %d", status.ExitStatus())
} else {
err = errors.Wrapf(exiterr, "Program exited unexpectedly")
}
} else {
// Otherwise, we didn't even get to run the program. This ought to never happen unless there's
// a bug or system condition that prevented us from running the language exec. Issue a scarier error.
err = errors.Wrapf(err, "Problem executing program (could not run language executor)")
}
errResult = err.Error()
}
return &pulumirpc.RunResponse{Error: errResult}, nil
}
func (host *dotnetLanguageHost) constructEnv(req *pulumirpc.RunRequest, config string) []string {
env := os.Environ()
maybeAppendEnv := func(k, v string) {
if v != "" {
env = append(env, strings.ToUpper("PULUMI_"+k)+"="+v)
}
}
maybeAppendEnv("monitor", req.GetMonitorAddress())
maybeAppendEnv("engine", host.engineAddress)
maybeAppendEnv("project", req.GetProject())
maybeAppendEnv("stack", req.GetStack())
maybeAppendEnv("pwd", req.GetPwd())
maybeAppendEnv("dry_run", fmt.Sprintf("%v", req.GetDryRun()))
maybeAppendEnv("query_mode", fmt.Sprint(req.GetQueryMode()))
maybeAppendEnv("parallel", fmt.Sprint(req.GetParallel()))
maybeAppendEnv("tracing", host.tracing)
maybeAppendEnv("config", config)
return env
}
// constructConfig json-serializes the configuration data given as part of a RunRequest.
func (host *dotnetLanguageHost) constructConfig(req *pulumirpc.RunRequest) (string, error) {
configMap := req.GetConfig()
if configMap == nil {
return "", nil
}
configJSON, err := json.Marshal(configMap)
if err != nil {
return "", err
}
return string(configJSON), nil
}
func (host *dotnetLanguageHost) GetPluginInfo(ctx context.Context, req *pbempty.Empty) (*pulumirpc.PluginInfo, error) {
return &pulumirpc.PluginInfo{
Version: version.Version,
}, nil
}

150
sdk/dotnet/dotnet.sln Normal file
View file

@ -0,0 +1,150 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29411.138
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pulumi", "Pulumi\Pulumi.csproj", "{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{F634CAAC-92CB-42A4-8229-37EABD9CAC5D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "bucket", "examples\bucket\bucket.csproj", "{02C0AC19-181C-4B6E-BD07-D98B5DC81370}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Pulumi.Tests", "Pulumi.Tests\Pulumi.Tests.csproj", "{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PulumiAzure", "examples\PulumiAzure\PulumiAzure.csproj", "{67972C68-173F-40D5-B0E5-974FC620943C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpExamples", "examples\CSharpExamples\CSharpExamples.csproj", "{E93FA892-060D-47A3-951E-C061C6487482}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharpExamples", "examples\FSharpExamples\FSharpExamples.fsproj", "{193DBB9E-BE39-4E32-BEC3-B40762F18C67}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Pulumi.FSharp", "Pulumi.FSharp\Pulumi.FSharp.fsproj", "{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}"
EndProject
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "VBExamples", "examples\VBExamples\VBExamples.vbproj", "{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D4626281-DB93-4BB3-B23C-02F47032E4FA}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Debug|x64.ActiveCfg = Debug|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Debug|x64.Build.0 = Debug|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Debug|x86.ActiveCfg = Debug|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Debug|x86.Build.0 = Debug|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|Any CPU.Build.0 = Release|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|x64.ActiveCfg = Release|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|x64.Build.0 = Release|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|x86.ActiveCfg = Release|Any CPU
{0A2BFED8-13F3-43A4-A38B-B5D1651203EB}.Release|x86.Build.0 = Release|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Debug|x64.ActiveCfg = Debug|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Debug|x64.Build.0 = Debug|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Debug|x86.ActiveCfg = Debug|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Debug|x86.Build.0 = Debug|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Release|Any CPU.Build.0 = Release|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Release|x64.ActiveCfg = Release|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Release|x64.Build.0 = Release|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Release|x86.ActiveCfg = Release|Any CPU
{02C0AC19-181C-4B6E-BD07-D98B5DC81370}.Release|x86.Build.0 = Release|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Debug|x64.ActiveCfg = Debug|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Debug|x64.Build.0 = Debug|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Debug|x86.ActiveCfg = Debug|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Debug|x86.Build.0 = Debug|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Release|Any CPU.Build.0 = Release|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Release|x64.ActiveCfg = Release|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Release|x64.Build.0 = Release|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Release|x86.ActiveCfg = Release|Any CPU
{B732E03B-BA4D-4F4A-AE38-5833BC2DAC7C}.Release|x86.Build.0 = Release|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Debug|x64.ActiveCfg = Debug|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Debug|x64.Build.0 = Debug|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Debug|x86.ActiveCfg = Debug|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Debug|x86.Build.0 = Debug|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Release|Any CPU.Build.0 = Release|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Release|x64.ActiveCfg = Release|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Release|x64.Build.0 = Release|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Release|x86.ActiveCfg = Release|Any CPU
{67972C68-173F-40D5-B0E5-974FC620943C}.Release|x86.Build.0 = Release|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Debug|x64.ActiveCfg = Debug|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Debug|x64.Build.0 = Debug|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Debug|x86.ActiveCfg = Debug|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Debug|x86.Build.0 = Debug|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Release|Any CPU.Build.0 = Release|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Release|x64.ActiveCfg = Release|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Release|x64.Build.0 = Release|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Release|x86.ActiveCfg = Release|Any CPU
{E93FA892-060D-47A3-951E-C061C6487482}.Release|x86.Build.0 = Release|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Debug|x64.ActiveCfg = Debug|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Debug|x64.Build.0 = Debug|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Debug|x86.ActiveCfg = Debug|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Debug|x86.Build.0 = Debug|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Release|Any CPU.Build.0 = Release|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Release|x64.ActiveCfg = Release|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Release|x64.Build.0 = Release|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Release|x86.ActiveCfg = Release|Any CPU
{193DBB9E-BE39-4E32-BEC3-B40762F18C67}.Release|x86.Build.0 = Release|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Debug|x64.ActiveCfg = Debug|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Debug|x64.Build.0 = Debug|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Debug|x86.ActiveCfg = Debug|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Debug|x86.Build.0 = Debug|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Release|Any CPU.Build.0 = Release|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Release|x64.ActiveCfg = Release|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Release|x64.Build.0 = Release|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Release|x86.ActiveCfg = Release|Any CPU
{F45E8B4A-DAF3-48E8-B9D6-01924AF2188D}.Release|x86.Build.0 = Release|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Debug|x64.ActiveCfg = Debug|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Debug|x64.Build.0 = Debug|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Debug|x86.ActiveCfg = Debug|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Debug|x86.Build.0 = Debug|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Release|Any CPU.Build.0 = Release|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Release|x64.ActiveCfg = Release|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Release|x64.Build.0 = Release|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Release|x86.ActiveCfg = Release|Any CPU
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{02C0AC19-181C-4B6E-BD07-D98B5DC81370} = {F634CAAC-92CB-42A4-8229-37EABD9CAC5D}
{E93FA892-060D-47A3-951E-C061C6487482} = {F634CAAC-92CB-42A4-8229-37EABD9CAC5D}
{193DBB9E-BE39-4E32-BEC3-B40762F18C67} = {F634CAAC-92CB-42A4-8229-37EABD9CAC5D}
{E2662E3A-4F40-42EB-B0DB-8F9166C2CE61} = {F634CAAC-92CB-42A4-8229-37EABD9CAC5D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {273EE841-009F-495B-A58A-681A8CC9DBAC}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\PulumiAzure\PulumiAzure.csproj" />
<ProjectReference Include="..\..\Pulumi\Pulumi.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,153 @@
// Copyright 2016-2019, Pulumi Corporation
#nullable enable
using System.Collections.Generic;
using Pulumi.Azure.Core;
using Pulumi.Azure.AppService;
using Pulumi.Azure.Storage;
using CosmosDB = Pulumi.Azure.CosmosDB;
using System;
using System.Linq;
namespace Pulumi.CSharpExamples
{
public class CosmosAppArgs
{
public Input<string>? ResourceGroupName { get; set; }
public InputList<string>? Locations { get; set; }
public Input<string>? DatabaseName { get; set; }
public Input<string>? ContainerName { get; set; }
}
public class CosmosApp : ComponentResource
{
public CosmosApp(string name, CosmosAppArgs args, ResourceOptions? options = null)
: base("examples:azure:CosmosApp", name, options)
{
if (args.Locations == null)
{
throw new ArgumentException(nameof(args.Locations));
}
var primaryLocation = args.Locations.ToOutput().Apply(ls => ls[0]);
var locations = args.Locations.ToOutput();
var cosmosAccount = new CosmosDB.Account($"cosmos-{name}",
new CosmosDB.AccountArgs
{
ResourceGroupName = args.ResourceGroupName,
Location = primaryLocation,
GeoLocations = locations.Apply(ls =>
ls.Select((l, i) =>
{
return new CosmosDB.AccountGeoLocation
{
Location = l,
FailoverPriority = i
};
})),
OfferType = "Standard",
ConsistencyPolicy = new CosmosDB.AccountConsistencyPolicy
{
ConsistencyLevel = "Session",
},
});
}
}
public class GlobalApp
{
public static Dictionary<string, object> Run()
{
var resourceGroup = new ResourceGroup("dotnet-rg", new ResourceGroupArgs
{
Location = "West Europe"
});
var cosmosapp = new CosmosApp("capp", new CosmosAppArgs
{
ResourceGroupName = resourceGroup.Name,
Locations = new[] { resourceGroup.Location },
});
var storageAccount = new Account("sa", new AccountArgs
{
ResourceGroupName = resourceGroup.Name,
AccountReplicationType = "LRS",
AccountTier = "Standard",
});
var appServicePlan = new Plan("asp", new PlanArgs
{
ResourceGroupName = resourceGroup.Name,
Kind = "App",
Sku = new PlanSkuArgs
{
Tier = "Basic",
Size = "B1",
},
});
var container = new Container("c", new ContainerArgs
{
StorageAccountName = storageAccount.Name,
ContainerAccessType = "private",
});
var blob = new ZipBlob("zip", new ZipBlobArgs
{
StorageAccountName = storageAccount.Name,
StorageContainerName = container.Name,
Type = "block",
Content = new FileArchive("wwwroot"),
});
var codeBlobUrl = SharedAccessSignature.SignedBlobReadUrl(blob, storageAccount);
//var username = "sa"; // TODO: Pulumi.Config
//var password = "pwd";
//var sqlServer = new SqlServer("sql", new SqlServerArgs
//{
// ResourceGroupName = resourceGroup.Name,
// AdministratorLogin = username,
// AdministratorLoginPassword = password,
// Version = "12.0",
//});
//var database = new Database("db", new DatabaseArgs
//{
// ResourceGroupName = resourceGroup.Name,
// ServerName = sqlServer.Name,
// RequestedServiceObjectiveName = "S0",
//});
var app = new AppService("app", new AppServiceArgs
{
ResourceGroupName = resourceGroup.Name,
AppServicePlanId = appServicePlan.Id,
AppSettings =
{
{ "WEBSITE_RUN_FROM_ZIP", codeBlobUrl },
},
//ConnectionStrings = new[]
//{
// new AppService.ConnectionStringArgs
// {
// Name = "db",
// Type = "SQLAzure",
// Value = Output.All<string>(sqlServer.Name, database.Name).Apply(values =>
// {
// return $"Server= tcp:${values[0]}.database.windows.net;initial catalog=${values[1]};userID=${username};password=${password};Min Pool Size=0;Max Pool Size=30;Persist Security Info=true;";
// }),
// },
//},
});
return new Dictionary<string, object>
{
{ "endpoint", app.DefaultSiteHostname },
};
}
}
}

View file

@ -0,0 +1,33 @@
// Copyright 2016-2019, Pulumi Corporation
#nullable enable
using System.Collections.Generic;
using Pulumi.Azure.Core;
using Storage = Pulumi.Azure.Storage;
namespace Pulumi.CSharpExamples
{
public class Minimal
{
public static IDictionary<string, Output<string>> Run()
{
var resourceGroup = new ResourceGroup("rg", new ResourceGroupArgs { Location = "West Europe" });
// "Account" without a namespace would be too vague, while "ResourceGroup" without namespace sounds good.
// We could suggest always using the namespace, but this makes new-ing of Args even longer and uglier?
var storageAccount = new Storage.Account("sa", new Storage.AccountArgs
{
ResourceGroupName = resourceGroup.Name,
AccountReplicationType = "LRS",
AccessTier = "Standard",
});
// How do we want to treat exports?
return new Dictionary<string, Output<string>>
{
{ "accessKey", storageAccount.PrimaryAccessKey }
};
}
}
}

View file

@ -0,0 +1,14 @@
// Copyright 2016-2019, Pulumi Corporation
#nullable enable
using System.Threading.Tasks;
using Pulumi;
class Program
{
static Task<int> Main()
{
return Deployment.RunAsync(Pulumi.CSharpExamples.GlobalApp.Run);
}
}

View file

@ -0,0 +1,2 @@
name: dotnet-azure
runtime: dotnet

View file

@ -0,0 +1,100 @@
// Copyright 2016-2019, Pulumi Corporation
#nullable enable
using System.Collections.Generic;
using Pulumi.Azure.Core;
using Pulumi.Azure.AppService;
using Pulumi.Azure.Storage;
namespace Pulumi.CSharpExamples
{
public class WebApp
{
public static Dictionary<string, object> Run()
{
var resourceGroup = new ResourceGroup("dotnet-rg", new ResourceGroupArgs
{
Location = "West Europe"
});
var storageAccount = new Account("sa", new AccountArgs
{
ResourceGroupName = resourceGroup.Name,
AccountReplicationType = "LRS",
AccountTier = "Standard",
});
var appServicePlan = new Plan("asp", new PlanArgs
{
ResourceGroupName = resourceGroup.Name,
Kind = "App",
Sku = new PlanSkuArgs
{
Tier = "Basic",
Size = "B1",
},
});
var container = new Container("c", new ContainerArgs
{
StorageAccountName = storageAccount.Name,
ContainerAccessType = "private",
});
var blob = new ZipBlob("zip", new ZipBlobArgs
{
StorageAccountName = storageAccount.Name,
StorageContainerName = container.Name,
Type = "block",
Content = new FileArchive("wwwroot"),
});
var codeBlobUrl = SharedAccessSignature.SignedBlobReadUrl(blob, storageAccount);
//var username = "sa"; // TODO: Pulumi.Config
//var password = "pwd";
//var sqlServer = new SqlServer("sql", new SqlServerArgs
//{
// ResourceGroupName = resourceGroup.Name,
// AdministratorLogin = username,
// AdministratorLoginPassword = password,
// Version = "12.0",
//});
//var database = new Database("db", new DatabaseArgs
//{
// ResourceGroupName = resourceGroup.Name,
// ServerName = sqlServer.Name,
// RequestedServiceObjectiveName = "S0",
//});
var app = new AppService("app2", new AppServiceArgs
{
ResourceGroupName = resourceGroup.Name,
AppServicePlanId = appServicePlan.Id,
AppSettings =
{
{ "WEBSITE_RUN_FROM_ZIP", codeBlobUrl },
},
//ConnectionStrings = new[]
//{
// new AppService.ConnectionStringArgs
// {
// Name = "db",
// Type = "SQLAzure",
// Value = Output.All<string>(sqlServer.Name, database.Name).Apply(values =>
// {
// return $"Server= tcp:${values[0]}.database.windows.net;initial catalog=${values[1]};userID=${username};password=${password};Min Pool Size=0;Max Pool Size=30;Persist Security Info=true;";
// }),
// },
//},
});
return new Dictionary<string, object>
{
{ "endpoint", app.DefaultSiteHostname },
};
}
}
}

View file

@ -0,0 +1 @@
<h1>OMG .NET works!!!</h1>

View file

@ -0,0 +1,12 @@
# How To Run a C# script
To run it from a console:
- Install the `dotnet-script` tool: `dotnet tool install -g dotnet-script`
- Build the solution with `PulumiAzure` from the parent folder
- Execute `dotnet script main.csx`
```
└─ core.ResourceGroup rg created
└─ storage.Account sa created
```

View file

@ -0,0 +1,14 @@
#r "../PulumiAzure/bin/Debug/netstandard2.1/Pulumi.dll"
#r "../PulumiAzure/bin/Debug/netstandard2.1/PulumiAzure.dll"
using Pulumi.Azure.Core;
using Pulumi.Azure.Storage;
var resourceGroup = new ResourceGroup("rg");
var storageAccount = new Account("sa", new AccountArgs
{
ResourceGroupName = resourceGroup.Name,
AccountReplicationType = "LRS",
AccountTier = "Standard",
});

View file

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Helpers.fs" />
<Compile Include="Minimal.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Pulumi.FSharp\Pulumi.FSharp.fsproj" />
<ProjectReference Include="..\PulumiAzure\PulumiAzure.csproj" />
<ProjectReference Include="..\..\Pulumi\Pulumi.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,50 @@
namespace Pulumi.FSharp
open Pulumi
open Pulumi.Azure.Core
open Pulumi.Azure.Storage
[<AutoOpen>]
module Builders =
type ResourceGroupBuilder internal (name) =
member __.Yield(_) = ResourceGroupArgs()
member __.Run(state: ResourceGroupArgs) : ResourceGroup = ResourceGroup(name, state)
[<CustomOperation("location")>]
member __.Location(state: ResourceGroupArgs, location: Input<string>) =
state.Location <- location
state
member this.Location(state: ResourceGroupArgs, location: Output<string>) = this.Location(state, io location)
member this.Location(state: ResourceGroupArgs, location: string) = this.Location(state, input location)
let resourceGroup name = ResourceGroupBuilder name
type StorageAccountBuilder internal (name) =
member __.Yield(_) = AccountArgs()
member __.Run(state: AccountArgs) : Account = Account(name, state)
[<CustomOperation("resourceGroupName")>]
member __.ResourceGroupName(state: AccountArgs, value: Input<string>) =
state.ResourceGroupName <- value
state
member this.ResourceGroupName(state: AccountArgs, value: Output<string>) = this.ResourceGroupName(state, io value)
member this.ResourceGroupName(state: AccountArgs, value: string) = this.ResourceGroupName(state, input value)
[<CustomOperation("accountReplicationType")>]
member __.AccountReplicationType(state: AccountArgs, value: Input<string>) =
state.AccountReplicationType <- value
state
member this.AccountReplicationType(state: AccountArgs, value: Output<string>) = this.AccountReplicationType(state, io value)
member this.AccountReplicationType(state: AccountArgs, value: string) = this.AccountReplicationType(state, input value)
[<CustomOperation("accountTier")>]
member __.AccountTier(state: AccountArgs, value: Input<string>) =
state.AccountTier <- value
state
member this.AccountTier(state: AccountArgs, value: Output<string>) = this.AccountTier(state, io value)
member this.AccountTier(state: AccountArgs, value: string) = this.AccountTier(state, input value)
let storageAccount name = StorageAccountBuilder name

View file

@ -0,0 +1,34 @@
module Minimal
open System
open System.Collections.Generic
open Pulumi
open Pulumi.FSharp
open Pulumi.Azure.Core
open Pulumi.Azure.Storage
let plain (): IDictionary<string, Object> =
let resourceGroup = ResourceGroup("rg", ResourceGroupArgs(Location = input "WestEurope"))
let storageAccount =
Account("sa",
AccountArgs(
ResourceGroupName = io resourceGroup.Name, // No implicit operators in F#
AccountReplicationType = input "LRS", // Can't have two functions with same name but different signatures
AccountTier = input "Standard")) // There may be some neat operator trick for that?
dict [ ("accessKey", storageAccount.PrimaryAccessKey :> Object) ]
let ce (): IDictionary<string, Output<string>> =
let resourceGroup = resourceGroup "rg" {
location "WestEurope"
}
let storageAccount = storageAccount "sa" {
resourceGroupName resourceGroup.Name
accountReplicationType "LRS"
accountTier "Standard"
}
dict [ ("accessKey", storageAccount.PrimaryAccessKey) ]

View file

@ -0,0 +1,9 @@
// Copyright 2016-2019, Pulumi Corporation
module Program
open Pulumi.FSharp
[<EntryPoint>]
let main _ =
Deployment.run Minimal.plain

View file

@ -0,0 +1,2 @@
name: dotnet-azure-fsharp
runtime: dotnet

View file

@ -0,0 +1,37 @@
using Pulumi.Serialization;
namespace Pulumi.Azure.Storage
{
public class Account : CustomResource
{
[Output("name")]
public Output<string> Name { get; private set; }
[Output("primaryAccessKey")]
public Output<string> PrimaryAccessKey { get; private set; }
[Output("primaryConnectionString")]
public Output<string> PrimaryConnectionString { get; private set; }
public Account(string name, AccountArgs args = default, ResourceOptions opts = default)
: base("azure:storage/account:Account", name, args, opts)
{
}
}
public class AccountArgs : ResourceArgs
{
[Input("accessTier")]
public Input<string> AccessTier { get; set; }
[Input("accountKind")]
public Input<string> AccountKind { get; set; }
[Input("accountTier")]
public Input<string> AccountTier { get; set; }
[Input("accountReplicationType")]
public Input<string> AccountReplicationType { get; set; }
[Input("location")]
public Input<string> Location { get; set; }
[Input("resourceGroupName")]
public Input<string> ResourceGroupName { get; set; }
}
}

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Pulumi.Serialization;
namespace Pulumi.Azure.AppService
{
public class AppService : CustomResource
{
[Output("defaultSiteHostname")]
public Output<string> DefaultSiteHostname { get; private set; }
public AppService(string name, AppServiceArgs args, ResourceOptions opts = null)
: base("azure:appservice/appService:AppService", name, args, opts)
{
}
}
public class AppServiceArgs : ResourceArgs
{
[Input("appServicePlanId")]
public Input<string> AppServicePlanId { get; set; }
[Input("location")]
public Input<string> Location { get; set; }
[Input("resourceGroupName")]
public Input<string> ResourceGroupName { get; set; }
[Input("appSettings")]
private InputMap<string> _appSettings;
public InputMap<string> AppSettings
{
get => _appSettings ?? (_appSettings = new InputMap<string>());
set => _appSettings = value;
}
// TODO: why is this disabled?
// [Input("connectionStrings")]
private InputList<ConnectionStringArgs> _connectionStrings;
public InputList<ConnectionStringArgs> ConnectionStrings
{
get => _connectionStrings ?? (_connectionStrings = new InputList<ConnectionStringArgs>());
set => _connectionStrings = value;
}
}
public class ConnectionStringArgs : ResourceArgs
{
[Input("name")]
public Input<string> Name { get; set; }
[Input("type")]
public Input<string> Type { get; set; }
[Input("value")]
public Input<string> Value { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace Pulumi.Asset
{
public interface IArchive
{
// TODO
}
//public class FileArchive : IArchive
//{
// public FileArchive(string name)
// {
// // TODO
// }
//}
}

View file

@ -0,0 +1,23 @@
using Pulumi.Serialization;
namespace Pulumi.Azure.Storage
{
public class Container : CustomResource
{
[Output("name")]
public Output<string> Name { get; private set; }
public Container(string name, ContainerArgs args = default, ResourceOptions opts = default)
: base("azure:storage/container:Container", name, args, opts)
{
}
}
public class ContainerArgs : ResourceArgs
{
[Input("containerAccessType")]
public Input<string> ContainerAccessType { get; set; }
[Input("storageAccountName")]
public Input<string> StorageAccountName { get; set; }
}
}

View file

@ -0,0 +1,43 @@
using Pulumi.Serialization;
namespace Pulumi.Azure.CosmosDB
{
public class Account : CustomResource
{
[Output("name")]
public Output<string> Name { get; private set; }
public Account(string name, AccountArgs args = default, ResourceOptions opts = default)
: base("azure:cosmosdb/account:Account", name, args, opts)
{
}
}
public class AccountArgs : ResourceArgs
{
[Input("consistencyPolicy")]
public Input<AccountConsistencyPolicy> ConsistencyPolicy { get; set; }
[Input("geoLocations")]
public InputList<AccountGeoLocation> GeoLocations { get; set; }
[Input("location")]
public Input<string> Location { get; set; }
[Input("offerType")]
public Input<string> OfferType { get; set; }
[Input("resourceGroupName")]
public Input<string> ResourceGroupName { get; set; }
}
public class AccountGeoLocation : ResourceArgs
{
[Input("location")]
public Input<string> Location { get; set; }
[Input("failoverPriority")]
public Input<int> FailoverPriority { get; set; }
}
public class AccountConsistencyPolicy : ResourceArgs
{
[Input("consistencyLevel")]
public Input<string> ConsistencyLevel { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show more