From 963b5ab7101eca9e48c29dfc695ef9ba296a9ada Mon Sep 17 00:00:00 2001 From: Josh Studt <32800478+orionstudt@users.noreply.github.com> Date: Thu, 18 Feb 2021 05:36:21 -0500 Subject: [PATCH] [Automation API] - C# Implementation (#5761) * Init Workspace interface for C# Automation API * fleshing out workspace interface and beginning of local workspace implementation * initial run pulumi cmd implementation * resolve issue with pulumi cmd cleanup wrapper task after testing * flesh out local workspace implementation, flesh out stack implementation, cleanup run pulumi cmd implementation and make it an instance so it is mockable/testable, separate serialization in prep for custom converters * project settings json serialization implemented * Initial commit of language server * Add deployment from language server * Cleanup * finish json serialization * project runtime yaml serialization completed. just need stack config value yaml serialization * Remove typed argument * Limit concurrency * Rename file for consistency * final commit of a semi-working project settings & stack settings serialization so that it is in the commit history * modify workspace API so that settings accessors aren't fully exposed since we are defering a complete serialization implementation until a later date * yaml converters wrap any outgoing exceptions so resolve that * getting the beginning of inline program GRPC communication set up * stack lifecycle operations implemented, and switched to newtonsoft for JSON serialization * change back to system.text.json with a custom object converter * local workspace tests written, working on getting them passing * fix the encoding on the GO files used for testing * all tests passing except inline program, pulumi engine not available with inline * inline program engine is now running as expecting, but inline program is not recognizing local stack config * All tests passing, but no concurrency capability because of the singleton DeploymentInstance. * cleanup unnecessary usings * minor cleanup / changes after a quick review. Make sure ConfigureAwait is used where needed. Remove newtonsoft dependency from testing. Update workspace API to use existing PluginKind enum. Modify LanguageRuntimeService so that its semaphore operates process-wide. * support for parallel execution of inline program, test included * Update LocalWorkspaceTests.cs remove some redundancy from the inline program parallel execution text * flesh out some comments and make asynclocal instance readonly * Strip out instance locking since it is no longer necessary with AsyncLocal wrapping the Deployment.Instance. Modify CreateRunner method such that we are ensuring there isn't a chance of delayed synchronous execution polluting the value of Deployment.Instance across calls to Deployment.RunAsync * resolve conflicts with changes made to Deployment.TestAsync entrypoints * update changelog * write a test that fails if the CreateRunnerAndRunAsync method on Deployment is not marked async and fix test project data file ref * make resource package state share the lifetime of the deployment so that their isn't cross deployment issues with resource packages, add support and tests for external resource packages (resource packages that aren't referenced by the executing assembly) * enable parallel test collection execution in test suite, add some additional tests for deployment instance protection and ensuring that our first class stack exceptions are thrown when expected * minor inline project name arg change, and re-add xunit json to build output (whoops) * strip out concurrency changes since they are now in #6139, split automation into separate assembly, split automation tests into separate assembly * add copyright to the top of each new file * resolve some PR remarks * inline program exception is now properly propagated to the caller on UpAsync and PreviewAsync * modify PulumiFn to allow TStack and other delegate overloads without needing multiple first class delegates. * whoops missing a copyright * resolve getting TStack into IRunner so that outputs are registered correctly and so that there isn't 2 instances of Pulumi.Stack instantiated. * resolve issue with propagation of TStack exceptions and add a test * add support for a TStack PulumiFn resolved via IServiceProvider * update automation API description * fix comment and remove unnecessary TODOs * disable packaging of automation api assembly * re-name automation api documentation file appropriately * add --limit support to dotnet automation api for stack history per #6257 * re-name XStack as WorkspaceStack * replace --limit usage with --page-size and --page in dotnet automation api per #6292 Co-authored-by: evanboyle Co-authored-by: Josh Studt Co-authored-by: Dan Friedman Co-authored-by: David Ferretti Co-authored-by: Mikhail Shilkov --- CHANGELOG.md | 4 +- sdk/dotnet/.gitignore | 3 +- .../AssemblyAttributes.cs | 6 + .../CommandExceptionTests.cs | 124 +++ .../Data/json/Pulumi.dev.json | 9 + .../Data/json/Pulumi.json | 5 + .../Data/testproj/Pulumi.yaml | 3 + .../Data/testproj/go.mod | 5 + .../Data/testproj/go.sum | 261 ++++++ .../Data/testproj/main.go | 16 + .../Data/yaml/Pulumi.dev.yaml | 5 + .../Data/yaml/Pulumi.yaml | 3 + .../Data/yml/Pulumi.dev.yml | 5 + .../Data/yml/Pulumi.yml | 3 + .../LocalWorkspaceTests.cs | 646 +++++++++++++ .../Pulumi.Automation.Tests.csproj | 60 ++ .../Serialization/DynamicObjectTests.cs | 52 ++ .../GeneralJsonConverterTests.cs | 140 +++ .../ProjectRuntimeJsonConverterTests.cs | 101 ++ .../ProjectRuntimeYamlConverterTests.cs | 97 ++ ...ckSettingsConfigValueJsonConverterTests.cs | 104 +++ ...ckSettingsConfigValueYamlConverterTests.cs | 83 ++ .../Pulumi.Automation.Tests/xunit.runner.json | 4 + .../Pulumi.Automation/AssemblyAttribute.cs | 5 + .../Commands/CommandResult.cs | 35 + .../Commands/Exceptions/CommandException.cs | 33 + .../Exceptions/ConcurrentUpdateException.cs | 12 + .../Exceptions/StackAlreadyExistsException.cs | 12 + .../Exceptions/StackNotFoundException.cs | 12 + .../Pulumi.Automation/Commands/IPulumiCmd.cs | 19 + .../Commands/LocalPulumiCmd.cs | 108 +++ sdk/dotnet/Pulumi.Automation/ConfigValue.cs | 19 + .../Pulumi.Automation/DestroyOptions.cs | 16 + .../Pulumi.Automation/HistoryOptions.cs | 14 + .../Pulumi.Automation/InlineProgramArgs.cs | 19 + .../Pulumi.Automation/LocalProgramArgs.cs | 20 + .../Pulumi.Automation/LocalWorkspace.cs | 598 ++++++++++++ .../LocalWorkspaceOptions.cs | 61 ++ sdk/dotnet/Pulumi.Automation/OperationType.cs | 15 + sdk/dotnet/Pulumi.Automation/OutputValue.cs | 19 + sdk/dotnet/Pulumi.Automation/PluginInfo.cs | 45 + sdk/dotnet/Pulumi.Automation/PluginKind.cs | 11 + .../Pulumi.Automation/PreviewOptions.cs | 20 + .../Pulumi.Automation/ProjectBackend.cs | 12 + .../Pulumi.Automation/ProjectRuntime.cs | 19 + .../Pulumi.Automation/ProjectRuntimeName.cs | 15 + .../ProjectRuntimeOptions.cs | 33 + .../Pulumi.Automation/ProjectSettings.cs | 48 + .../Pulumi.Automation/ProjectTemplate.cs | 20 + .../ProjectTemplateConfigValue.cs | 16 + .../Pulumi.Automation/PublicAPI.Shipped.txt | 1 + .../Pulumi.Automation/PublicAPI.Unshipped.txt | 327 +++++++ .../Pulumi.Automation.csproj | 64 ++ .../Pulumi.Automation/Pulumi.Automation.xml | 881 ++++++++++++++++++ .../Pulumi.Automation/PulumiFn.Inline.cs | 41 + .../Pulumi.Automation/PulumiFn.TStack.cs | 52 ++ sdk/dotnet/Pulumi.Automation/PulumiFn.cs | 123 +++ .../Pulumi.Automation/RefreshOptions.cs | 16 + .../Runtime/LanguageRuntimeService.cs | 82 ++ .../Serialization/ConfigValueModel.cs | 17 + .../Serialization/Json/IJsonModel.cs | 9 + .../Json/LowercaseJsonNamingPolicy.cs | 12 + .../Json/MapToModelJsonConverter.cs | 28 + .../Json/ProjectRuntimeJsonConverter.cs | 75 ++ .../Json/ResourceChangesJsonConverter.cs | 71 ++ .../StackSettingsConfigValueJsonConverter.cs | 57 ++ .../Json/SystemObjectJsonConverter.cs | 80 ++ .../Serialization/LocalSerializer.cs | 81 ++ .../Serialization/PluginInfoModel.cs | 44 + .../Serialization/ProjectSettingsModel.cs | 43 + .../Serialization/StackSummaryModel.cs | 32 + .../Serialization/UpdateSummaryModel.cs | 56 ++ .../ProjectRuntimeOptionsYamlConverter.cs | 91 ++ .../Yaml/ProjectRuntimeYamlConverter.cs | 96 ++ .../StackSettingsConfigValueYamlConverter.cs | 72 ++ .../Yaml/YamlScalarExtensions.cs | 19 + sdk/dotnet/Pulumi.Automation/StackSettings.cs | 31 + .../StackSettingsConfigValue.cs | 19 + sdk/dotnet/Pulumi.Automation/StackSummary.cs | 37 + sdk/dotnet/Pulumi.Automation/UpOptions.cs | 23 + sdk/dotnet/Pulumi.Automation/UpResult.cs | 21 + sdk/dotnet/Pulumi.Automation/UpdateKind.cs | 14 + sdk/dotnet/Pulumi.Automation/UpdateOptions.cs | 19 + sdk/dotnet/Pulumi.Automation/UpdateResult.cs | 23 + sdk/dotnet/Pulumi.Automation/UpdateState.cs | 12 + sdk/dotnet/Pulumi.Automation/UpdateSummary.cs | 57 ++ sdk/dotnet/Pulumi.Automation/WhoAmIResult.cs | 14 + sdk/dotnet/Pulumi.Automation/Workspace.cs | 289 ++++++ .../Pulumi.Automation/WorkspaceStack.cs | 681 ++++++++++++++ sdk/dotnet/Pulumi.Tests/AssemblyAttributes.cs | 2 +- sdk/dotnet/Pulumi.Tests/Pulumi.Tests.csproj | 6 +- sdk/dotnet/Pulumi/AssemblyAttributes.cs | 1 + .../Pulumi/Deployment/Deployment.Runner.cs | 2 +- .../Pulumi/Deployment/Deployment_Config.cs | 7 + .../Pulumi/Deployment/Deployment_Inline.cs | 44 + .../Pulumi/Deployment/Deployment_Run.cs | 10 +- sdk/dotnet/Pulumi/Deployment/IRunner.cs | 1 + .../Deployment/InlineDeploymentSettings.cs | 39 + .../Pulumi/Serialization/ResourcePackages.cs | 5 +- sdk/dotnet/dotnet.sln | 28 + 100 files changed, 6838 insertions(+), 12 deletions(-) create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/AssemblyAttributes.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/CommandExceptionTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.dev.json create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.json create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/Pulumi.yaml create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.mod create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.sum create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/main.go create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.dev.yaml create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.yaml create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.dev.yml create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.yml create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/LocalWorkspaceTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Pulumi.Automation.Tests.csproj create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Serialization/DynamicObjectTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Serialization/GeneralJsonConverterTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeJsonConverterTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeYamlConverterTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueJsonConverterTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueYamlConverterTests.cs create mode 100644 sdk/dotnet/Pulumi.Automation.Tests/xunit.runner.json create mode 100644 sdk/dotnet/Pulumi.Automation/AssemblyAttribute.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Commands/CommandResult.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Commands/Exceptions/CommandException.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Commands/Exceptions/ConcurrentUpdateException.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackAlreadyExistsException.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackNotFoundException.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Commands/IPulumiCmd.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Commands/LocalPulumiCmd.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ConfigValue.cs create mode 100644 sdk/dotnet/Pulumi.Automation/DestroyOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/HistoryOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/InlineProgramArgs.cs create mode 100644 sdk/dotnet/Pulumi.Automation/LocalProgramArgs.cs create mode 100644 sdk/dotnet/Pulumi.Automation/LocalWorkspace.cs create mode 100644 sdk/dotnet/Pulumi.Automation/LocalWorkspaceOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/OperationType.cs create mode 100644 sdk/dotnet/Pulumi.Automation/OutputValue.cs create mode 100644 sdk/dotnet/Pulumi.Automation/PluginInfo.cs create mode 100644 sdk/dotnet/Pulumi.Automation/PluginKind.cs create mode 100644 sdk/dotnet/Pulumi.Automation/PreviewOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ProjectBackend.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ProjectRuntime.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ProjectRuntimeName.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ProjectRuntimeOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ProjectSettings.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ProjectTemplate.cs create mode 100644 sdk/dotnet/Pulumi.Automation/ProjectTemplateConfigValue.cs create mode 100644 sdk/dotnet/Pulumi.Automation/PublicAPI.Shipped.txt create mode 100644 sdk/dotnet/Pulumi.Automation/PublicAPI.Unshipped.txt create mode 100644 sdk/dotnet/Pulumi.Automation/Pulumi.Automation.csproj create mode 100644 sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml create mode 100644 sdk/dotnet/Pulumi.Automation/PulumiFn.Inline.cs create mode 100644 sdk/dotnet/Pulumi.Automation/PulumiFn.TStack.cs create mode 100644 sdk/dotnet/Pulumi.Automation/PulumiFn.cs create mode 100644 sdk/dotnet/Pulumi.Automation/RefreshOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Runtime/LanguageRuntimeService.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/ConfigValueModel.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Json/IJsonModel.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Json/LowercaseJsonNamingPolicy.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Json/MapToModelJsonConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Json/ProjectRuntimeJsonConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Json/ResourceChangesJsonConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Json/StackSettingsConfigValueJsonConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Json/SystemObjectJsonConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/LocalSerializer.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/PluginInfoModel.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/ProjectSettingsModel.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/StackSummaryModel.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/UpdateSummaryModel.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeOptionsYamlConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeYamlConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Yaml/StackSettingsConfigValueYamlConverter.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Serialization/Yaml/YamlScalarExtensions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/StackSettings.cs create mode 100644 sdk/dotnet/Pulumi.Automation/StackSettingsConfigValue.cs create mode 100644 sdk/dotnet/Pulumi.Automation/StackSummary.cs create mode 100644 sdk/dotnet/Pulumi.Automation/UpOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/UpResult.cs create mode 100644 sdk/dotnet/Pulumi.Automation/UpdateKind.cs create mode 100644 sdk/dotnet/Pulumi.Automation/UpdateOptions.cs create mode 100644 sdk/dotnet/Pulumi.Automation/UpdateResult.cs create mode 100644 sdk/dotnet/Pulumi.Automation/UpdateState.cs create mode 100644 sdk/dotnet/Pulumi.Automation/UpdateSummary.cs create mode 100644 sdk/dotnet/Pulumi.Automation/WhoAmIResult.cs create mode 100644 sdk/dotnet/Pulumi.Automation/Workspace.cs create mode 100644 sdk/dotnet/Pulumi.Automation/WorkspaceStack.cs create mode 100644 sdk/dotnet/Pulumi/Deployment/Deployment_Inline.cs create mode 100644 sdk/dotnet/Pulumi/Deployment/InlineDeploymentSettings.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 123e7aa99..b5637d9dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ CHANGELOG ========= ## HEAD (Unreleased) -_(none)_ + + [sdk/dotnet] C# Automation API. + [#5761](https://github.com/pulumi/pulumi/pull/5761) ## 2.21.0 (2021-02-17) diff --git a/sdk/dotnet/.gitignore b/sdk/dotnet/.gitignore index b7c7f5d1d..1b4ddf07f 100644 --- a/sdk/dotnet/.gitignore +++ b/sdk/dotnet/.gitignore @@ -1,4 +1,5 @@ [Bb]in/ [Oo]bj/ .leu -Pulumi/Pulumi.xml \ No newline at end of file +Pulumi/Pulumi.xml +*.user \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/AssemblyAttributes.cs b/sdk/dotnet/Pulumi.Automation.Tests/AssemblyAttributes.cs new file mode 100644 index 000000000..8e21ee46e --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/AssemblyAttributes.cs @@ -0,0 +1,6 @@ +// Copyright 2016-2021, Pulumi Corporation + +using Xunit; + +// Unfortunately, we depend on static state. So for now disable parallelization. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/sdk/dotnet/Pulumi.Automation.Tests/CommandExceptionTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/CommandExceptionTests.cs new file mode 100644 index 000000000..ee0a9a7b9 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/CommandExceptionTests.cs @@ -0,0 +1,124 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Pulumi.Automation.Commands.Exceptions; +using Xunit; + +namespace Pulumi.Automation.Tests +{ + public class CommandExceptionTests + { + private static string GetTestSuffix() + { + var random = new Random(); + var result = 100000 + random.Next(0, 900000); + return result.ToString(); + } + + [Fact] + public async Task StackNotFoundExceptionIsThrown() + { + var projectSettings = new ProjectSettings("command_exception_test", ProjectRuntimeName.NodeJS); + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + ProjectSettings = projectSettings, + }); + + var stackName = $"non_existent_stack{GetTestSuffix()}"; + var selectTask = workspace.SelectStackAsync(stackName); + + await Assert.ThrowsAsync( + () => selectTask); + } + + [Fact] + public async Task StackAlreadyExistsExceptionIsThrown() + { + var projectSettings = new ProjectSettings("command_exception_test", ProjectRuntimeName.NodeJS); + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + ProjectSettings = projectSettings, + }); + + var stackName = $"already_existing_stack{GetTestSuffix()}"; + await workspace.CreateStackAsync(stackName); + + try + { + var createTask = workspace.CreateStackAsync(stackName); + await Assert.ThrowsAsync( + () => createTask); + } + finally + { + await workspace.RemoveStackAsync(stackName); + } + } + + [Fact] + public async Task ConcurrentUpdateExceptionIsThrown() + { + + var projectSettings = new ProjectSettings("command_exception_test", ProjectRuntimeName.NodeJS); + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + ProjectSettings = projectSettings, + }); + + var stackName = $"concurrent_update_stack{GetTestSuffix()}"; + await workspace.CreateStackAsync(stackName); + + try + { + var stack = await WorkspaceStack.SelectAsync(stackName, workspace); + + var hitSemaphore = false; + using var semaphore = new SemaphoreSlim(0, 1); + var program = PulumiFn.Create(() => + { + hitSemaphore = true; + semaphore.Wait(); + return new Dictionary() + { + ["test"] = "doesnt matter", + }; + }); + + var upTask = stack.UpAsync(new UpOptions + { + Program = program, + }); + + // wait until we hit semaphore + while (!hitSemaphore) + { + await Task.Delay(TimeSpan.FromSeconds(2)); + if (upTask.IsFaulted) + throw upTask.Exception!; + else if (upTask.IsCompleted) + throw new Exception("never hit semaphore in first UP task"); + } + + // before releasing the semaphore, ensure another up throws + var concurrentTask = stack.UpAsync(new UpOptions + { + Program = program, // should never make it into this + }); + + await Assert.ThrowsAsync( + () => concurrentTask); + + // finish first up call + semaphore.Release(); + await upTask; + } + finally + { + await workspace.RemoveStackAsync(stackName); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.dev.json b/sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.dev.json new file mode 100644 index 000000000..0dffa4c33 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.dev.json @@ -0,0 +1,9 @@ +{ + "secretsProvider": "abc", + "config": { + "plain": "plain", + "secure": { + "secure": "secret" + } + } +} \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.json b/sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.json new file mode 100644 index 000000000..fc6f51048 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/json/Pulumi.json @@ -0,0 +1,5 @@ +{ + "name": "testproj", + "runtime": "go", + "description": "A minimal Go Pulumi program" +} \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/Pulumi.yaml b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/Pulumi.yaml new file mode 100644 index 000000000..009e9c7cf --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/Pulumi.yaml @@ -0,0 +1,3 @@ +name: testproj +runtime: go +description: A minimal Go Pulumi program \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.mod b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.mod new file mode 100644 index 000000000..045a46d4d --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.mod @@ -0,0 +1,5 @@ +module testproj + +go 1.14 + +require github.com/pulumi/pulumi/sdk/v2 v2.0.0 \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.sum b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.sum new file mode 100644 index 000000000..0529c1ad5 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/go.sum @@ -0,0 +1,261 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cheggaaa/pb v1.0.18 h1:G/DgkKaBP0V5lnBg/vx61nVxxAU+VqU5yMzSc0f2PPE= +github.com/cheggaaa/pb v1.0.18/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/djherbis/times v1.2.0 h1:xANXjsC/iBqbO00vkWlYwPWgBgEVU6m6AFYg0Pic+Mc= +github.com/djherbis/times v1.2.0/go.mod h1:CGMZlo255K5r4Yw0b9RRfFQpM2y7uOmxg4jm9HsaVf8= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= +github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pulumi/pulumi/sdk/v2 v2.0.0/go.mod h1:W7k1UDYerc5o97mHnlHHp5iQZKEby+oQrQefWt+2RF4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= +github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/texttheater/golang-levenshtein v0.0.0-20191208221605-eb6844b05fc6 h1:9VTskZOIRf2vKF3UL8TuWElry5pgUpV1tFSe/e/0m/E= +github.com/texttheater/golang-levenshtein v0.0.0-20191208221605-eb6844b05fc6/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/uber/jaeger-client-go v2.22.1+incompatible h1:NHcubEkVbahf9t3p75TOCR83gdUHXjRJvjoBh1yACsM= +github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww= +golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d h1:62ap6LNOjDU6uGmKXHJbSfciMoV+FeI1sRXx/pLDL44= +golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/main.go b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/main.go new file mode 100644 index 000000000..cf22aaa79 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/testproj/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/pulumi/pulumi/sdk/v2/go/pulumi" + "github.com/pulumi/pulumi/sdk/v2/go/pulumi/config" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + c := config.New(ctx, "") + ctx.Export("exp_static", pulumi.String("foo")) + ctx.Export("exp_cfg", pulumi.String(c.Get("bar"))) + ctx.Export("exp_secret", c.GetSecret("buzz")) + return nil + }) +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.dev.yaml b/sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.dev.yaml new file mode 100644 index 000000000..355e2ce47 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.dev.yaml @@ -0,0 +1,5 @@ +secretsProvider: abc +config: + plain: plain + secure: + secure: secret \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.yaml b/sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.yaml new file mode 100644 index 000000000..009e9c7cf --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/yaml/Pulumi.yaml @@ -0,0 +1,3 @@ +name: testproj +runtime: go +description: A minimal Go Pulumi program \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.dev.yml b/sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.dev.yml new file mode 100644 index 000000000..355e2ce47 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.dev.yml @@ -0,0 +1,5 @@ +secretsProvider: abc +config: + plain: plain + secure: + secure: secret \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.yml b/sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.yml new file mode 100644 index 000000000..009e9c7cf --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Data/yml/Pulumi.yml @@ -0,0 +1,3 @@ +name: testproj +runtime: go +description: A minimal Go Pulumi program \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation.Tests/LocalWorkspaceTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/LocalWorkspaceTests.cs new file mode 100644 index 000000000..8cdf7ddad --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/LocalWorkspaceTests.cs @@ -0,0 +1,646 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Pulumi.Automation.Commands.Exceptions; +using Xunit; + +namespace Pulumi.Automation.Tests +{ + public class LocalWorkspaceTests + { + private static readonly string _dataDirectory = + Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName, "Data"); + + private static string GetTestSuffix() + { + var random = new Random(); + var result = 100000 + random.Next(0, 900000); + return result.ToString(); + } + + private static string NormalizeConfigKey(string key, string projectName) + { + var parts = key.Split(":"); + if (parts.Length < 2) + return $"{projectName}:{key}"; + + return string.Empty; + } + + [Theory] + [InlineData("yaml")] + [InlineData("yml")] + [InlineData("json")] + public async Task GetProjectSettings(string extension) + { + var workingDir = Path.Combine(_dataDirectory, extension); + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + WorkDir = workingDir, + }); + + var settings = await workspace.GetProjectSettingsAsync(); + Assert.NotNull(settings); + Assert.Equal("testproj", settings!.Name); + Assert.Equal(ProjectRuntimeName.Go, settings.Runtime.Name); + Assert.Equal("A minimal Go Pulumi program", settings.Description); + } + + [Theory] + [InlineData("yaml")] + [InlineData("yml")] + [InlineData("json")] + public async Task GetStackSettings(string extension) + { + var workingDir = Path.Combine(_dataDirectory, extension); + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + WorkDir = workingDir, + }); + + var settings = await workspace.GetStackSettingsAsync("dev"); + Assert.NotNull(settings); + Assert.Equal("abc", settings!.SecretsProvider); + Assert.NotNull(settings.Config); + + Assert.True(settings.Config!.TryGetValue("plain", out var plainValue)); + Assert.Equal("plain", plainValue!.Value); + Assert.False(plainValue.IsSecure); + + Assert.True(settings.Config.TryGetValue("secure", out var secureValue)); + Assert.Equal("secret", secureValue!.Value); + Assert.True(secureValue.IsSecure); + } + + [Fact] + public async Task AddRemoveListPlugins() + { + using var workspace = await LocalWorkspace.CreateAsync(); + + var plugins = await workspace.ListPluginsAsync(); + if (plugins.Any(p => p.Name == "aws" && p.Version == "3.0.0")) + { + await workspace.RemovePluginAsync("aws", "3.0.0"); + plugins = await workspace.ListPluginsAsync(); + Assert.DoesNotContain(plugins, p => p.Name == "aws" && p.Version == "3.0.0"); + } + + await workspace.InstallPluginAsync("aws", "v3.0.0"); + plugins = await workspace.ListPluginsAsync(); + var aws = plugins.FirstOrDefault(p => p.Name == "aws" && p.Version == "3.0.0"); + Assert.NotNull(aws); + + await workspace.RemovePluginAsync("aws", "3.0.0"); + plugins = await workspace.ListPluginsAsync(); + Assert.DoesNotContain(plugins, p => p.Name == "aws" && p.Version == "3.0.0"); + } + + [Fact] + public async Task CreateSelectRemoveStack() + { + var projectSettings = new ProjectSettings("node_test", ProjectRuntimeName.NodeJS); + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + ProjectSettings = projectSettings, + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var stackName = $"int_test{GetTestSuffix()}"; + + var stacks = await workspace.ListStacksAsync(); + Assert.Empty(stacks); + + await workspace.CreateStackAsync(stackName); + stacks = await workspace.ListStacksAsync(); + var newStack = stacks.FirstOrDefault(s => s.Name == stackName); + Assert.NotNull(newStack); + Assert.True(newStack.IsCurrent); + + await workspace.SelectStackAsync(stackName); + await workspace.RemoveStackAsync(stackName); + stacks = await workspace.ListStacksAsync(); + Assert.Empty(stacks); + } + + [Fact] + public async Task ManipulateConfig() + { + var projectName = "node_test"; + var projectSettings = new ProjectSettings(projectName, ProjectRuntimeName.NodeJS); + + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + ProjectSettings = projectSettings, + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var stackName = $"int_test{GetTestSuffix()}"; + var stack = await WorkspaceStack.CreateAsync(stackName, workspace); + + var config = new Dictionary() + { + ["plain"] = new ConfigValue("abc"), + ["secret"] = new ConfigValue("def", isSecret: true), + }; + + var plainKey = NormalizeConfigKey("plain", projectName); + var secretKey = NormalizeConfigKey("secret", projectName); + + await Assert.ThrowsAsync( + () => stack.GetConfigValueAsync(plainKey)); + + var values = await stack.GetConfigAsync(); + Assert.Empty(values); + + await stack.SetConfigAsync(config); + values = await stack.GetConfigAsync(); + Assert.True(values.TryGetValue(plainKey, out var plainValue)); + Assert.Equal("abc", plainValue!.Value); + Assert.False(plainValue.IsSecret); + Assert.True(values.TryGetValue(secretKey, out var secretValue)); + Assert.Equal("def", secretValue!.Value); + Assert.True(secretValue.IsSecret); + + await stack.RemoveConfigValueAsync("plain"); + values = await stack.GetConfigAsync(); + Assert.Single(values); + + await stack.SetConfigValueAsync("foo", new ConfigValue("bar")); + values = await stack.GetConfigAsync(); + Assert.Equal(2, values.Count); + + await workspace.RemoveStackAsync(stackName); + } + + [Fact] + public async Task ListStackAndCurrentlySelected() + { + var projectSettings = new ProjectSettings( + $"node_list_test{GetTestSuffix()}", + ProjectRuntimeName.NodeJS); + + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + ProjectSettings = projectSettings, + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var stackNames = new List(); + try + { + for (var i = 0; i < 2; i++) + { + var stackName = GetStackName(); + await WorkspaceStack.CreateAsync(stackName, workspace); + stackNames.Add(stackName); + var summary = await workspace.GetStackAsync(); + Assert.NotNull(summary); + Assert.True(summary!.IsCurrent); + var stacks = await workspace.ListStacksAsync(); + Assert.Equal(i + 1, stacks.Count); + } + } + finally + { + foreach (var name in stackNames) + await workspace.RemoveStackAsync(name); + } + + static string GetStackName() + => $"int_test{GetTestSuffix()}"; + } + + [Fact] + public async Task CheckStackStatus() + { + var projectSettings = new ProjectSettings("node_test", ProjectRuntimeName.NodeJS); + using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions + { + ProjectSettings = projectSettings, + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var stackName = $"int_test{GetTestSuffix()}"; + var stack = await WorkspaceStack.CreateAsync(stackName, workspace); + try + { + var history = await stack.GetHistoryAsync(); + Assert.Empty(history); + var info = await stack.GetInfoAsync(); + Assert.Null(info); + } + finally + { + await workspace.RemoveStackAsync(stackName); + } + } + + [Fact] + public async Task StackLifecycleLocalProgram() + { + var stackName = $"int_test{GetTestSuffix()}"; + var workingDir = Path.Combine(_dataDirectory, "testproj"); + using var stack = await LocalWorkspace.CreateStackAsync(new LocalProgramArgs(stackName, workingDir) + { + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var config = new Dictionary() + { + ["bar"] = new ConfigValue("abc"), + ["buzz"] = new ConfigValue("secret", isSecret: true), + }; + await stack.SetConfigAsync(config); + + // pulumi up + var upResult = await stack.UpAsync(); + Assert.Equal(UpdateKind.Update, upResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result); + Assert.Equal(3, upResult.Outputs.Count); + + // exp_static + Assert.True(upResult.Outputs.TryGetValue("exp_static", out var expStaticValue)); + Assert.Equal("foo", expStaticValue!.Value); + Assert.False(expStaticValue.IsSecret); + + // exp_cfg + Assert.True(upResult.Outputs.TryGetValue("exp_cfg", out var expConfigValue)); + Assert.Equal("abc", expConfigValue!.Value); + Assert.False(expConfigValue.IsSecret); + + // exp_secret + Assert.True(upResult.Outputs.TryGetValue("exp_secret", out var expSecretValue)); + Assert.Equal("secret", expSecretValue!.Value); + Assert.True(expSecretValue.IsSecret); + + // pulumi preview + await stack.PreviewAsync(); + // TODO: update assertions when we have structured output + + // pulumi refresh + var refreshResult = await stack.RefreshAsync(); + Assert.Equal(UpdateKind.Refresh, refreshResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, refreshResult.Summary.Result); + + // pulumi destroy + var destroyResult = await stack.DestroyAsync(); + Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result); + + await stack.Workspace.RemoveStackAsync(stackName); + } + + [Fact] + public async Task StackLifecycleInlineProgram() + { + var program = PulumiFn.Create(() => + { + var config = new Pulumi.Config(); + return new Dictionary + { + ["exp_static"] = "foo", + ["exp_cfg"] = config.Get("bar"), + ["exp_secret"] = config.GetSecret("buzz"), + }; + }); + + var stackName = $"int_test{GetTestSuffix()}"; + var projectName = "inline_node"; + using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program) + { + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var config = new Dictionary() + { + ["bar"] = new ConfigValue("abc"), + ["buzz"] = new ConfigValue("secret", isSecret: true), + }; + await stack.SetConfigAsync(config); + + // pulumi up + var upResult = await stack.UpAsync(); + Assert.Equal(UpdateKind.Update, upResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result); + Assert.Equal(3, upResult.Outputs.Count); + + // exp_static + Assert.True(upResult.Outputs.TryGetValue("exp_static", out var expStaticValue)); + Assert.Equal("foo", expStaticValue!.Value); + Assert.False(expStaticValue.IsSecret); + + // exp_cfg + Assert.True(upResult.Outputs.TryGetValue("exp_cfg", out var expConfigValue)); + Assert.Equal("abc", expConfigValue!.Value); + Assert.False(expConfigValue.IsSecret); + + // exp_secret + Assert.True(upResult.Outputs.TryGetValue("exp_secret", out var expSecretValue)); + Assert.Equal("secret", expSecretValue!.Value); + Assert.True(expSecretValue.IsSecret); + + // pulumi preview + await stack.PreviewAsync(); + // TODO: update assertions when we have structured output + + // pulumi refresh + var refreshResult = await stack.RefreshAsync(); + Assert.Equal(UpdateKind.Refresh, refreshResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, refreshResult.Summary.Result); + + // pulumi destroy + var destroyResult = await stack.DestroyAsync(); + Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result); + + await stack.Workspace.RemoveStackAsync(stackName); + } + + private class ValidStack : Stack + { + [Output("exp_static")] + public Output ExpStatic { get; set; } + + [Output("exp_cfg")] + public Output ExpConfig { get; set; } + + [Output("exp_secret")] + public Output ExpSecret { get; set; } + + public ValidStack() + { + var config = new Pulumi.Config(); + this.ExpStatic = Output.Create("foo"); + this.ExpConfig = Output.Create(config.Get("bar")!); + this.ExpSecret = config.GetSecret("buzz")!; + } + } + + [Fact] + public async Task StackLifecycleInlineProgramWithTStack() + { + var program = PulumiFn.Create(); + + var stackName = $"int_test{GetTestSuffix()}"; + var projectName = "inline_tstack_node"; + using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program) + { + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var config = new Dictionary() + { + ["bar"] = new ConfigValue("abc"), + ["buzz"] = new ConfigValue("secret", isSecret: true), + }; + await stack.SetConfigAsync(config); + + // pulumi up + var upResult = await stack.UpAsync(); + Assert.Equal(UpdateKind.Update, upResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result); + Assert.Equal(3, upResult.Outputs.Count); + + // exp_static + Assert.True(upResult.Outputs.TryGetValue("exp_static", out var expStaticValue)); + Assert.Equal("foo", expStaticValue!.Value); + Assert.False(expStaticValue.IsSecret); + + // exp_cfg + Assert.True(upResult.Outputs.TryGetValue("exp_cfg", out var expConfigValue)); + Assert.Equal("abc", expConfigValue!.Value); + Assert.False(expConfigValue.IsSecret); + + // exp_secret + Assert.True(upResult.Outputs.TryGetValue("exp_secret", out var expSecretValue)); + Assert.Equal("secret", expSecretValue!.Value); + Assert.True(expSecretValue.IsSecret); + + // pulumi preview + await stack.PreviewAsync(); + // TODO: update assertions when we have structured output + + // pulumi refresh + var refreshResult = await stack.RefreshAsync(); + Assert.Equal(UpdateKind.Refresh, refreshResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, refreshResult.Summary.Result); + + // pulumi destroy + var destroyResult = await stack.DestroyAsync(); + Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result); + + await stack.Workspace.RemoveStackAsync(stackName); + } + + [Fact] + public async Task InlineProgramExceptionPropagatesToCaller() + { + const string projectName = "exception_inline_node"; + var stackName = $"int_test_{GetTestSuffix()}"; + var program = PulumiFn.Create((Action)(() => throw new FileNotFoundException())); + + using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program) + { + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var upTask = stack.UpAsync(); + await Assert.ThrowsAsync( + () => upTask); + } + + private class FileNotFoundStack : Pulumi.Stack + { + public FileNotFoundStack() + { + throw new FileNotFoundException(); + } + } + + [Fact] + public async Task InlineProgramExceptionPropagatesToCallerWithTStack() + { + const string projectName = "exception_inline_tstack_node"; + var stackName = $"int_test_{GetTestSuffix()}"; + var program = PulumiFn.Create(); + + using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program) + { + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + var upTask = stack.UpAsync(); + await Assert.ThrowsAsync( + () => upTask); + } + + [Fact(Skip = "Parallel execution is not supported in this first version.")] + public async Task InlineProgramAllowsParallelExecution() + { + const string projectNameOne = "parallel_inline_node1"; + const string projectNameTwo = "parallel_inline_node2"; + var stackNameOne = $"int_test1_{GetTestSuffix()}"; + var stackNameTwo = $"int_test2_{GetTestSuffix()}"; + + var hasReachedSemaphoreOne = false; + using var semaphoreOne = new SemaphoreSlim(0, 1); + + var programOne = PulumiFn.Create(() => + { + // we want to assert before and after each interaction with + // the semaphore because we want to alternately stutter + // programOne and programTwo so we can assert they aren't + // touching eachothers instances + var config = new Pulumi.Config(); + Assert.Equal(projectNameOne, Deployment.Instance.ProjectName); + Assert.Equal(stackNameOne, Deployment.Instance.StackName); + hasReachedSemaphoreOne = true; + semaphoreOne.Wait(); + Assert.Equal(projectNameOne, Deployment.Instance.ProjectName); + Assert.Equal(stackNameOne, Deployment.Instance.StackName); + return new Dictionary + { + ["exp_static"] = "1", + ["exp_cfg"] = config.Get("bar"), + ["exp_secret"] = config.GetSecret("buzz"), + }; + }); + + var hasReachedSemaphoreTwo = false; + using var semaphoreTwo = new SemaphoreSlim(0, 1); + + var programTwo = PulumiFn.Create(() => + { + var config = new Pulumi.Config(); + Assert.Equal(projectNameTwo, Deployment.Instance.ProjectName); + Assert.Equal(stackNameTwo, Deployment.Instance.StackName); + hasReachedSemaphoreTwo = true; + semaphoreTwo.Wait(); + Assert.Equal(projectNameTwo, Deployment.Instance.ProjectName); + Assert.Equal(stackNameTwo, Deployment.Instance.StackName); + return new Dictionary + { + ["exp_static"] = "2", + ["exp_cfg"] = config.Get("bar"), + ["exp_secret"] = config.GetSecret("buzz"), + }; + }); + + using var stackOne = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectNameOne, stackNameOne, programOne) + { + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + using var stackTwo = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectNameTwo, stackNameTwo, programTwo) + { + EnvironmentVariables = new Dictionary() + { + ["PULUMI_CONFIG_PASSPHRASE"] = "test", + } + }); + + await stackOne.SetConfigAsync(new Dictionary() + { + ["bar"] = new ConfigValue("1"), + ["buzz"] = new ConfigValue("1", isSecret: true), + }); + + await stackTwo.SetConfigAsync(new Dictionary() + { + ["bar"] = new ConfigValue("2"), + ["buzz"] = new ConfigValue("2", isSecret: true), + }); + + var upTaskOne = stackOne.UpAsync(); + // wait until we hit semaphore one + while (!hasReachedSemaphoreOne) + { + await Task.Delay(TimeSpan.FromSeconds(2)); + if (upTaskOne.IsFaulted) + throw upTaskOne.Exception!; + else if (upTaskOne.IsCompleted) + throw new Exception("Never hit semaphore in first UP task."); + } + + var upTaskTwo = stackTwo.UpAsync(); + // wait until we hit semaphore two + while (!hasReachedSemaphoreTwo) + { + await Task.Delay(TimeSpan.FromSeconds(2)); + if (upTaskTwo.IsFaulted) + throw upTaskTwo.Exception!; + else if (upTaskTwo.IsCompleted) + throw new Exception("Never hit semaphore in second UP task."); + } + + // alternately allow them to progress + semaphoreOne.Release(); + var upResultOne = await upTaskOne; + + semaphoreTwo.Release(); + var upResultTwo = await upTaskTwo; + + AssertUpResult(upResultOne, "1"); + AssertUpResult(upResultTwo, "2"); + + static void AssertUpResult(UpResult upResult, string value) + { + Assert.Equal(UpdateKind.Update, upResult.Summary.Kind); + Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result); + Assert.Equal(3, upResult.Outputs.Count); + + // exp_static + Assert.True(upResult.Outputs.TryGetValue("exp_static", out var expStaticValue)); + Assert.Equal(value, expStaticValue!.Value); + Assert.False(expStaticValue.IsSecret); + + // exp_cfg + Assert.True(upResult.Outputs.TryGetValue("exp_cfg", out var expConfigValue)); + Assert.Equal(value, expConfigValue!.Value); + Assert.False(expConfigValue.IsSecret); + + // exp_secret + Assert.True(upResult.Outputs.TryGetValue("exp_secret", out var expSecretValue)); + Assert.Equal(value, expSecretValue!.Value); + Assert.True(expSecretValue.IsSecret); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Pulumi.Automation.Tests.csproj b/sdk/dotnet/Pulumi.Automation.Tests/Pulumi.Automation.Tests.csproj new file mode 100644 index 000000000..c9cc28a67 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Pulumi.Automation.Tests.csproj @@ -0,0 +1,60 @@ + + + + netcoreapp3.1 + enable + true + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Serialization/DynamicObjectTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/DynamicObjectTests.cs new file mode 100644 index 000000000..9a64dfc6f --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/DynamicObjectTests.cs @@ -0,0 +1,52 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Collections.Generic; +using Pulumi.Automation.Serialization; +using Xunit; + +namespace Pulumi.Automation.Tests.Serialization +{ + public class DynamicObjectTests + { + private static LocalSerializer _serializer = new LocalSerializer(); + + [Fact] + public void Dynamic_With_YamlDotNet() + { + const string yaml = @" +one: 123 +two: two +three: true +nested: + test: test + testtwo: 123 +"; + + var dict = _serializer.DeserializeYaml>(yaml); + Assert.NotNull(dict); + Assert.NotEmpty(dict); + Assert.Equal(4, dict.Count); + } + + [Fact] + public void Dynamic_With_SystemTextJson() + { + const string json = @" +{ + ""one"": 123, + ""two"": ""two"", + ""three"": true, + ""nested"": { + ""test"": ""test"", + ""testtwo"": 123, + } +} +"; + + var dict = _serializer.DeserializeJson>(json); + Assert.NotNull(dict); + Assert.NotEmpty(dict); + Assert.Equal(4, dict.Count); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Serialization/GeneralJsonConverterTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/GeneralJsonConverterTests.cs new file mode 100644 index 000000000..136c7f147 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/GeneralJsonConverterTests.cs @@ -0,0 +1,140 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using Pulumi.Automation.Serialization; +using Xunit; + +namespace Pulumi.Automation.Tests.Serialization +{ + public class GeneralJsonConverterTests + { + private static LocalSerializer _serializer = new LocalSerializer(); + + [Fact] + public void CanDeserializeConfigValue() + { + var json = @" +{ + ""aws:region"": { + ""value"": ""us-east-1"", + ""secret"": false, + }, + ""project:name"": { + ""value"": ""test"", + ""secret"": true, + } +} +"; + + var config = _serializer.DeserializeJson>(json); + Assert.NotNull(config); + Assert.True(config.TryGetValue("aws:region", out var regionValue)); + Assert.Equal("us-east-1", regionValue!.Value); + Assert.False(regionValue.IsSecret); + Assert.True(config.TryGetValue("project:name", out var secretValue)); + Assert.Equal("test", secretValue!.Value); + Assert.True(secretValue.IsSecret); + } + + [Fact] + public void CanDeserializePluginInfo() + { + var json = @" +{ + ""name"": ""aws"", + ""kind"": ""resource"", + ""version"": ""3.19.2"", + ""size"": 258460028, + ""installTime"": ""2020-12-09T19:24:23.214Z"", + ""lastUsedTime"": ""2020-12-09T19:24:26.059Z"" +} +"; + var installTime = new DateTime(2020, 12, 9, 19, 24, 23, 214); + var lastUsedTime = new DateTime(2020, 12, 9, 19, 24, 26, 059); + + var info = _serializer.DeserializeJson(json); + Assert.NotNull(info); + Assert.Equal("aws", info.Name); + Assert.Equal(PluginKind.Resource, info.Kind); + Assert.Equal(258460028, info.Size); + Assert.Equal(new DateTimeOffset(installTime, TimeSpan.Zero), info.InstallTime); + Assert.Equal(new DateTimeOffset(lastUsedTime, TimeSpan.Zero), info.LastUsedTime); + } + + [Fact] + public void CanDeserializeUpdateSummary() + { + var json = @" +[ + { + ""kind"": ""destroy"", + ""startTime"": ""2021-01-07T17:08:49.000Z"", + ""message"": """", + ""environment"": { + ""exec.kind"": ""cli"" + }, + ""config"": { + ""aws:region"": { + ""value"": ""us-east-1"", + ""secret"": false + }, + ""quickstart:test"": { + ""value"": ""okok"", + ""secret"": true + } + }, + ""result"": ""in-progress"", + ""endTime"": ""2021-01-07T17:09:14.000Z"", + ""resourceChanges"": { + ""delete"": 3 + } + }, + { + ""kind"": ""update"", + ""startTime"": ""2021-01-07T17:02:10.000Z"", + ""message"": """", + ""environment"": { + ""exec.kind"": ""cli"" + }, + ""config"": { + ""aws:region"": { + ""value"": ""us-east-1"", + ""secret"": false + }, + ""quickstart:test"": { + ""value"": ""okok"", + ""secret"": true + } + }, + ""result"": ""succeeded"", + ""endTime"": ""2021-01-07T17:02:24.000Z"", + ""resourceChanges"": { + ""create"": 3 + } + } +] +"; + + var history = _serializer.DeserializeJson>(json); + Assert.NotNull(history); + Assert.Equal(2, history.Count); + + var destroy = history[0]; + Assert.Equal(UpdateKind.Destroy, destroy.Kind); + Assert.Equal(UpdateState.InProgress, destroy.Result); + Assert.NotNull(destroy.ResourceChanges); + Assert.Equal(1, destroy.ResourceChanges!.Count); + Assert.True(destroy.ResourceChanges.TryGetValue(OperationType.Delete, out var deletedCount)); + Assert.Equal(3, deletedCount); + + var update = history[1]; + Assert.Equal(UpdateKind.Update, update.Kind); + Assert.Equal(UpdateState.Succeeded, update.Result); + Assert.NotNull(update.ResourceChanges); + Assert.Equal(1, update.ResourceChanges!.Count); + Assert.True(update.ResourceChanges.TryGetValue(OperationType.Create, out var createdCount)); + Assert.Equal(3, createdCount); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeJsonConverterTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeJsonConverterTests.cs new file mode 100644 index 000000000..d6dd17d7f --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeJsonConverterTests.cs @@ -0,0 +1,101 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Text.Json; +using Pulumi.Automation.Serialization; +using Xunit; + +namespace Pulumi.Automation.Tests.Serialization +{ + public class ProjectRuntimeJsonConverterTests + { + private static LocalSerializer _serializer = new LocalSerializer(); + + [Theory] + [InlineData(ProjectRuntimeName.NodeJS)] + [InlineData(ProjectRuntimeName.Go)] + [InlineData(ProjectRuntimeName.Python)] + [InlineData(ProjectRuntimeName.Dotnet)] + public void CanDeserializeWithStringRuntime(ProjectRuntimeName runtimeName) + { + var json = $@" +{{ + ""name"": ""test-project"", + ""runtime"": ""{runtimeName.ToString().ToLower()}"" +}} +"; + + var settings = _serializer.DeserializeJson(json); + Assert.NotNull(settings); + Assert.IsType(settings); + Assert.Equal("test-project", settings.Name); + Assert.Equal(runtimeName, settings.Runtime.Name); + Assert.Null(settings.Runtime.Options); + } + + [Theory] + [InlineData(ProjectRuntimeName.NodeJS)] + [InlineData(ProjectRuntimeName.Go)] + [InlineData(ProjectRuntimeName.Python)] + [InlineData(ProjectRuntimeName.Dotnet)] + public void CanDeserializeWithObjectRuntime(ProjectRuntimeName runtimeName) + { + var json = $@" +{{ + ""name"": ""test-project"", + ""runtime"": {{ + ""name"": ""{runtimeName.ToString().ToLower()}"", + ""options"": {{ + ""typeScript"": true, + ""binary"": ""test-binary"", + ""virtualEnv"": ""test-env"" + }} + }} +}} +"; + + var settings = _serializer.DeserializeJson(json); + Assert.NotNull(settings); + Assert.IsType(settings); + Assert.Equal("test-project", settings.Name); + Assert.Equal(runtimeName, settings.Runtime.Name); + Assert.NotNull(settings.Runtime.Options); + Assert.Equal(true, settings.Runtime.Options!.TypeScript); + Assert.Equal("test-binary", settings.Runtime.Options.Binary); + Assert.Equal("test-env", settings.Runtime.Options.VirtualEnv); + } + + [Fact] + public void SerializesAsStringIfOptionsNull() + { + var runtime = new ProjectRuntime(ProjectRuntimeName.Dotnet); + + var json = _serializer.SerializeJson(runtime); + Console.WriteLine(json); + + using var document = JsonDocument.Parse(json); + Assert.NotNull(document); + Assert.Equal(JsonValueKind.String, document.RootElement.ValueKind); + Assert.Equal("dotnet", document.RootElement.GetString()); + } + + [Fact] + public void SerializesAsObjectIfOptionsNotNull() + { + var runtime = new ProjectRuntime(ProjectRuntimeName.Dotnet) + { + Options = new ProjectRuntimeOptions + { + TypeScript = true, + }, + }; + + var json = _serializer.SerializeJson(runtime); + Console.WriteLine(json); + + using var document = JsonDocument.Parse(json); + Assert.NotNull(document); + Assert.Equal(JsonValueKind.Object, document.RootElement.ValueKind); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeYamlConverterTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeYamlConverterTests.cs new file mode 100644 index 000000000..667e3eb60 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/ProjectRuntimeYamlConverterTests.cs @@ -0,0 +1,97 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Text; +using Pulumi.Automation.Serialization; +using Xunit; + +namespace Pulumi.Automation.Tests.Serialization +{ + public class ProjectRuntimeYamlConverterTests + { + private static LocalSerializer _serializer = new LocalSerializer(); + + [Theory] + [InlineData(ProjectRuntimeName.NodeJS)] + [InlineData(ProjectRuntimeName.Go)] + [InlineData(ProjectRuntimeName.Python)] + [InlineData(ProjectRuntimeName.Dotnet)] + public void CanDeserializeWithStringRuntime(ProjectRuntimeName runtimeName) + { + var yaml = $@" +name: test-project +runtime: {runtimeName.ToString().ToLower()} +"; + + var model = _serializer.DeserializeYaml(yaml); + var settings = model.Convert(); + Assert.NotNull(settings); + Assert.IsType(settings); + Assert.Equal("test-project", settings.Name); + Assert.Equal(runtimeName, settings.Runtime.Name); + Assert.Null(settings.Runtime.Options); + } + + [Theory] + [InlineData(ProjectRuntimeName.NodeJS)] + [InlineData(ProjectRuntimeName.Go)] + [InlineData(ProjectRuntimeName.Python)] + [InlineData(ProjectRuntimeName.Dotnet)] + public void CanDeserializeWithObjectRuntime(ProjectRuntimeName runtimeName) + { + var yaml = $@" +name: test-project +runtime: + name: {runtimeName.ToString().ToLower()} + options: + typescript: true + binary: test-binary + virtualenv: test-env +"; + + var model = _serializer.DeserializeYaml(yaml); + var settings = model.Convert(); + Assert.NotNull(settings); + Assert.IsType(settings); + Assert.Equal("test-project", settings.Name); + Assert.Equal(runtimeName, settings.Runtime.Name); + Assert.NotNull(settings.Runtime.Options); + Assert.Equal(true, settings.Runtime.Options!.TypeScript); + Assert.Equal("test-binary", settings.Runtime.Options.Binary); + Assert.Equal("test-env", settings.Runtime.Options.VirtualEnv); + } + + [Fact] + public void SerializesAsStringIfOptionsNull() + { + var runtime = new ProjectRuntime(ProjectRuntimeName.Dotnet); + + var yaml = _serializer.SerializeYaml(runtime); + Console.WriteLine(yaml); + + Assert.Equal("dotnet\r\n", yaml); + } + + [Fact] + public void SerializesAsObjectIfOptionsNotNull() + { + var runtime = new ProjectRuntime(ProjectRuntimeName.Dotnet) + { + Options = new ProjectRuntimeOptions + { + TypeScript = true, + }, + }; + + var yaml = _serializer.SerializeYaml(runtime); + Console.WriteLine(yaml); + + var expected = new StringBuilder(); + expected.Append("name: dotnet\r\n"); + expected.Append("options:\r\n"); + expected.Append(" typescript: true\r\n"); + + Assert.Equal(expected.ToString(), yaml); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueJsonConverterTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueJsonConverterTests.cs new file mode 100644 index 000000000..30367ef0e --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueJsonConverterTests.cs @@ -0,0 +1,104 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Text.Json; +using Pulumi.Automation.Serialization; +using Xunit; + +namespace Pulumi.Automation.Tests.Serialization +{ + public class StackSettingsConfigValueJsonConverterTests + { + private static LocalSerializer _serializer = new LocalSerializer(); + + [Fact] + public void CanDeserializePlainString() + { + const string json = @" +{ + ""config"": { + ""test"": ""plain"" + } +} +"; + + var settings = _serializer.DeserializeJson(json); + Assert.NotNull(settings?.Config); + Assert.True(settings!.Config!.ContainsKey("test")); + + var value = settings.Config["test"]; + Assert.NotNull(value); + Assert.Equal("plain", value.Value); + Assert.False(value.IsSecure); + } + + [Fact] + public void CanDeserializeSecureString() + { + const string json = @" +{ + ""config"": { + ""test"": { + ""secure"": ""secret"" + } + } +} +"; + + var settings = _serializer.DeserializeJson(json); + Assert.NotNull(settings?.Config); + Assert.True(settings!.Config!.ContainsKey("test")); + + var value = settings.Config["test"]; + Assert.NotNull(value); + Assert.Equal("secret", value.Value); + Assert.True(value.IsSecure); + } + + [Fact] + public void CannotDeserializeObject() + { + const string json = @" +{ + ""config"": { + ""value"": { + ""test"": ""test"", + ""nested"": { + ""one"": 1, + ""two"": true, + ""three"": ""three"" + } + } + } +} +"; + + Assert.Throws( + () => _serializer.DeserializeJson(json)); + } + + [Fact] + public void SerializesPlainStringAsString() + { + var value = new StackSettingsConfigValue("test", false); + var json = _serializer.SerializeJson(value); + + var element = JsonSerializer.Deserialize(json); + Assert.Equal(JsonValueKind.String, element.ValueKind); + Assert.Equal("test", element.GetString()); + } + + [Fact] + public void SerializesSecureStringAsObject() + { + var value = new StackSettingsConfigValue("secret", true); + var json = _serializer.SerializeJson(value); + + var element = JsonSerializer.Deserialize(json); + Assert.Equal(JsonValueKind.Object, element.ValueKind); + Assert.True(element.TryGetProperty("secure", out var secureProperty)); + Assert.Equal(JsonValueKind.String, secureProperty.ValueKind); + Assert.Equal("secret", secureProperty.GetString()); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueYamlConverterTests.cs b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueYamlConverterTests.cs new file mode 100644 index 000000000..61e414d81 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/Serialization/StackSettingsConfigValueYamlConverterTests.cs @@ -0,0 +1,83 @@ +// Copyright 2016-2021, Pulumi Corporation + +using Pulumi.Automation.Serialization; +using Xunit; +using YamlDotNet.Core; + +namespace Pulumi.Automation.Tests.Serialization +{ + public class StackSettingsConfigValueYamlConverterTests + { + private static LocalSerializer _serializer = new LocalSerializer(); + + [Fact] + public void CanDeserializePlainString() + { + const string yaml = @" +config: + test: plain +"; + + var settings = _serializer.DeserializeYaml(yaml); + Assert.NotNull(settings?.Config); + Assert.True(settings!.Config!.ContainsKey("test")); + + var value = settings.Config["test"]; + Assert.NotNull(value); + Assert.Equal("plain", value.Value); + Assert.False(value.IsSecure); + } + + [Fact] + public void CanDeserializeSecureString() + { + const string yaml = @" +config: + test: + secure: secret +"; + + var settings = _serializer.DeserializeYaml(yaml); + Assert.NotNull(settings?.Config); + Assert.True(settings!.Config!.ContainsKey("test")); + + var value = settings.Config["test"]; + Assert.NotNull(value); + Assert.Equal("secret", value.Value); + Assert.True(value.IsSecure); + } + + [Fact] + public void CannotDeserializeObject() + { + const string yaml = @" +config: + value: + test: test + nested: + one: 1 + two: true + three: three +"; + + Assert.Throws( + () => _serializer.DeserializeYaml(yaml)); + } + + [Fact] + public void SerializesPlainStringAsString() + { + var value = new StackSettingsConfigValue("test", false); + var yaml = _serializer.SerializeYaml(value); + Assert.Equal("test\r\n", yaml); + } + + [Fact] + public void SerializesSecureStringAsObject() + { + var value = new StackSettingsConfigValue("secret", true); + var yaml = _serializer.SerializeYaml(value); + Assert.Equal("secure: secret\r\n", yaml); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation.Tests/xunit.runner.json b/sdk/dotnet/Pulumi.Automation.Tests/xunit.runner.json new file mode 100644 index 000000000..517db67fc --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false +} diff --git a/sdk/dotnet/Pulumi.Automation/AssemblyAttribute.cs b/sdk/dotnet/Pulumi.Automation/AssemblyAttribute.cs new file mode 100644 index 000000000..78d8e7d75 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/AssemblyAttribute.cs @@ -0,0 +1,5 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Pulumi.Automation.Tests")] diff --git a/sdk/dotnet/Pulumi.Automation/Commands/CommandResult.cs b/sdk/dotnet/Pulumi.Automation/Commands/CommandResult.cs new file mode 100644 index 000000000..eeeb1741c --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Commands/CommandResult.cs @@ -0,0 +1,35 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Text; + +namespace Pulumi.Automation.Commands +{ + internal class CommandResult + { + public int Code { get; } + + public string StandardOutput { get; } + + public string StandardError { get; } + + public CommandResult( + int code, + string standardOutput, + string standardError) + { + this.Code = code; + this.StandardOutput = standardOutput; + this.StandardError = standardError; + } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine($"code: {this.Code}"); + sb.AppendLine($"stdout: {this.StandardOutput}"); + sb.AppendLine($"stderr: {this.StandardError}"); + + return sb.ToString(); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/CommandException.cs b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/CommandException.cs new file mode 100644 index 000000000..3c00ecc90 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/CommandException.cs @@ -0,0 +1,33 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Text.RegularExpressions; + +namespace Pulumi.Automation.Commands.Exceptions +{ + public class CommandException : Exception + { + public string Name { get; } + + internal CommandException(CommandResult result) + : this(nameof(CommandException), result) + { + } + + internal CommandException(string name, CommandResult result) + : base(result.ToString()) + { + this.Name = name; + } + + private static readonly Regex NotFoundRegexPattern = new Regex("no stack named.*found"); + private static readonly Regex AlreadyExistsRegexPattern = new Regex("stack.*already exists"); + private static readonly string ConflictText = "[409] Conflict: Another update is currently in progress."; + + internal static CommandException CreateFromResult(CommandResult result) + => NotFoundRegexPattern.IsMatch(result.StandardError) ? new StackNotFoundException(result) + : AlreadyExistsRegexPattern.IsMatch(result.StandardError) ? new StackAlreadyExistsException(result) + : result.StandardError?.IndexOf(ConflictText) >= 0 ? new ConcurrentUpdateException(result) + : new CommandException(result); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/ConcurrentUpdateException.cs b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/ConcurrentUpdateException.cs new file mode 100644 index 000000000..553ed4fac --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/ConcurrentUpdateException.cs @@ -0,0 +1,12 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation.Commands.Exceptions +{ + public sealed class ConcurrentUpdateException : CommandException + { + internal ConcurrentUpdateException(CommandResult result) + : base(nameof(ConcurrentUpdateException), result) + { + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackAlreadyExistsException.cs b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackAlreadyExistsException.cs new file mode 100644 index 000000000..b2e3c7c80 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackAlreadyExistsException.cs @@ -0,0 +1,12 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation.Commands.Exceptions +{ + public class StackAlreadyExistsException : CommandException + { + internal StackAlreadyExistsException(CommandResult result) + : base(nameof(StackAlreadyExistsException), result) + { + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackNotFoundException.cs b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackNotFoundException.cs new file mode 100644 index 000000000..8c1b4273a --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Commands/Exceptions/StackNotFoundException.cs @@ -0,0 +1,12 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation.Commands.Exceptions +{ + public class StackNotFoundException : CommandException + { + internal StackNotFoundException(CommandResult result) + : base(nameof(StackNotFoundException), result) + { + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Commands/IPulumiCmd.cs b/sdk/dotnet/Pulumi.Automation/Commands/IPulumiCmd.cs new file mode 100644 index 000000000..546c8b3eb --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Commands/IPulumiCmd.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Pulumi.Automation.Commands +{ + internal interface IPulumiCmd + { + Task RunAsync( + IEnumerable args, + string workingDir, + IDictionary additionalEnv, + Action? onOutput = null, + CancellationToken cancellationToken = default); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Commands/LocalPulumiCmd.cs b/sdk/dotnet/Pulumi.Automation/Commands/LocalPulumiCmd.cs new file mode 100644 index 000000000..f896b9055 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Commands/LocalPulumiCmd.cs @@ -0,0 +1,108 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pulumi.Automation.Commands.Exceptions; + +namespace Pulumi.Automation.Commands +{ + internal class LocalPulumiCmd : IPulumiCmd + { + public async Task RunAsync( + IEnumerable args, + string workingDir, + IDictionary additionalEnv, + Action? onOutput = null, + CancellationToken cancellationToken = default) + { + // all commands should be run in non-interactive mode. + // this causes commands to fail rather than prompting for input (and thus hanging indefinitely) + var completeArgs = args.Concat(new[] { "--non-interactive" }); + + var env = new Dictionary(); + foreach (var element in Environment.GetEnvironmentVariables()) + { + if (element is KeyValuePair pair + && pair.Value is string valueStr) + env[pair.Key] = valueStr; + } + + foreach (var pair in additionalEnv) + env[pair.Key] = pair.Value; + + using var proc = new Process + { + EnableRaisingEvents = true, + StartInfo = new ProcessStartInfo + { + FileName = "pulumi", + WorkingDirectory = workingDir, + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + }, + }; + + foreach (var arg in completeArgs) + proc.StartInfo.ArgumentList.Add(arg); + + foreach (var pair in env) + proc.StartInfo.Environment[pair.Key] = pair.Value; + + proc.OutputDataReceived += (_, @event) => + { + if (@event.Data != null) + onOutput?.Invoke(@event.Data); + }; + + var tcs = new TaskCompletionSource(); + using var cancelRegistration = cancellationToken.Register(() => + { + // if the process has already exited than let's + // just let it set the result on the task + if (proc.HasExited || tcs.Task.IsCompleted) + return; + + // setting it cancelled before killing so there + // isn't a race condition to the proc.Exited event + tcs.TrySetCanceled(cancellationToken); + + try + { + proc.Kill(); + } + catch + { + // in case the process hasn't started yet + // or has already terminated + } + }); + + proc.Exited += async (_, @event) => + { + var code = proc.ExitCode; + var stdOut = await proc.StandardOutput.ReadToEndAsync().ConfigureAwait(false); + var stdErr = await proc.StandardError.ReadToEndAsync().ConfigureAwait(false); + + var result = new CommandResult(code, stdOut, stdErr); + if (code != 0) + { + var ex = CommandException.CreateFromResult(result); + tcs.TrySetException(ex); + } + else + { + tcs.TrySetResult(result); + } + }; + + proc.Start(); + return await tcs.Task.ConfigureAwait(false); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ConfigValue.cs b/sdk/dotnet/Pulumi.Automation/ConfigValue.cs new file mode 100644 index 000000000..9443b29a2 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ConfigValue.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public class ConfigValue + { + public string Value { get; set; } + + public bool IsSecret { get; set; } + + public ConfigValue( + string value, + bool isSecret = false) + { + this.Value = value; + this.IsSecret = isSecret; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/DestroyOptions.cs b/sdk/dotnet/Pulumi.Automation/DestroyOptions.cs new file mode 100644 index 000000000..fcf818bf3 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/DestroyOptions.cs @@ -0,0 +1,16 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; + +namespace Pulumi.Automation +{ + /// + /// Options controlling the behavior of an operation. + /// + public sealed class DestroyOptions : UpdateOptions + { + public bool? TargetDependents { get; set; } + + public Action? OnOutput { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/HistoryOptions.cs b/sdk/dotnet/Pulumi.Automation/HistoryOptions.cs new file mode 100644 index 000000000..d1d1fcdf0 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/HistoryOptions.cs @@ -0,0 +1,14 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// Options controlling the behavior of a operation. + /// + public sealed class HistoryOptions + { + public int? Page { get; set; } + + public int? PageSize { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/InlineProgramArgs.cs b/sdk/dotnet/Pulumi.Automation/InlineProgramArgs.cs new file mode 100644 index 000000000..782ba3560 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/InlineProgramArgs.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public class InlineProgramArgs : LocalWorkspaceOptions + { + public string StackName { get; } + + public InlineProgramArgs( + string projectName, + string stackName, + PulumiFn program) + { + this.ProjectSettings = ProjectSettings.Default(projectName); + this.StackName = stackName; + this.Program = program; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/LocalProgramArgs.cs b/sdk/dotnet/Pulumi.Automation/LocalProgramArgs.cs new file mode 100644 index 000000000..f2621eba2 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/LocalProgramArgs.cs @@ -0,0 +1,20 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// Description of a stack backed by pre-existing local Pulumi CLI program. + /// + public class LocalProgramArgs : LocalWorkspaceOptions + { + public string StackName { get; } + + public LocalProgramArgs( + string stackName, + string workDir) + { + this.StackName = stackName; + this.WorkDir = workDir; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/LocalWorkspace.cs b/sdk/dotnet/Pulumi.Automation/LocalWorkspace.cs new file mode 100644 index 000000000..851323577 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/LocalWorkspace.cs @@ -0,0 +1,598 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Pulumi.Automation.Commands; +using Pulumi.Automation.Serialization; + +namespace Pulumi.Automation +{ + /// + /// LocalWorkspace is a default implementation of the Workspace interface. + /// + /// A Workspace is the execution context containing a single Pulumi project, a program, + /// and multiple stacks.Workspaces are used to manage the execution environment, + /// providing various utilities such as plugin installation, environment configuration + /// ($PULUMI_HOME), and creation, deletion, and listing of Stacks. + /// + /// LocalWorkspace relies on Pulumi.yaml and Pulumi.{stack}.yaml as the intermediate format + /// for Project and Stack settings.Modifying ProjectSettings will + /// alter the Workspace Pulumi.yaml file, and setting config on a Stack will modify the Pulumi.{stack}.yaml file. + /// This is identical to the behavior of Pulumi CLI driven workspaces. + /// + /// If not provided a working directory - causing LocalWorkspace to create a temp directory, + /// than the temp directory will be cleaned up on . + /// + public sealed class LocalWorkspace : Workspace + { + private readonly LocalSerializer _serializer = new LocalSerializer(); + private readonly bool _ownsWorkingDir; + private readonly Task _readyTask; + + /// + public override string WorkDir { get; } + + /// + public override string? PulumiHome { get; } + + /// + public override string? SecretsProvider { get; } + + /// + public override PulumiFn? Program { get; set; } + + /// + public override IDictionary? EnvironmentVariables { get; set; } + + /// + /// Creates a workspace using the specified options. Used for maximal control and + /// customization of the underlying environment before any stacks are created or selected. + /// + /// Options used to configure the workspace. + /// A cancellation token. + public static async Task CreateAsync( + LocalWorkspaceOptions? options = null, + CancellationToken cancellationToken = default) + { + var ws = new LocalWorkspace( + new LocalPulumiCmd(), + options, + cancellationToken); + await ws._readyTask.ConfigureAwait(false); + return ws; + } + + /// + /// Creates a Stack with a utilizing the specified + /// inline (in process) . This program + /// is fully debuggable and runs in process. If no + /// option is specified, default project settings will be created on behalf of the user. Similarly, unless a + /// option is specified, the working directory will default + /// to a new temporary directory provided by the OS. + /// + /// + /// A set of arguments to initialize a Stack with an inline program + /// that runs in process, as well as any additional customizations to be applied to the + /// workspace. + /// + public static Task CreateStackAsync(InlineProgramArgs args) + => CreateStackAsync(args, default); + + /// + /// Creates a Stack with a utilizing the specified + /// inline (in process) . This program + /// is fully debuggable and runs in process. If no + /// option is specified, default project settings will be created on behalf of the user. Similarly, unless a + /// option is specified, the working directory will default + /// to a new temporary directory provided by the OS. + /// + /// + /// A set of arguments to initialize a Stack with an inline program + /// that runs in process, as well as any additional customizations to be applied to the + /// workspace. + /// + /// A cancellation token. + public static Task CreateStackAsync(InlineProgramArgs args, CancellationToken cancellationToken) + => CreateStackHelperAsync(args, WorkspaceStack.CreateAsync, cancellationToken); + + /// + /// Creates a Stack with a utilizing the local Pulumi CLI program + /// from the specified . This is a way to create drivers + /// on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + /// files(Pulumi.yaml, Pulumi.{stack}.yaml). + /// + /// + /// A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + /// already exists on disk, as well as any additional customizations to be applied to the + /// workspace. + /// + public static Task CreateStackAsync(LocalProgramArgs args) + => CreateStackAsync(args, default); + + /// + /// Creates a Stack with a utilizing the local Pulumi CLI program + /// from the specified . This is a way to create drivers + /// on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + /// files(Pulumi.yaml, Pulumi.{stack}.yaml). + /// + /// + /// A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + /// already exists on disk, as well as any additional customizations to be applied to the + /// workspace. + /// + /// A cancellation token. + public static Task CreateStackAsync(LocalProgramArgs args, CancellationToken cancellationToken) + => CreateStackHelperAsync(args, WorkspaceStack.CreateAsync, cancellationToken); + + /// + /// Selects an existing Stack with a utilizing the specified + /// inline (in process) . This program + /// is fully debuggable and runs in process. If no + /// option is specified, default project settings will be created on behalf of the user. Similarly, unless a + /// option is specified, the working directory will default + /// to a new temporary directory provided by the OS. + /// + /// + /// A set of arguments to initialize a Stack with an inline program + /// that runs in process, as well as any additional customizations to be applied to the + /// workspace. + /// + public static Task SelectStackAsync(InlineProgramArgs args) + => SelectStackAsync(args, default); + + /// + /// Selects an existing Stack with a utilizing the specified + /// inline (in process) . This program + /// is fully debuggable and runs in process. If no + /// option is specified, default project settings will be created on behalf of the user. Similarly, unless a + /// option is specified, the working directory will default + /// to a new temporary directory provided by the OS. + /// + /// + /// A set of arguments to initialize a Stack with an inline program + /// that runs in process, as well as any additional customizations to be applied to the + /// workspace. + /// + /// A cancellation token. + public static Task SelectStackAsync(InlineProgramArgs args, CancellationToken cancellationToken) + => CreateStackHelperAsync(args, WorkspaceStack.SelectAsync, cancellationToken); + + /// + /// Selects an existing Stack with a utilizing the local Pulumi CLI program + /// from the specified . This is a way to create drivers + /// on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + /// files(Pulumi.yaml, Pulumi.{stack}.yaml). + /// + /// + /// A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + /// already exists on disk, as well as any additional customizations to be applied to the + /// workspace. + /// + public static Task SelectStackAsync(LocalProgramArgs args) + => SelectStackAsync(args, default); + + /// + /// Selects an existing Stack with a utilizing the local Pulumi CLI program + /// from the specified . This is a way to create drivers + /// on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + /// files(Pulumi.yaml, Pulumi.{stack}.yaml). + /// + /// + /// A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + /// already exists on disk, as well as any additional customizations to be applied to the + /// workspace. + /// + /// A cancellation token. + public static Task SelectStackAsync(LocalProgramArgs args, CancellationToken cancellationToken) + => CreateStackHelperAsync(args, WorkspaceStack.SelectAsync, cancellationToken); + + /// + /// Creates or selects an existing Stack with a utilizing the specified + /// inline (in process) . This program + /// is fully debuggable and runs in process. If no + /// option is specified, default project settings will be created on behalf of the user. Similarly, unless a + /// option is specified, the working directory will default + /// to a new temporary directory provided by the OS. + /// + /// + /// A set of arguments to initialize a Stack with an inline program + /// that runs in process, as well as any additional customizations to be applied to the + /// workspace. + /// + public static Task CreateOrSelectStackAsync(InlineProgramArgs args) + => CreateOrSelectStackAsync(args, default); + + /// + /// Creates or selects an existing Stack with a utilizing the specified + /// inline (in process) . This program + /// is fully debuggable and runs in process. If no + /// option is specified, default project settings will be created on behalf of the user. Similarly, unless a + /// option is specified, the working directory will default + /// to a new temporary directory provided by the OS. + /// + /// + /// A set of arguments to initialize a Stack with an inline program + /// that runs in process, as well as any additional customizations to be applied to the + /// workspace. + /// + /// A cancellation token. + public static Task CreateOrSelectStackAsync(InlineProgramArgs args, CancellationToken cancellationToken) + => CreateStackHelperAsync(args, WorkspaceStack.CreateOrSelectAsync, cancellationToken); + + /// + /// Creates or selects an existing Stack with a utilizing the local Pulumi CLI program + /// from the specified . This is a way to create drivers + /// on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + /// files(Pulumi.yaml, Pulumi.{stack}.yaml). + /// + /// + /// A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + /// already exists on disk, as well as any additional customizations to be applied to the + /// workspace. + /// + public static Task CreateOrSelectStackAsync(LocalProgramArgs args) + => CreateOrSelectStackAsync(args, default); + + /// + /// Creates or selects an existing Stack with a utilizing the local Pulumi CLI program + /// from the specified . This is a way to create drivers + /// on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + /// files(Pulumi.yaml, Pulumi.{stack}.yaml). + /// + /// + /// A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + /// already exists on disk, as well as any additional customizations to be applied to the + /// workspace. + /// + /// A cancellation token. + public static Task CreateOrSelectStackAsync(LocalProgramArgs args, CancellationToken cancellationToken) + => CreateStackHelperAsync(args, WorkspaceStack.CreateOrSelectAsync, cancellationToken); + + private static async Task CreateStackHelperAsync( + InlineProgramArgs args, + Func> initFunc, + CancellationToken cancellationToken) + { + if (args.ProjectSettings is null) + throw new ArgumentNullException(nameof(args.ProjectSettings)); + + var ws = new LocalWorkspace( + new LocalPulumiCmd(), + args, + cancellationToken); + await ws._readyTask.ConfigureAwait(false); + + return await initFunc(args.StackName, ws, cancellationToken).ConfigureAwait(false); + } + + private static async Task CreateStackHelperAsync( + LocalProgramArgs args, + Func> initFunc, + CancellationToken cancellationToken) + { + var ws = new LocalWorkspace( + new LocalPulumiCmd(), + args, + cancellationToken); + await ws._readyTask.ConfigureAwait(false); + + return await initFunc(args.StackName, ws, cancellationToken).ConfigureAwait(false); + } + + internal LocalWorkspace( + IPulumiCmd cmd, + LocalWorkspaceOptions? options, + CancellationToken cancellationToken) + : base(cmd) + { + string? dir = null; + var readyTasks = new List(); + + if (options != null) + { + if (!string.IsNullOrWhiteSpace(options.WorkDir)) + dir = options.WorkDir; + + this.PulumiHome = options.PulumiHome; + this.Program = options.Program; + this.SecretsProvider = options.SecretsProvider; + + if (options.EnvironmentVariables != null) + this.EnvironmentVariables = new Dictionary(options.EnvironmentVariables); + } + + if (string.IsNullOrWhiteSpace(dir)) + { + // note that csharp doesn't guarantee that Path.GetRandomFileName returns a name + // for a file or folder that doesn't already exist. + // we should be OK with the "automation-" prefix but a collision is still + // theoretically possible + dir = Path.Combine(Path.GetTempPath(), $"automation-{Path.GetRandomFileName()}"); + Directory.CreateDirectory(dir); + this._ownsWorkingDir = true; + } + + this.WorkDir = dir; + + // these are after working dir is set because they start immediately + if (options?.ProjectSettings != null) + readyTasks.Add(this.SaveProjectSettingsAsync(options.ProjectSettings, cancellationToken)); + + if (options?.StackSettings != null && options.StackSettings.Any()) + { + foreach (var pair in options.StackSettings) + readyTasks.Add(this.SaveStackSettingsAsync(pair.Key, pair.Value, cancellationToken)); + } + + this._readyTask = Task.WhenAll(readyTasks); + } + + private static readonly string[] SettingsExtensions = new string[] { ".yaml", ".yml", ".json" }; + + /// + public override async Task GetProjectSettingsAsync(CancellationToken cancellationToken = default) + { + foreach (var ext in SettingsExtensions) + { + var isJson = ext == ".json"; + var path = Path.Combine(this.WorkDir, $"Pulumi{ext}"); + if (!File.Exists(path)) + continue; + + var content = await File.ReadAllTextAsync(path, cancellationToken).ConfigureAwait(false); + if (isJson) + return this._serializer.DeserializeJson(content); + + var model = this._serializer.DeserializeYaml(content); + return model.Convert(); + } + + return null; + } + + /// + public override Task SaveProjectSettingsAsync(ProjectSettings settings, CancellationToken cancellationToken = default) + { + var foundExt = ".yaml"; + foreach (var ext in SettingsExtensions) + { + var testPath = Path.Combine(this.WorkDir, $"Pulumi{ext}"); + if (File.Exists(testPath)) + { + foundExt = ext; + break; + } + } + + var path = Path.Combine(this.WorkDir, $"Pulumi{foundExt}"); + var content = foundExt == ".json" ? this._serializer.SerializeJson(settings) : this._serializer.SerializeYaml(settings); + return File.WriteAllTextAsync(path, content, cancellationToken); + } + + private static string GetStackSettingsName(string stackName) + { + var parts = stackName.Split('/'); + if (parts.Length < 1) + return stackName; + + return parts[^1]; + } + + /// + public override async Task GetStackSettingsAsync(string stackName, CancellationToken cancellationToken = default) + { + var settingsName = GetStackSettingsName(stackName); + + foreach (var ext in SettingsExtensions) + { + var isJson = ext == ".json"; + var path = Path.Combine(this.WorkDir, $"Pulumi.{settingsName}{ext}"); + if (!File.Exists(path)) + continue; + + var content = await File.ReadAllTextAsync(path, cancellationToken).ConfigureAwait(false); + return isJson ? this._serializer.DeserializeJson(content) : this._serializer.DeserializeYaml(content); + } + + return null; + } + + /// + public override Task SaveStackSettingsAsync(string stackName, StackSettings settings, CancellationToken cancellationToken = default) + { + var settingsName = GetStackSettingsName(stackName); + + var foundExt = ".yaml"; + foreach (var ext in SettingsExtensions) + { + var testPath = Path.Combine(this.WorkDir, $"Pulumi.{settingsName}{ext}"); + if (File.Exists(testPath)) + { + foundExt = ext; + break; + } + } + + var path = Path.Combine(this.WorkDir, $"Pulumi.{settingsName}{foundExt}"); + var content = foundExt == ".json" ? this._serializer.SerializeJson(settings) : this._serializer.SerializeYaml(settings); + return File.WriteAllTextAsync(path, content, cancellationToken); + } + + /// + public override Task> SerializeArgsForOpAsync(string stackName, CancellationToken cancellationToken = default) + => Task.FromResult(ImmutableList.Empty); + + /// + public override Task PostCommandCallbackAsync(string stackName, CancellationToken cancellationToken = default) + => Task.CompletedTask; + + /// + public override async Task GetConfigValueAsync(string stackName, string key, CancellationToken cancellationToken = default) + { + await this.SelectStackAsync(stackName, cancellationToken).ConfigureAwait(false); + var result = await this.RunCommandAsync(new[] { "config", "get", key, "--json" }, cancellationToken).ConfigureAwait(false); + return JsonSerializer.Deserialize(result.StandardOutput); + } + + /// + public override async Task> GetConfigAsync(string stackName, CancellationToken cancellationToken = default) + { + await this.SelectStackAsync(stackName, cancellationToken).ConfigureAwait(false); + return await this.GetConfigAsync(cancellationToken).ConfigureAwait(false); + } + + private async Task> GetConfigAsync(CancellationToken cancellationToken) + { + var result = await this.RunCommandAsync(new[] { "config", "--show-secrets", "--json" }, cancellationToken).ConfigureAwait(false); + var dict = this._serializer.DeserializeJson>(result.StandardOutput); + return dict.ToImmutableDictionary(); + } + + /// + public override async Task SetConfigValueAsync(string stackName, string key, ConfigValue value, CancellationToken cancellationToken = default) + { + await this.SelectStackAsync(stackName, cancellationToken).ConfigureAwait(false); + await this.SetConfigValueAsync(key, value, cancellationToken).ConfigureAwait(false); + } + + /// + public override async Task SetConfigAsync(string stackName, IDictionary configMap, CancellationToken cancellationToken = default) + { + // TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/3877 + await this.SelectStackAsync(stackName, cancellationToken).ConfigureAwait(false); + + foreach (var (key, value) in configMap) + await this.SetConfigValueAsync(key, value, cancellationToken).ConfigureAwait(false); + } + + private async Task SetConfigValueAsync(string key, ConfigValue value, CancellationToken cancellationToken) + { + var secretArg = value.IsSecret ? "--secret" : "--plaintext"; + await this.RunCommandAsync(new[] { "config", "set", key, value.Value, secretArg }, cancellationToken).ConfigureAwait(false); + } + + /// + public override async Task RemoveConfigValueAsync(string stackName, string key, CancellationToken cancellationToken = default) + { + await this.SelectStackAsync(stackName, cancellationToken).ConfigureAwait(false); + await this.RunCommandAsync(new[] { "config", "rm", key }, cancellationToken).ConfigureAwait(false); + } + + /// + public override async Task RemoveConfigAsync(string stackName, IEnumerable keys, CancellationToken cancellationToken = default) + { + // TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/3877 + await this.SelectStackAsync(stackName, cancellationToken).ConfigureAwait(false); + + foreach (var key in keys) + await this.RunCommandAsync(new[] { "config", "rm", key }, cancellationToken).ConfigureAwait(false); + } + + /// + public override async Task> RefreshConfigAsync(string stackName, CancellationToken cancellationToken = default) + { + await this.SelectStackAsync(stackName, cancellationToken).ConfigureAwait(false); + await this.RunCommandAsync(new[] { "config", "refresh", "--force" }, cancellationToken).ConfigureAwait(false); + return await this.GetConfigAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public override async Task WhoAmIAsync(CancellationToken cancellationToken = default) + { + var result = await this.RunCommandAsync(new[] { "whoami" }, cancellationToken).ConfigureAwait(false); + return new WhoAmIResult(result.StandardOutput.Trim()); + } + + /// + public override Task CreateStackAsync(string stackName, CancellationToken cancellationToken) + { + var args = new List() + { + "stack", + "init", + stackName, + }; + + if (!string.IsNullOrWhiteSpace(this.SecretsProvider)) + args.AddRange(new[] { "--secrets-provider", this.SecretsProvider }); + + return this.RunCommandAsync(args, cancellationToken); + } + + /// + public override Task SelectStackAsync(string stackName, CancellationToken cancellationToken) + => this.RunCommandAsync(new[] { "stack", "select", stackName }, cancellationToken); + + /// + public override Task RemoveStackAsync(string stackName, CancellationToken cancellationToken = default) + => this.RunCommandAsync(new[] { "stack", "rm", "--yes", stackName }, cancellationToken); + + /// + public override async Task> ListStacksAsync(CancellationToken cancellationToken = default) + { + var result = await this.RunCommandAsync(new[] { "stack", "ls", "--json" }, cancellationToken).ConfigureAwait(false); + var stacks = this._serializer.DeserializeJson>(result.StandardOutput); + return stacks.ToImmutableList(); + } + + /// + public override Task InstallPluginAsync(string name, string version, PluginKind kind = PluginKind.Resource, CancellationToken cancellationToken = default) + => this.RunCommandAsync(new[] { "plugin", "install", kind.ToString().ToLower(), name, version }, cancellationToken); + + /// + public override Task RemovePluginAsync(string? name = null, string? versionRange = null, PluginKind kind = PluginKind.Resource, CancellationToken cancellationToken = default) + { + var args = new List() + { + "plugin", + "rm", + kind.ToString().ToLower(), + }; + + if (!string.IsNullOrWhiteSpace(name)) + args.Add(name); + + if (!string.IsNullOrWhiteSpace(versionRange)) + args.Add(versionRange); + + args.Add("--yes"); + return this.RunCommandAsync(args, cancellationToken); + } + + /// + public override async Task> ListPluginsAsync(CancellationToken cancellationToken = default) + { + var result = await this.RunCommandAsync(new[] { "plugin", "ls", "--json" }, cancellationToken).ConfigureAwait(false); + var plugins = this._serializer.DeserializeJson>(result.StandardOutput); + return plugins.ToImmutableList(); + } + + public override void Dispose() + { + base.Dispose(); + + if (this._ownsWorkingDir + && !string.IsNullOrWhiteSpace(this.WorkDir) + && Directory.Exists(this.WorkDir)) + { + try + { + Directory.Delete(this.WorkDir, true); + } + catch + { + // allow graceful exit if for some reason + // we're not able to delete the directory + // will rely on OS to clean temp directory + // in this case. + } + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/LocalWorkspaceOptions.cs b/sdk/dotnet/Pulumi.Automation/LocalWorkspaceOptions.cs new file mode 100644 index 000000000..bbafc71d0 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/LocalWorkspaceOptions.cs @@ -0,0 +1,61 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Collections.Generic; + +namespace Pulumi.Automation +{ + /// + /// Extensibility options to configure a LocalWorkspace; e.g: settings to seed + /// and environment variables to pass through to every command. + /// + public class LocalWorkspaceOptions + { + /// + /// The directory to run Pulumi commands and read settings (Pulumi.yaml and Pulumi.{stack}.yaml). + /// + public string? WorkDir { get; set; } + + /// + /// The directory to override for CLI metadata. + /// + public string? PulumiHome { get; set; } + + /// + /// The secrets provider to user for encryption and decryption of stack secrets. + /// + /// See: https://www.pulumi.com/docs/intro/concepts/config/#available-encryption-providers + /// + public string? SecretsProvider { get; set; } + + /// + /// The inline program to be used for Preview/Update operations if any. + /// + /// If none is specified, the stack will refer to for this information. + /// + public PulumiFn? Program { get; set; } + + /// + /// Environment values scoped to the current workspace. These will be supplied to every + /// Pulumi command. + /// + public IDictionary? EnvironmentVariables { get; set; } + + /// + /// The settings object for the current project. + /// + /// If provided when initializing a project settings + /// file will be written to when the workspace is initialized via + /// . + /// + public ProjectSettings? ProjectSettings { get; set; } + + /// + /// A map of Stack names and corresponding settings objects. + /// + /// If provided when initializing stack settings + /// file(s) will be written to when the workspace is initialized via + /// . + /// + public IDictionary? StackSettings { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/OperationType.cs b/sdk/dotnet/Pulumi.Automation/OperationType.cs new file mode 100644 index 000000000..7740fa11c --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/OperationType.cs @@ -0,0 +1,15 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public enum OperationType + { + Same, + Create, + Update, + Delete, + Replace, + CreateReplacement, + DeleteReplaced, + } +} diff --git a/sdk/dotnet/Pulumi.Automation/OutputValue.cs b/sdk/dotnet/Pulumi.Automation/OutputValue.cs new file mode 100644 index 000000000..84d5e77f1 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/OutputValue.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public sealed class OutputValue + { + public object Value { get; } + + public bool IsSecret { get; } + + internal OutputValue( + object value, + bool isSecret) + { + this.Value = value; + this.IsSecret = isSecret; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/PluginInfo.cs b/sdk/dotnet/Pulumi.Automation/PluginInfo.cs new file mode 100644 index 000000000..f907bf828 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PluginInfo.cs @@ -0,0 +1,45 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; + +namespace Pulumi.Automation +{ + public class PluginInfo + { + public string Name { get; } + + public string? Path { get; } + + public PluginKind Kind { get; } + + public string? Version { get; } + + public long Size { get; } + + public DateTimeOffset InstallTime { get; } + + public DateTimeOffset LastUsedTime { get; } + + public string? ServerUrl { get; } + + internal PluginInfo( + string name, + string? path, + PluginKind kind, + string? version, + long size, + DateTimeOffset installTime, + DateTimeOffset lastUsedTime, + string? serverUrl) + { + this.Name = name; + this.Path = path; + this.Kind = kind; + this.Version = version; + this.Size = size; + this.InstallTime = installTime; + this.LastUsedTime = lastUsedTime; + this.ServerUrl = serverUrl; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/PluginKind.cs b/sdk/dotnet/Pulumi.Automation/PluginKind.cs new file mode 100644 index 000000000..5ecee37fb --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PluginKind.cs @@ -0,0 +1,11 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public enum PluginKind + { + Analyzer, + Language, + Resource, + } +} diff --git a/sdk/dotnet/Pulumi.Automation/PreviewOptions.cs b/sdk/dotnet/Pulumi.Automation/PreviewOptions.cs new file mode 100644 index 000000000..c4fd3fd39 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PreviewOptions.cs @@ -0,0 +1,20 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Collections.Generic; + +namespace Pulumi.Automation +{ + /// + /// Options controlling the behavior of an operation. + /// + public sealed class PreviewOptions : UpdateOptions + { + public bool? ExpectNoChanges { get; set; } + + public List? Replace { get; set; } + + public bool? TargetDependents { get; set; } + + public PulumiFn? Program { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ProjectBackend.cs b/sdk/dotnet/Pulumi.Automation/ProjectBackend.cs new file mode 100644 index 000000000..c1efa4063 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ProjectBackend.cs @@ -0,0 +1,12 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// Configuration for the project's Pulumi state storage backend. + /// + public class ProjectBackend + { + public string? Url { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ProjectRuntime.cs b/sdk/dotnet/Pulumi.Automation/ProjectRuntime.cs new file mode 100644 index 000000000..9f23a2b45 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ProjectRuntime.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// A description of the Project's program runtime and associated metadata. + /// + public class ProjectRuntime + { + public ProjectRuntimeName Name { get; set; } + + public ProjectRuntimeOptions? Options { get; set; } + + public ProjectRuntime(ProjectRuntimeName name) + { + this.Name = name; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ProjectRuntimeName.cs b/sdk/dotnet/Pulumi.Automation/ProjectRuntimeName.cs new file mode 100644 index 000000000..36ca98a1c --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ProjectRuntimeName.cs @@ -0,0 +1,15 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// Supported Pulumi program language runtimes. + /// + public enum ProjectRuntimeName + { + NodeJS, + Go, + Python, + Dotnet, + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ProjectRuntimeOptions.cs b/sdk/dotnet/Pulumi.Automation/ProjectRuntimeOptions.cs new file mode 100644 index 000000000..9405eb74e --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ProjectRuntimeOptions.cs @@ -0,0 +1,33 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// Various configuration options that apply to different language runtimes. + /// + public class ProjectRuntimeOptions + { + /// + /// Applies to NodeJS projects only. + /// + /// A boolean that controls whether to use ts-node to execute sources. + /// + public bool? TypeScript { get; set; } + + /// + /// Applies to Go and .NET project only. + /// + /// Go: A string that specifies the name of a pre-build executable to look for on your path. + /// + /// .NET: A string that specifies the path of a pre-build .NET assembly. + /// + public string? Binary { get; set; } + + /// + /// Applies to Python projects only. + /// + /// A string that specifies the path to a virtual environment to use when running the program. + /// + public string? VirtualEnv { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ProjectSettings.cs b/sdk/dotnet/Pulumi.Automation/ProjectSettings.cs new file mode 100644 index 000000000..67c3b41c9 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ProjectSettings.cs @@ -0,0 +1,48 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// A Pulumi project manifest. It describes metadata applying to all sub-stacks created from the project. + /// + public class ProjectSettings + { + public string Name { get; set; } + + public ProjectRuntime Runtime { get; set; } + + public string? Main { get; set; } + + public string? Description { get; set; } + + public string? Author { get; set; } + + public string? Website { get; set; } + + public string? License { get; set; } + + public string? Config { get; set; } + + public ProjectTemplate? Template { get; set; } + + public ProjectBackend? Backend { get; set; } + + public ProjectSettings( + string name, + ProjectRuntime runtime) + { + this.Name = name; + this.Runtime = runtime; + } + + public ProjectSettings( + string name, + ProjectRuntimeName runtime) + : this(name, new ProjectRuntime(runtime)) + { + } + + internal static ProjectSettings Default(string name) + => new ProjectSettings(name, new ProjectRuntime(ProjectRuntimeName.NodeJS)); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ProjectTemplate.cs b/sdk/dotnet/Pulumi.Automation/ProjectTemplate.cs new file mode 100644 index 000000000..d8a58f6a3 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ProjectTemplate.cs @@ -0,0 +1,20 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Collections.Generic; + +namespace Pulumi.Automation +{ + /// + /// A template used to seed new stacks created from this project. + /// + public class ProjectTemplate + { + public string? Description { get; set; } + + public string? QuickStart { get; set; } + + public IDictionary? Config { get; set; } + + public bool? Important { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/ProjectTemplateConfigValue.cs b/sdk/dotnet/Pulumi.Automation/ProjectTemplateConfigValue.cs new file mode 100644 index 000000000..8a63fffcd --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/ProjectTemplateConfigValue.cs @@ -0,0 +1,16 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + /// + /// A placeholder config value for a project template. + /// + public class ProjectTemplateConfigValue + { + public string? Description { get; set; } + + public string? Default { get; set; } + + public bool? Secret { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/PublicAPI.Shipped.txt b/sdk/dotnet/Pulumi.Automation/PublicAPI.Shipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation/PublicAPI.Unshipped.txt b/sdk/dotnet/Pulumi.Automation/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..3e9a89fc4 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PublicAPI.Unshipped.txt @@ -0,0 +1,327 @@ +Pulumi.Automation.Commands.Exceptions.CommandException +Pulumi.Automation.Commands.Exceptions.CommandException.Name.get -> string +Pulumi.Automation.Commands.Exceptions.ConcurrentUpdateException +Pulumi.Automation.Commands.Exceptions.StackAlreadyExistsException +Pulumi.Automation.Commands.Exceptions.StackNotFoundException +Pulumi.Automation.ConfigValue +Pulumi.Automation.ConfigValue.ConfigValue(string value, bool isSecret = false) -> void +Pulumi.Automation.ConfigValue.IsSecret.get -> bool +Pulumi.Automation.ConfigValue.IsSecret.set -> void +Pulumi.Automation.ConfigValue.Value.get -> string +Pulumi.Automation.ConfigValue.Value.set -> void +Pulumi.Automation.DestroyOptions +Pulumi.Automation.DestroyOptions.DestroyOptions() -> void +Pulumi.Automation.DestroyOptions.OnOutput.get -> System.Action +Pulumi.Automation.DestroyOptions.OnOutput.set -> void +Pulumi.Automation.DestroyOptions.TargetDependents.get -> bool? +Pulumi.Automation.DestroyOptions.TargetDependents.set -> void +Pulumi.Automation.HistoryOptions +Pulumi.Automation.HistoryOptions.HistoryOptions() -> void +Pulumi.Automation.HistoryOptions.Page.get -> int? +Pulumi.Automation.HistoryOptions.Page.set -> void +Pulumi.Automation.HistoryOptions.PageSize.get -> int? +Pulumi.Automation.HistoryOptions.PageSize.set -> void +Pulumi.Automation.InlineProgramArgs +Pulumi.Automation.InlineProgramArgs.InlineProgramArgs(string projectName, string stackName, Pulumi.Automation.PulumiFn program) -> void +Pulumi.Automation.InlineProgramArgs.StackName.get -> string +Pulumi.Automation.LocalProgramArgs +Pulumi.Automation.LocalProgramArgs.LocalProgramArgs(string stackName, string workDir) -> void +Pulumi.Automation.LocalProgramArgs.StackName.get -> string +Pulumi.Automation.LocalWorkspace +Pulumi.Automation.LocalWorkspaceOptions +Pulumi.Automation.LocalWorkspaceOptions.EnvironmentVariables.get -> System.Collections.Generic.IDictionary +Pulumi.Automation.LocalWorkspaceOptions.EnvironmentVariables.set -> void +Pulumi.Automation.LocalWorkspaceOptions.LocalWorkspaceOptions() -> void +Pulumi.Automation.LocalWorkspaceOptions.Program.get -> Pulumi.Automation.PulumiFn +Pulumi.Automation.LocalWorkspaceOptions.Program.set -> void +Pulumi.Automation.LocalWorkspaceOptions.ProjectSettings.get -> Pulumi.Automation.ProjectSettings +Pulumi.Automation.LocalWorkspaceOptions.ProjectSettings.set -> void +Pulumi.Automation.LocalWorkspaceOptions.PulumiHome.get -> string +Pulumi.Automation.LocalWorkspaceOptions.PulumiHome.set -> void +Pulumi.Automation.LocalWorkspaceOptions.SecretsProvider.get -> string +Pulumi.Automation.LocalWorkspaceOptions.SecretsProvider.set -> void +Pulumi.Automation.LocalWorkspaceOptions.StackSettings.get -> System.Collections.Generic.IDictionary +Pulumi.Automation.LocalWorkspaceOptions.StackSettings.set -> void +Pulumi.Automation.LocalWorkspaceOptions.WorkDir.get -> string +Pulumi.Automation.LocalWorkspaceOptions.WorkDir.set -> void +Pulumi.Automation.OperationType +Pulumi.Automation.OperationType.Create = 1 -> Pulumi.Automation.OperationType +Pulumi.Automation.OperationType.CreateReplacement = 5 -> Pulumi.Automation.OperationType +Pulumi.Automation.OperationType.Delete = 3 -> Pulumi.Automation.OperationType +Pulumi.Automation.OperationType.DeleteReplaced = 6 -> Pulumi.Automation.OperationType +Pulumi.Automation.OperationType.Replace = 4 -> Pulumi.Automation.OperationType +Pulumi.Automation.OperationType.Same = 0 -> Pulumi.Automation.OperationType +Pulumi.Automation.OperationType.Update = 2 -> Pulumi.Automation.OperationType +Pulumi.Automation.OutputValue +Pulumi.Automation.OutputValue.IsSecret.get -> bool +Pulumi.Automation.OutputValue.Value.get -> object +Pulumi.Automation.PluginInfo +Pulumi.Automation.PluginInfo.InstallTime.get -> System.DateTimeOffset +Pulumi.Automation.PluginInfo.Kind.get -> Pulumi.Automation.PluginKind +Pulumi.Automation.PluginInfo.LastUsedTime.get -> System.DateTimeOffset +Pulumi.Automation.PluginInfo.Name.get -> string +Pulumi.Automation.PluginInfo.Path.get -> string +Pulumi.Automation.PluginInfo.ServerUrl.get -> string +Pulumi.Automation.PluginInfo.Size.get -> long +Pulumi.Automation.PluginInfo.Version.get -> string +Pulumi.Automation.PluginKind +Pulumi.Automation.PluginKind.Analyzer = 0 -> Pulumi.Automation.PluginKind +Pulumi.Automation.PluginKind.Language = 1 -> Pulumi.Automation.PluginKind +Pulumi.Automation.PluginKind.Resource = 2 -> Pulumi.Automation.PluginKind +Pulumi.Automation.PreviewOptions +Pulumi.Automation.PreviewOptions.ExpectNoChanges.get -> bool? +Pulumi.Automation.PreviewOptions.ExpectNoChanges.set -> void +Pulumi.Automation.PreviewOptions.PreviewOptions() -> void +Pulumi.Automation.PreviewOptions.Program.get -> Pulumi.Automation.PulumiFn +Pulumi.Automation.PreviewOptions.Program.set -> void +Pulumi.Automation.PreviewOptions.Replace.get -> System.Collections.Generic.List +Pulumi.Automation.PreviewOptions.Replace.set -> void +Pulumi.Automation.PreviewOptions.TargetDependents.get -> bool? +Pulumi.Automation.PreviewOptions.TargetDependents.set -> void +Pulumi.Automation.ProjectBackend +Pulumi.Automation.ProjectBackend.ProjectBackend() -> void +Pulumi.Automation.ProjectBackend.Url.get -> string +Pulumi.Automation.ProjectBackend.Url.set -> void +Pulumi.Automation.ProjectRuntime +Pulumi.Automation.ProjectRuntime.Name.get -> Pulumi.Automation.ProjectRuntimeName +Pulumi.Automation.ProjectRuntime.Name.set -> void +Pulumi.Automation.ProjectRuntime.Options.get -> Pulumi.Automation.ProjectRuntimeOptions +Pulumi.Automation.ProjectRuntime.Options.set -> void +Pulumi.Automation.ProjectRuntime.ProjectRuntime(Pulumi.Automation.ProjectRuntimeName name) -> void +Pulumi.Automation.ProjectRuntimeName +Pulumi.Automation.ProjectRuntimeName.Dotnet = 3 -> Pulumi.Automation.ProjectRuntimeName +Pulumi.Automation.ProjectRuntimeName.Go = 1 -> Pulumi.Automation.ProjectRuntimeName +Pulumi.Automation.ProjectRuntimeName.NodeJS = 0 -> Pulumi.Automation.ProjectRuntimeName +Pulumi.Automation.ProjectRuntimeName.Python = 2 -> Pulumi.Automation.ProjectRuntimeName +Pulumi.Automation.ProjectRuntimeOptions +Pulumi.Automation.ProjectRuntimeOptions.Binary.get -> string +Pulumi.Automation.ProjectRuntimeOptions.Binary.set -> void +Pulumi.Automation.ProjectRuntimeOptions.ProjectRuntimeOptions() -> void +Pulumi.Automation.ProjectRuntimeOptions.TypeScript.get -> bool? +Pulumi.Automation.ProjectRuntimeOptions.TypeScript.set -> void +Pulumi.Automation.ProjectRuntimeOptions.VirtualEnv.get -> string +Pulumi.Automation.ProjectRuntimeOptions.VirtualEnv.set -> void +Pulumi.Automation.ProjectSettings +Pulumi.Automation.ProjectSettings.Author.get -> string +Pulumi.Automation.ProjectSettings.Author.set -> void +Pulumi.Automation.ProjectSettings.Backend.get -> Pulumi.Automation.ProjectBackend +Pulumi.Automation.ProjectSettings.Backend.set -> void +Pulumi.Automation.ProjectSettings.Config.get -> string +Pulumi.Automation.ProjectSettings.Config.set -> void +Pulumi.Automation.ProjectSettings.Description.get -> string +Pulumi.Automation.ProjectSettings.Description.set -> void +Pulumi.Automation.ProjectSettings.License.get -> string +Pulumi.Automation.ProjectSettings.License.set -> void +Pulumi.Automation.ProjectSettings.Main.get -> string +Pulumi.Automation.ProjectSettings.Main.set -> void +Pulumi.Automation.ProjectSettings.Name.get -> string +Pulumi.Automation.ProjectSettings.Name.set -> void +Pulumi.Automation.ProjectSettings.ProjectSettings(string name, Pulumi.Automation.ProjectRuntime runtime) -> void +Pulumi.Automation.ProjectSettings.ProjectSettings(string name, Pulumi.Automation.ProjectRuntimeName runtime) -> void +Pulumi.Automation.ProjectSettings.Runtime.get -> Pulumi.Automation.ProjectRuntime +Pulumi.Automation.ProjectSettings.Runtime.set -> void +Pulumi.Automation.ProjectSettings.Template.get -> Pulumi.Automation.ProjectTemplate +Pulumi.Automation.ProjectSettings.Template.set -> void +Pulumi.Automation.ProjectSettings.Website.get -> string +Pulumi.Automation.ProjectSettings.Website.set -> void +Pulumi.Automation.ProjectTemplate +Pulumi.Automation.ProjectTemplate.Config.get -> System.Collections.Generic.IDictionary +Pulumi.Automation.ProjectTemplate.Config.set -> void +Pulumi.Automation.ProjectTemplate.Description.get -> string +Pulumi.Automation.ProjectTemplate.Description.set -> void +Pulumi.Automation.ProjectTemplate.Important.get -> bool? +Pulumi.Automation.ProjectTemplate.Important.set -> void +Pulumi.Automation.ProjectTemplate.ProjectTemplate() -> void +Pulumi.Automation.ProjectTemplate.QuickStart.get -> string +Pulumi.Automation.ProjectTemplate.QuickStart.set -> void +Pulumi.Automation.ProjectTemplateConfigValue +Pulumi.Automation.ProjectTemplateConfigValue.Default.get -> string +Pulumi.Automation.ProjectTemplateConfigValue.Default.set -> void +Pulumi.Automation.ProjectTemplateConfigValue.Description.get -> string +Pulumi.Automation.ProjectTemplateConfigValue.Description.set -> void +Pulumi.Automation.ProjectTemplateConfigValue.ProjectTemplateConfigValue() -> void +Pulumi.Automation.ProjectTemplateConfigValue.Secret.get -> bool? +Pulumi.Automation.ProjectTemplateConfigValue.Secret.set -> void +Pulumi.Automation.PulumiFn +Pulumi.Automation.RefreshOptions +Pulumi.Automation.RefreshOptions.ExpectNoChanges.get -> bool? +Pulumi.Automation.RefreshOptions.ExpectNoChanges.set -> void +Pulumi.Automation.RefreshOptions.OnOutput.get -> System.Action +Pulumi.Automation.RefreshOptions.OnOutput.set -> void +Pulumi.Automation.RefreshOptions.RefreshOptions() -> void +Pulumi.Automation.StackSettings +Pulumi.Automation.StackSettings.Config.get -> System.Collections.Generic.IDictionary +Pulumi.Automation.StackSettings.Config.set -> void +Pulumi.Automation.StackSettings.EncryptedKey.get -> string +Pulumi.Automation.StackSettings.EncryptedKey.set -> void +Pulumi.Automation.StackSettings.EncryptionSalt.get -> string +Pulumi.Automation.StackSettings.EncryptionSalt.set -> void +Pulumi.Automation.StackSettings.SecretsProvider.get -> string +Pulumi.Automation.StackSettings.SecretsProvider.set -> void +Pulumi.Automation.StackSettings.StackSettings() -> void +Pulumi.Automation.StackSettingsConfigValue +Pulumi.Automation.StackSettingsConfigValue.IsSecure.get -> bool +Pulumi.Automation.StackSettingsConfigValue.StackSettingsConfigValue(string value, bool isSecure) -> void +Pulumi.Automation.StackSettingsConfigValue.Value.get -> string +Pulumi.Automation.StackSummary +Pulumi.Automation.StackSummary.IsCurrent.get -> bool +Pulumi.Automation.StackSummary.IsUpdateInProgress.get -> bool +Pulumi.Automation.StackSummary.LastUpdate.get -> System.DateTimeOffset? +Pulumi.Automation.StackSummary.Name.get -> string +Pulumi.Automation.StackSummary.ResourceCount.get -> int? +Pulumi.Automation.StackSummary.Url.get -> string +Pulumi.Automation.UpOptions +Pulumi.Automation.UpOptions.ExpectNoChanges.get -> bool? +Pulumi.Automation.UpOptions.ExpectNoChanges.set -> void +Pulumi.Automation.UpOptions.OnOutput.get -> System.Action +Pulumi.Automation.UpOptions.OnOutput.set -> void +Pulumi.Automation.UpOptions.Program.get -> Pulumi.Automation.PulumiFn +Pulumi.Automation.UpOptions.Program.set -> void +Pulumi.Automation.UpOptions.Replace.get -> System.Collections.Generic.List +Pulumi.Automation.UpOptions.Replace.set -> void +Pulumi.Automation.UpOptions.TargetDependents.get -> bool? +Pulumi.Automation.UpOptions.TargetDependents.set -> void +Pulumi.Automation.UpOptions.UpOptions() -> void +Pulumi.Automation.UpResult +Pulumi.Automation.UpResult.Outputs.get -> System.Collections.Immutable.IImmutableDictionary +Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateKind.Destroy = 4 -> Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateKind.Import = 5 -> Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateKind.Preview = 1 -> Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateKind.Refresh = 2 -> Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateKind.Rename = 3 -> Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateKind.Update = 0 -> Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateOptions +Pulumi.Automation.UpdateOptions.Message.get -> string +Pulumi.Automation.UpdateOptions.Message.set -> void +Pulumi.Automation.UpdateOptions.Parallel.get -> int? +Pulumi.Automation.UpdateOptions.Parallel.set -> void +Pulumi.Automation.UpdateOptions.Target.get -> System.Collections.Generic.List +Pulumi.Automation.UpdateOptions.Target.set -> void +Pulumi.Automation.UpdateOptions.UpdateOptions() -> void +Pulumi.Automation.UpdateResult +Pulumi.Automation.UpdateResult.StandardError.get -> string +Pulumi.Automation.UpdateResult.StandardOutput.get -> string +Pulumi.Automation.UpdateResult.Summary.get -> Pulumi.Automation.UpdateSummary +Pulumi.Automation.UpdateState +Pulumi.Automation.UpdateState.Failed = 3 -> Pulumi.Automation.UpdateState +Pulumi.Automation.UpdateState.InProgress = 1 -> Pulumi.Automation.UpdateState +Pulumi.Automation.UpdateState.NotStarted = 0 -> Pulumi.Automation.UpdateState +Pulumi.Automation.UpdateState.Succeeded = 2 -> Pulumi.Automation.UpdateState +Pulumi.Automation.UpdateSummary +Pulumi.Automation.UpdateSummary.Config.get -> System.Collections.Immutable.IImmutableDictionary +Pulumi.Automation.UpdateSummary.Deployment.get -> string +Pulumi.Automation.UpdateSummary.EndTime.get -> System.DateTimeOffset +Pulumi.Automation.UpdateSummary.Environment.get -> System.Collections.Immutable.IImmutableDictionary +Pulumi.Automation.UpdateSummary.Kind.get -> Pulumi.Automation.UpdateKind +Pulumi.Automation.UpdateSummary.Message.get -> string +Pulumi.Automation.UpdateSummary.ResourceChanges.get -> System.Collections.Immutable.IImmutableDictionary +Pulumi.Automation.UpdateSummary.Result.get -> Pulumi.Automation.UpdateState +Pulumi.Automation.UpdateSummary.StartTime.get -> System.DateTimeOffset +Pulumi.Automation.UpdateSummary.Version.get -> int? +Pulumi.Automation.WhoAmIResult +Pulumi.Automation.WhoAmIResult.User.get -> string +Pulumi.Automation.WhoAmIResult.WhoAmIResult(string user) -> void +Pulumi.Automation.Workspace +Pulumi.Automation.Workspace.CreateStackAsync(string stackName) -> System.Threading.Tasks.Task +Pulumi.Automation.Workspace.SelectStackAsync(string stackName) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack +Pulumi.Automation.WorkspaceStack.DestroyAsync(Pulumi.Automation.DestroyOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.Dispose() -> void +Pulumi.Automation.WorkspaceStack.GetConfigAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +Pulumi.Automation.WorkspaceStack.GetConfigValueAsync(string key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.GetHistoryAsync(Pulumi.Automation.HistoryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +Pulumi.Automation.WorkspaceStack.GetInfoAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.Name.get -> string +Pulumi.Automation.WorkspaceStack.PreviewAsync(Pulumi.Automation.PreviewOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.RefreshAsync(Pulumi.Automation.RefreshOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.RefreshConfigAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +Pulumi.Automation.WorkspaceStack.RemoveConfigAsync(System.Collections.Generic.IEnumerable keys, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.RemoveConfigValueAsync(string key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.SetConfigAsync(System.Collections.Generic.IDictionary configMap, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.SetConfigValueAsync(string key, Pulumi.Automation.ConfigValue value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.UpAsync(Pulumi.Automation.UpOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Pulumi.Automation.WorkspaceStack.Workspace.get -> Pulumi.Automation.Workspace +abstract Pulumi.Automation.Workspace.CreateStackAsync(string stackName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.EnvironmentVariables.get -> System.Collections.Generic.IDictionary +abstract Pulumi.Automation.Workspace.EnvironmentVariables.set -> void +abstract Pulumi.Automation.Workspace.GetConfigAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +abstract Pulumi.Automation.Workspace.GetConfigValueAsync(string stackName, string key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.GetProjectSettingsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.GetStackSettingsAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.InstallPluginAsync(string name, string version, Pulumi.Automation.PluginKind kind = Pulumi.Automation.PluginKind.Resource, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.ListPluginsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +abstract Pulumi.Automation.Workspace.ListStacksAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +abstract Pulumi.Automation.Workspace.PostCommandCallbackAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.Program.get -> Pulumi.Automation.PulumiFn +abstract Pulumi.Automation.Workspace.Program.set -> void +abstract Pulumi.Automation.Workspace.PulumiHome.get -> string +abstract Pulumi.Automation.Workspace.RefreshConfigAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +abstract Pulumi.Automation.Workspace.RemoveConfigAsync(string stackName, System.Collections.Generic.IEnumerable keys, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.RemoveConfigValueAsync(string stackName, string key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.RemovePluginAsync(string name = null, string versionRange = null, Pulumi.Automation.PluginKind kind = Pulumi.Automation.PluginKind.Resource, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.RemoveStackAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.SaveProjectSettingsAsync(Pulumi.Automation.ProjectSettings settings, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.SaveStackSettingsAsync(string stackName, Pulumi.Automation.StackSettings settings, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.SecretsProvider.get -> string +abstract Pulumi.Automation.Workspace.SelectStackAsync(string stackName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.SerializeArgsForOpAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +abstract Pulumi.Automation.Workspace.SetConfigAsync(string stackName, System.Collections.Generic.IDictionary configMap, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.SetConfigValueAsync(string stackName, string key, Pulumi.Automation.ConfigValue value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.WhoAmIAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +abstract Pulumi.Automation.Workspace.WorkDir.get -> string +override Pulumi.Automation.LocalWorkspace.CreateStackAsync(string stackName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.Dispose() -> void +override Pulumi.Automation.LocalWorkspace.EnvironmentVariables.get -> System.Collections.Generic.IDictionary +override Pulumi.Automation.LocalWorkspace.EnvironmentVariables.set -> void +override Pulumi.Automation.LocalWorkspace.GetConfigAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +override Pulumi.Automation.LocalWorkspace.GetConfigValueAsync(string stackName, string key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.GetProjectSettingsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.GetStackSettingsAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.InstallPluginAsync(string name, string version, Pulumi.Automation.PluginKind kind = Pulumi.Automation.PluginKind.Resource, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.ListPluginsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +override Pulumi.Automation.LocalWorkspace.ListStacksAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +override Pulumi.Automation.LocalWorkspace.PostCommandCallbackAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.Program.get -> Pulumi.Automation.PulumiFn +override Pulumi.Automation.LocalWorkspace.Program.set -> void +override Pulumi.Automation.LocalWorkspace.PulumiHome.get -> string +override Pulumi.Automation.LocalWorkspace.RefreshConfigAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +override Pulumi.Automation.LocalWorkspace.RemoveConfigAsync(string stackName, System.Collections.Generic.IEnumerable keys, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.RemoveConfigValueAsync(string stackName, string key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.RemovePluginAsync(string name = null, string versionRange = null, Pulumi.Automation.PluginKind kind = Pulumi.Automation.PluginKind.Resource, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.RemoveStackAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.SaveProjectSettingsAsync(Pulumi.Automation.ProjectSettings settings, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.SaveStackSettingsAsync(string stackName, Pulumi.Automation.StackSettings settings, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.SecretsProvider.get -> string +override Pulumi.Automation.LocalWorkspace.SelectStackAsync(string stackName, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.SerializeArgsForOpAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +override Pulumi.Automation.LocalWorkspace.SetConfigAsync(string stackName, System.Collections.Generic.IDictionary configMap, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.SetConfigValueAsync(string stackName, string key, Pulumi.Automation.ConfigValue value, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.WhoAmIAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +override Pulumi.Automation.LocalWorkspace.WorkDir.get -> string +static Pulumi.Automation.LocalWorkspace.CreateAsync(Pulumi.Automation.LocalWorkspaceOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateOrSelectStackAsync(Pulumi.Automation.InlineProgramArgs args) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateOrSelectStackAsync(Pulumi.Automation.InlineProgramArgs args, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateOrSelectStackAsync(Pulumi.Automation.LocalProgramArgs args) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateOrSelectStackAsync(Pulumi.Automation.LocalProgramArgs args, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateStackAsync(Pulumi.Automation.InlineProgramArgs args) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateStackAsync(Pulumi.Automation.InlineProgramArgs args, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateStackAsync(Pulumi.Automation.LocalProgramArgs args) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.CreateStackAsync(Pulumi.Automation.LocalProgramArgs args, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.SelectStackAsync(Pulumi.Automation.InlineProgramArgs args) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.SelectStackAsync(Pulumi.Automation.InlineProgramArgs args, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.SelectStackAsync(Pulumi.Automation.LocalProgramArgs args) -> System.Threading.Tasks.Task +static Pulumi.Automation.LocalWorkspace.SelectStackAsync(Pulumi.Automation.LocalProgramArgs args, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task +static Pulumi.Automation.PulumiFn.Create(System.Action program) -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.PulumiFn.Create(System.Func> program) -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.PulumiFn.Create(System.Func>> program) -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.PulumiFn.Create(System.Func program) -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.PulumiFn.Create(System.Func>> program) -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.PulumiFn.Create(System.Func program) -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.PulumiFn.Create() -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.PulumiFn.Create(System.IServiceProvider serviceProvider) -> Pulumi.Automation.PulumiFn +static Pulumi.Automation.WorkspaceStack.CreateAsync(string name, Pulumi.Automation.Workspace workspace, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +static Pulumi.Automation.WorkspaceStack.CreateOrSelectAsync(string name, Pulumi.Automation.Workspace workspace, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +static Pulumi.Automation.WorkspaceStack.SelectAsync(string name, Pulumi.Automation.Workspace workspace, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +virtual Pulumi.Automation.Workspace.Dispose() -> void +virtual Pulumi.Automation.Workspace.GetStackAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file diff --git a/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.csproj b/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.csproj new file mode 100644 index 000000000..af0b4a67b --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.csproj @@ -0,0 +1,64 @@ + + + + netcoreapp3.1 + enable + false + false + Pulumi + Pulumi Corp. + Pulumi Automation API, the programmatic interface for driving Pulumi programs without the CLI. + https://www.pulumi.com + https://github.com/pulumi/pulumi + Apache-2.0 + pulumi_logo_64x64.png + true + + + + .\Pulumi.Automation.xml + 1701;1702;1591;NU5105 + + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + True + + + + + + + + + diff --git a/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml b/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml new file mode 100644 index 000000000..e7ef44f88 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Pulumi.Automation.xml @@ -0,0 +1,881 @@ + + + + Pulumi.Automation + + + + + Options controlling the behavior of an operation. + + + + + Options controlling the behavior of a operation. + + + + + Description of a stack backed by pre-existing local Pulumi CLI program. + + + + + LocalWorkspace is a default implementation of the Workspace interface. + + A Workspace is the execution context containing a single Pulumi project, a program, + and multiple stacks.Workspaces are used to manage the execution environment, + providing various utilities such as plugin installation, environment configuration + ($PULUMI_HOME), and creation, deletion, and listing of Stacks. + + LocalWorkspace relies on Pulumi.yaml and Pulumi.{stack}.yaml as the intermediate format + for Project and Stack settings.Modifying ProjectSettings will + alter the Workspace Pulumi.yaml file, and setting config on a Stack will modify the Pulumi.{stack}.yaml file. + This is identical to the behavior of Pulumi CLI driven workspaces. + + If not provided a working directory - causing LocalWorkspace to create a temp directory, + than the temp directory will be cleaned up on . + + + + + + + + + + + + + + + + + + + + Creates a workspace using the specified options. Used for maximal control and + customization of the underlying environment before any stacks are created or selected. + + Options used to configure the workspace. + A cancellation token. + + + + Creates a Stack with a utilizing the specified + inline (in process) . This program + is fully debuggable and runs in process. If no + option is specified, default project settings will be created on behalf of the user. Similarly, unless a + option is specified, the working directory will default + to a new temporary directory provided by the OS. + + + A set of arguments to initialize a Stack with an inline program + that runs in process, as well as any additional customizations to be applied to the + workspace. + + + + + Creates a Stack with a utilizing the specified + inline (in process) . This program + is fully debuggable and runs in process. If no + option is specified, default project settings will be created on behalf of the user. Similarly, unless a + option is specified, the working directory will default + to a new temporary directory provided by the OS. + + + A set of arguments to initialize a Stack with an inline program + that runs in process, as well as any additional customizations to be applied to the + workspace. + + A cancellation token. + + + + Creates a Stack with a utilizing the local Pulumi CLI program + from the specified . This is a way to create drivers + on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + files(Pulumi.yaml, Pulumi.{stack}.yaml). + + + A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + already exists on disk, as well as any additional customizations to be applied to the + workspace. + + + + + Creates a Stack with a utilizing the local Pulumi CLI program + from the specified . This is a way to create drivers + on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + files(Pulumi.yaml, Pulumi.{stack}.yaml). + + + A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + already exists on disk, as well as any additional customizations to be applied to the + workspace. + + A cancellation token. + + + + Selects an existing Stack with a utilizing the specified + inline (in process) . This program + is fully debuggable and runs in process. If no + option is specified, default project settings will be created on behalf of the user. Similarly, unless a + option is specified, the working directory will default + to a new temporary directory provided by the OS. + + + A set of arguments to initialize a Stack with an inline program + that runs in process, as well as any additional customizations to be applied to the + workspace. + + + + + Selects an existing Stack with a utilizing the specified + inline (in process) . This program + is fully debuggable and runs in process. If no + option is specified, default project settings will be created on behalf of the user. Similarly, unless a + option is specified, the working directory will default + to a new temporary directory provided by the OS. + + + A set of arguments to initialize a Stack with an inline program + that runs in process, as well as any additional customizations to be applied to the + workspace. + + A cancellation token. + + + + Selects an existing Stack with a utilizing the local Pulumi CLI program + from the specified . This is a way to create drivers + on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + files(Pulumi.yaml, Pulumi.{stack}.yaml). + + + A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + already exists on disk, as well as any additional customizations to be applied to the + workspace. + + + + + Selects an existing Stack with a utilizing the local Pulumi CLI program + from the specified . This is a way to create drivers + on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + files(Pulumi.yaml, Pulumi.{stack}.yaml). + + + A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + already exists on disk, as well as any additional customizations to be applied to the + workspace. + + A cancellation token. + + + + Creates or selects an existing Stack with a utilizing the specified + inline (in process) . This program + is fully debuggable and runs in process. If no + option is specified, default project settings will be created on behalf of the user. Similarly, unless a + option is specified, the working directory will default + to a new temporary directory provided by the OS. + + + A set of arguments to initialize a Stack with an inline program + that runs in process, as well as any additional customizations to be applied to the + workspace. + + + + + Creates or selects an existing Stack with a utilizing the specified + inline (in process) . This program + is fully debuggable and runs in process. If no + option is specified, default project settings will be created on behalf of the user. Similarly, unless a + option is specified, the working directory will default + to a new temporary directory provided by the OS. + + + A set of arguments to initialize a Stack with an inline program + that runs in process, as well as any additional customizations to be applied to the + workspace. + + A cancellation token. + + + + Creates or selects an existing Stack with a utilizing the local Pulumi CLI program + from the specified . This is a way to create drivers + on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + files(Pulumi.yaml, Pulumi.{stack}.yaml). + + + A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + already exists on disk, as well as any additional customizations to be applied to the + workspace. + + + + + Creates or selects an existing Stack with a utilizing the local Pulumi CLI program + from the specified . This is a way to create drivers + on top of pre-existing Pulumi programs. This Workspace will pick up any available Settings + files(Pulumi.yaml, Pulumi.{stack}.yaml). + + + A set of arguments to initialize a Stack with a pre-configured Pulumi CLI program that + already exists on disk, as well as any additional customizations to be applied to the + workspace. + + A cancellation token. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Extensibility options to configure a LocalWorkspace; e.g: settings to seed + and environment variables to pass through to every command. + + + + + The directory to run Pulumi commands and read settings (Pulumi.yaml and Pulumi.{stack}.yaml). + + + + + The directory to override for CLI metadata. + + + + + The secrets provider to user for encryption and decryption of stack secrets. + + See: https://www.pulumi.com/docs/intro/concepts/config/#available-encryption-providers + + + + + The inline program to be used for Preview/Update operations if any. + + If none is specified, the stack will refer to for this information. + + + + + Environment values scoped to the current workspace. These will be supplied to every + Pulumi command. + + + + + The settings object for the current project. + + If provided when initializing a project settings + file will be written to when the workspace is initialized via + . + + + + + A map of Stack names and corresponding settings objects. + + If provided when initializing stack settings + file(s) will be written to when the workspace is initialized via + . + + + + + Options controlling the behavior of an operation. + + + + + Configuration for the project's Pulumi state storage backend. + + + + + A description of the Project's program runtime and associated metadata. + + + + + Supported Pulumi program language runtimes. + + + + + Various configuration options that apply to different language runtimes. + + + + + Applies to NodeJS projects only. + + A boolean that controls whether to use ts-node to execute sources. + + + + + Applies to Go and .NET project only. + + Go: A string that specifies the name of a pre-build executable to look for on your path. + + .NET: A string that specifies the path of a pre-build .NET assembly. + + + + + Applies to Python projects only. + + A string that specifies the path to a virtual environment to use when running the program. + + + + + A Pulumi project manifest. It describes metadata applying to all sub-stacks created from the project. + + + + + A template used to seed new stacks created from this project. + + + + + A placeholder config value for a project template. + + + + + A Pulumi program as an inline function (in process). + + + + + Creates an asynchronous inline (in process) pulumi program. + + An asynchronous pulumi program that takes in a and returns an output. + + + + Creates an asynchronous inline (in process) pulumi program. + + An asynchronous pulumi program that returns an output. + + + + Creates an asynchronous inline (in process) pulumi program. + + An asynchronous pulumi program that takes in a . + + + + Creates an asynchronous inline (in process) pulumi program. + + An asynchronous pulumi program. + + + + Creates an inline (in process) pulumi program. + + A pulumi program that returns an output. + + + + Creates an inline (in process) pulumi program. + + A pulumi program. + + + + Creates an inline (in process) pulumi program via a traditional implementation. + + The type. + + + + Creates an inline (in process) pulumi program via a traditional implementation. + + When invoked, a new stack instance will be resolved based + on the provided type parameter + using the . + + The type. + + + + Options controlling the behavior of an operation. + + + + + This stack's secrets provider. + + + + + This is the KMS-encrypted ciphertext for the data key used for secrets + encryption. Only used for cloud-based secrets providers. + + + + + This is this stack's base64 encoded encryption salt. Only used for + passphrase-based secrets providers. + + + + + This is an optional configuration bag. + + + + + Common options controlling the behavior of update actions taken + against an instance of . + + + + + Options controlling the behavior of an operation. + + + + + Workspace is the execution context containing a single Pulumi project, a program, and multiple stacks. + + Workspaces are used to manage the execution environment, providing various utilities such as plugin + installation, environment configuration ($PULUMI_HOME), and creation, deletion, and listing of Stacks. + + + + + The working directory to run Pulumi CLI commands. + + + + + The directory override for CLI metadata if set. + + This customizes the location of $PULUMI_HOME where metadata is stored and plugins are installed. + + + + + The secrets provider to use for encryption and decryption of stack secrets. + + See: https://www.pulumi.com/docs/intro/concepts/config/#available-encryption-providers + + + + + The inline program to be used for Preview/Update operations if any. + + If none is specified, the stack will refer to for this information. + + + + + Environment values scoped to the current workspace. These will be supplied to every Pulumi command. + + + + + Returns project settings for the current project if any. + + + + + Overwrites the settings for the current project. + + There can only be a single project per workspace. Fails if new project name does not match old. + + The settings object to save. + A cancellation token. + + + + Returns stack settings for the stack matching the specified stack name if any. + + The name of the stack. + A cancellation token. + + + + Overwrite the settings for the stack matching the specified stack name. + + The name of the stack to operate on. + The settings object to save. + A cancellation token. + + + + Hook to provide additional args to every CLI command before they are executed. + + Provided with a stack name, returns an array of args to append to an invoked command ["--config=...", ]. + + does not utilize this extensibility point. + + The name of the stack. + A cancellation token. + + + + Hook executed after every command. Called with the stack name. + + An extensibility point to perform workspace cleanup (CLI operations may create/modify a Pulumi.stack.yaml). + + does not utilize this extensibility point. + + The name of the stack. + A cancellation token. + + + + Returns the value associated with the specified stack name and key, scoped + to the Workspace. + + The name of the stack to read config from. + The key to use for the config lookup. + A cancellation token. + + + + Returns the config map for the specified stack name, scoped to the current Workspace. + + The name of the stack to read config from. + A cancellation token. + + + + Sets the specified key-value pair in the provided stack's config. + + The name of the stack to operate on. + The config key to set. + The config value to set. + A cancellation token. + + + + Sets all values in the provided config map for the specified stack name. + + The name of the stack to operate on. + The config map to upsert against the existing config. + A cancellation token. + + + + Removes the specified key-value pair from the provided stack's config. + + The name of the stack to operate on. + The config key to remove. + A cancellation token. + + + + Removes all values in the provided key collection from the config map for the specified stack name. + + The name of the stack to operate on. + The collection of keys to remove from the underlying config map. + A cancellation token. + + + + Gets and sets the config map used with the last update for the stack matching the specified stack name. + + The name of the stack to operate on. + A cancellation token. + + + + Returns the currently authenticated user. + + + + + Returns a summary of the currently selected stack, if any. + + + + + Creates and sets a new stack with the specified stack name, failing if one already exists. + + The stack to create. + + + + Creates and sets a new stack with the specified stack name, failing if one already exists. + + The stack to create. + A cancellation token. + If a stack already exists by the provided name. + + + + Selects and sets an existing stack matching the stack name, failing if none exists. + + The stack to select. + If no stack was found by the provided name. + + + + Selects and sets an existing stack matching the stack name, failing if none exists. + + The stack to select. + A cancellation token. + + + + Deletes the stack and all associated configuration and history. + + The stack to remove. + A cancellation token. + + + + Returns all stacks created under the current project. + + This queries underlying backend and may return stacks not present in the Workspace (as Pulumi.{stack}.yaml files). + + + + + Installs a plugin in the Workspace, for example to use cloud providers like AWS or GCP. + + The name of the plugin. + The version of the plugin e.g. "v1.0.0". + The kind of plugin e.g. "resource". + A cancellation token. + + + + Removes a plugin from the Workspace matching the specified name and version. + + The optional name of the plugin. + The optional semver range to check when removing plugins matching the given name e.g. "1.0.0", ">1.0.0". + The kind of plugin e.g. "resource". + A cancellation token. + + + + Returns a list of all plugins installed in the Workspace. + + + + + is an isolated, independently configurable instance of a + Pulumi program. exposes methods for the full pulumi lifecycle + (up/preview/refresh/destroy), as well as managing configuration. + + Multiple stacks are commonly used to denote different phases of development + (such as development, staging, and production) or feature branches (such as + feature-x-dev, jane-feature-x-dev). + + Will dispose the on . + + + + + The name identifying the Stack. + + + + + The Workspace the Stack was created from. + + + + + Creates a new stack using the given workspace, and stack name. + It fails if a stack with that name already exists. + + The name identifying the stack. + The Workspace the Stack was created from. + A cancellation token. + If a stack with the provided name already exists. + + + + Selects stack using the given workspace, and stack name. + It returns an error if the given Stack does not exist. + + The name identifying the stack. + The Workspace the Stack was created from. + A cancellation token. + If a stack with the provided name does not exists. + + + + Tries to create a new Stack using the given workspace, and stack name + if the stack does not already exist, or falls back to selecting an + existing stack. If the stack does not exist, it will be created and + selected. + + The name of the identifying stack. + The Workspace the Stack was created from. + A cancellation token. + + + + Returns the config value associated with the specified key. + + The key to use for the config lookup. + A cancellation token. + + + + Returns the full config map associated with the stack in the Workspace. + + A cancellation token. + + + + Sets the config key-value pair on the Stack in the associated Workspace. + + The key to set. + The config value to set. + A cancellation token. + + + + Sets all specified config values on the stack in the associated Workspace. + + The map of config key-value pairs to set. + A cancellation token. + + + + Removes the specified config key from the Stack in the associated Workspace. + + The config key to remove. + A cancellation token. + + + + Removes the specified config keys from the Stack in the associated Workspace. + + The config keys to remove. + A cancellation token. + + + + Gets and sets the config map used with the last update. + + A cancellation token. + + + + Creates or updates the resources in a stack by executing the program in the Workspace. + + https://www.pulumi.com/docs/reference/cli/pulumi_up/ + + Options to customize the behavior of the update. + A cancellation token. + + + + Performs a dry-run update to a stack, returning pending changes. + + https://www.pulumi.com/docs/reference/cli/pulumi_preview/ + + Options to customize the behavior of the update. + A cancellation token. + + + + Compares the current stack’s resource state with the state known to exist in the actual + cloud provider. Any such changes are adopted into the current stack. + + Options to customize the behavior of the refresh. + A cancellation token. + + + + Destroy deletes all resources in a stack, leaving all history and configuration intact. + + Options to customize the behavior of the destroy. + A cancellation token. + + + + Gets the current set of Stack outputs from the last . + + + + + Returns a list summarizing all previews and current results from Stack lifecycle operations (up/preview/refresh/destroy). + + Options to customize the behavior of the fetch history action. + A cancellation token. + + + diff --git a/sdk/dotnet/Pulumi.Automation/PulumiFn.Inline.cs b/sdk/dotnet/Pulumi.Automation/PulumiFn.Inline.cs new file mode 100644 index 000000000..a4dee895d --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PulumiFn.Inline.cs @@ -0,0 +1,41 @@ +// Copyright 2016-2021, Pulumi Corporation + + +using System; +using System.Collections.Generic; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Pulumi.Automation +{ + internal sealed class PulumiFnInline : PulumiFn + { + private readonly Func>> _program; + + public PulumiFnInline(Func>> program) + { + this._program = program; + } + + internal override async Task InvokeAsync(IRunner runner, CancellationToken cancellationToken) + { + ExceptionDispatchInfo? info = null; + + await runner.RunAsync(async () => + { + try + { + return await this._program(cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + info = ExceptionDispatchInfo.Capture(ex); + throw; + } + }, null).ConfigureAwait(false); + + return info; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/PulumiFn.TStack.cs b/sdk/dotnet/Pulumi.Automation/PulumiFn.TStack.cs new file mode 100644 index 000000000..896207756 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PulumiFn.TStack.cs @@ -0,0 +1,52 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Pulumi.Automation +{ + internal class PulumiFn : PulumiFn where TStack : Pulumi.Stack + { + private readonly Func _stackFactory; + + public PulumiFn(Func stackFactory) + { + this._stackFactory = stackFactory; + } + + internal override async Task InvokeAsync(IRunner runner, CancellationToken cancellationToken) + { + ExceptionDispatchInfo? info = null; + + await runner.RunAsync(() => + { + try + { + return this._stackFactory(); + } + // because we are newing a generic, reflection comes in to + // construct the instance. And if there is an exception in + // the constructor of the user-provided TStack, it will be wrapped + // in TargetInvocationException - which is not the exception + // we want to throw to the consumer. + catch (TargetInvocationException ex) + { + info = ex.InnerException != null + ? ExceptionDispatchInfo.Capture(ex.InnerException) + : ExceptionDispatchInfo.Capture(ex); + throw; + } + catch (Exception ex) + { + info = ExceptionDispatchInfo.Capture(ex); + throw; + } + }).ConfigureAwait(false); + + return info; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/PulumiFn.cs b/sdk/dotnet/Pulumi.Automation/PulumiFn.cs new file mode 100644 index 000000000..5876e88c5 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/PulumiFn.cs @@ -0,0 +1,123 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Pulumi.Automation +{ + /// + /// A Pulumi program as an inline function (in process). + /// + public abstract class PulumiFn + { + internal PulumiFn() + { + } + + internal abstract Task InvokeAsync(IRunner runner, CancellationToken cancellationToken); + + /// + /// Creates an asynchronous inline (in process) pulumi program. + /// + /// An asynchronous pulumi program that takes in a and returns an output. + public static PulumiFn Create(Func>> program) + => new PulumiFnInline(program); + + /// + /// Creates an asynchronous inline (in process) pulumi program. + /// + /// An asynchronous pulumi program that returns an output. + public static PulumiFn Create(Func>> program) + => new PulumiFnInline(cancellationToken => program()); + + /// + /// Creates an asynchronous inline (in process) pulumi program. + /// + /// An asynchronous pulumi program that takes in a . + public static PulumiFn Create(Func program) + { + Func>> wrapper = async cancellationToken => + { + await program(cancellationToken).ConfigureAwait(false); + return ImmutableDictionary.Empty; + }; + + return new PulumiFnInline(wrapper); + } + + /// + /// Creates an asynchronous inline (in process) pulumi program. + /// + /// An asynchronous pulumi program. + public static PulumiFn Create(Func program) + { + Func>> wrapper = async cancellationToken => + { + await program().ConfigureAwait(false); + return ImmutableDictionary.Empty; + }; + + return new PulumiFnInline(wrapper); + } + + /// + /// Creates an inline (in process) pulumi program. + /// + /// A pulumi program that returns an output. + public static PulumiFn Create(Func> program) + { + Func>> wrapper = cancellationToken => + { + var output = program(); + return Task.FromResult(output); + }; + + return new PulumiFnInline(wrapper); + } + + /// + /// Creates an inline (in process) pulumi program. + /// + /// A pulumi program. + public static PulumiFn Create(Action program) + => Create(() => { program(); return ImmutableDictionary.Empty; }); + + /// + /// Creates an inline (in process) pulumi program via a traditional implementation. + /// + /// The type. + public static PulumiFn Create() + where TStack : Pulumi.Stack, new() + => new PulumiFn(() => new TStack()); + + /// + /// Creates an inline (in process) pulumi program via a traditional implementation. + /// + /// When invoked, a new stack instance will be resolved based + /// on the provided type parameter + /// using the . + /// + /// The type. + public static PulumiFn Create(IServiceProvider serviceProvider) + where TStack : Pulumi.Stack + { + if (serviceProvider is null) + throw new ArgumentNullException(nameof(serviceProvider)); + + return new PulumiFn( + () => + { + if (serviceProvider is null) + throw new ArgumentNullException(nameof(serviceProvider), $"The provided service provider was null by the time this {nameof(PulumiFn)} was invoked."); + + return serviceProvider.GetService(typeof(TStack)) as TStack + ?? throw new ApplicationException( + $"Failed to resolve instance of type {typeof(TStack)} from service provider. Register the type with the service provider before this {nameof(PulumiFn)} is invoked."); + }); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/RefreshOptions.cs b/sdk/dotnet/Pulumi.Automation/RefreshOptions.cs new file mode 100644 index 000000000..2902f4981 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/RefreshOptions.cs @@ -0,0 +1,16 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; + +namespace Pulumi.Automation +{ + /// + /// Options controlling the behavior of an operation. + /// + public sealed class RefreshOptions : UpdateOptions + { + public bool? ExpectNoChanges { get; set; } + + public Action? OnOutput { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Runtime/LanguageRuntimeService.cs b/sdk/dotnet/Pulumi.Automation/Runtime/LanguageRuntimeService.cs new file mode 100644 index 000000000..0075fd91e --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Runtime/LanguageRuntimeService.cs @@ -0,0 +1,82 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Pulumirpc; + +namespace Pulumi.Automation +{ + internal class LanguageRuntimeService : LanguageRuntime.LanguageRuntimeBase + { + // MaxRpcMesageSize raises the gRPC Max Message size from `4194304` (4mb) to `419430400` (400mb) + public const int MaxRpcMesageSize = 1024 * 1024 * 400; + + private readonly CallerContext _callerContext; + + public LanguageRuntimeService(CallerContext callerContext) + { + this._callerContext = callerContext; + } + + public override Task GetRequiredPlugins(GetRequiredPluginsRequest request, ServerCallContext context) + { + var response = new GetRequiredPluginsResponse(); + return Task.FromResult(response); + } + + public override async Task Run(RunRequest request, ServerCallContext context) + { + if (this._callerContext.CancellationToken.IsCancellationRequested // if caller of UpAsync has cancelled + || context.CancellationToken.IsCancellationRequested) // if CLI has cancelled + { + return new RunResponse(); + } + + var args = request.Args; + var engineAddr = args != null && args.Any() ? args[0] : ""; + + var settings = new InlineDeploymentSettings( + engineAddr, + request.MonitorAddress, + request.Config, + request.Project, + request.Stack, + request.Parallel, + request.DryRun); + + using var cts = CancellationTokenSource.CreateLinkedTokenSource( + this._callerContext.CancellationToken, + context.CancellationToken); + + this._callerContext.ExceptionDispatchInfo = await Deployment.RunInlineAsync( + settings, + runner => this._callerContext.Program.InvokeAsync(runner, cts.Token)) + .ConfigureAwait(false); + + Deployment.Instance = null!; + return new RunResponse(); + } + + public class CallerContext + { + public PulumiFn Program { get; } + + public CancellationToken CancellationToken { get; } + + public ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } + + public CallerContext( + PulumiFn program, + CancellationToken cancellationToken) + { + this.Program = program; + this.CancellationToken = cancellationToken; + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/ConfigValueModel.cs b/sdk/dotnet/Pulumi.Automation/Serialization/ConfigValueModel.cs new file mode 100644 index 000000000..7ba6eed78 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/ConfigValueModel.cs @@ -0,0 +1,17 @@ +// Copyright 2016-2021, Pulumi Corporation + +using Pulumi.Automation.Serialization.Json; + +namespace Pulumi.Automation.Serialization +{ + // necessary for constructor deserialization + internal class ConfigValueModel : IJsonModel + { + public string Value { get; set; } = null!; + + public bool Secret { get; set; } + + public ConfigValue Convert() + => new ConfigValue(this.Value, this.Secret); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Json/IJsonModel.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Json/IJsonModel.cs new file mode 100644 index 000000000..63bf94c80 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Json/IJsonModel.cs @@ -0,0 +1,9 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation.Serialization.Json +{ + internal interface IJsonModel + { + T Convert(); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Json/LowercaseJsonNamingPolicy.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Json/LowercaseJsonNamingPolicy.cs new file mode 100644 index 000000000..190586d80 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Json/LowercaseJsonNamingPolicy.cs @@ -0,0 +1,12 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Text.Json; + +namespace Pulumi.Automation.Serialization.Json +{ + internal class LowercaseJsonNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + => name.ToLower(); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Json/MapToModelJsonConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Json/MapToModelJsonConverter.cs new file mode 100644 index 000000000..1df4dfbd7 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Json/MapToModelJsonConverter.cs @@ -0,0 +1,28 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Pulumi.Automation.Serialization.Json +{ + // necessary because this version of System.Text.Json + // can't deserialize a type that doesn't have a parameterless constructor + internal class MapToModelJsonConverter : JsonConverter + where TModel : IJsonModel + { + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var model = JsonSerializer.Deserialize(ref reader, options); + if (model is null) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Expecting object."); + + return model.Convert(); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Json/ProjectRuntimeJsonConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Json/ProjectRuntimeJsonConverter.cs new file mode 100644 index 000000000..f264c78f4 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Json/ProjectRuntimeJsonConverter.cs @@ -0,0 +1,75 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Pulumi.Automation.Serialization.Json +{ + internal class ProjectRuntimeJsonConverter : JsonConverter + { + public override ProjectRuntime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var runtimeName = DeserializeName(ref reader, typeToConvert); + return new ProjectRuntime(runtimeName); + } + + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Expecting string or object."); + + reader.Read(); + if (reader.TokenType != JsonTokenType.PropertyName + || !string.Equals(nameof(ProjectRuntime.Name), reader.GetString(), StringComparison.OrdinalIgnoreCase)) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Expecting runtime name property."); + + reader.Read(); + if (reader.TokenType != JsonTokenType.String) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Runtime name property should be a string."); + + var name = DeserializeName(ref reader, typeToConvert); + + reader.Read(); + if (reader.TokenType == JsonTokenType.EndObject) + return new ProjectRuntime(name); + + if (reader.TokenType != JsonTokenType.PropertyName + || !string.Equals(nameof(ProjectRuntime.Options), reader.GetString(), StringComparison.OrdinalIgnoreCase)) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Expecting runtime options property."); + + reader.Read(); + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Runtime options property should be an object."); + + var runtimeOptions = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); // read final EndObject token + + return new ProjectRuntime(name) { Options = runtimeOptions }; + + static ProjectRuntimeName DeserializeName(ref Utf8JsonReader reader, Type typeToConvert) + { + var runtimeStr = reader.GetString(); + if (string.IsNullOrWhiteSpace(runtimeStr)) + throw new JsonException($"A valid runtime name was not provided when deserializing [{typeToConvert.FullName}]."); + + if (Enum.TryParse(runtimeStr, true, out var runtimeName)) + return runtimeName; + + throw new JsonException($"Unexpected runtime name of \"{runtimeStr}\" provided when deserializing [{typeToConvert.FullName}]."); + } + } + + public override void Write(Utf8JsonWriter writer, ProjectRuntime value, JsonSerializerOptions options) + { + if (value.Options is null) + { + writer.WriteStringValue(value.Name.ToString().ToLower()); + } + else + { + JsonSerializer.Serialize(writer, value); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Json/ResourceChangesJsonConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Json/ResourceChangesJsonConverter.cs new file mode 100644 index 000000000..09ac954e4 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Json/ResourceChangesJsonConverter.cs @@ -0,0 +1,71 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Pulumi.Automation.Serialization.Json +{ + internal class ResourceChangesJsonConverter : JsonConverter> + { + public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException($"Cannot deserialize [{typeToConvert.FullName}]. Expecing object."); + + var dictionary = new Dictionary(); + + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Expecting property name."); + + var propertyName = reader.GetString(); + if (string.IsNullOrWhiteSpace(propertyName)) + throw new JsonException("Unable to retrieve property name."); + + var operationType = ConvertToOperationType(propertyName); + + reader.Read(); + if (reader.TokenType != JsonTokenType.Number + || !reader.TryGetInt32(out var count)) + throw new JsonException("Expecting number."); + + dictionary[operationType] = count; + reader.Read(); + } + + return dictionary; + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + private static OperationType ConvertToOperationType(string opType) + { + switch (opType) + { + case "create": + return OperationType.Create; + case "create-replacement": + return OperationType.CreateReplacement; + case "delete": + return OperationType.Delete; + case "delete-replaced": + return OperationType.DeleteReplaced; + case "replace": + return OperationType.Replace; + case "same": + return OperationType.Same; + case "update": + return OperationType.Update; + default: + throw new JsonException($"Invalid operation type: {opType}"); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Json/StackSettingsConfigValueJsonConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Json/StackSettingsConfigValueJsonConverter.cs new file mode 100644 index 000000000..30e179719 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Json/StackSettingsConfigValueJsonConverter.cs @@ -0,0 +1,57 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Pulumi.Automation.Serialization.Json +{ + internal class StackSettingsConfigValueJsonConverter : JsonConverter + { + public override StackSettingsConfigValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var element = JsonSerializer.Deserialize(ref reader, options); + + // check if plain string + if (element.ValueKind == JsonValueKind.String) + { + var value = element.GetString(); + return new StackSettingsConfigValue(value, false); + } + + // confirm object + if (element.ValueKind != JsonValueKind.Object) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Expecting object if not plain string."); + + // check if secure string + var securePropertyName = options.PropertyNamingPolicy?.ConvertName("Secure") ?? "Secure"; + if (element.TryGetProperty(securePropertyName, out var secureProperty)) + { + if (secureProperty.ValueKind != JsonValueKind.String) + throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}] as a secure string. Expecting a string secret."); + + var secret = secureProperty.GetString(); + return new StackSettingsConfigValue(secret, true); + } + + throw new NotSupportedException("Automation API does not currently support deserializing complex objects from stack settings."); + } + + public override void Write(Utf8JsonWriter writer, StackSettingsConfigValue value, JsonSerializerOptions options) + { + // secure string + if (value.IsSecure) + { + var securePropertyName = options.PropertyNamingPolicy?.ConvertName("Secure") ?? "Secure"; + writer.WriteStartObject(); + writer.WriteString(securePropertyName, value.Value); + writer.WriteEndObject(); + } + // plain string + else + { + writer.WriteStringValue(value.Value); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Json/SystemObjectJsonConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Json/SystemObjectJsonConverter.cs new file mode 100644 index 000000000..a4a94f663 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Json/SystemObjectJsonConverter.cs @@ -0,0 +1,80 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Pulumi.Automation.Serialization.Json +{ + internal class SystemObjectJsonConverter : JsonConverter + { + public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.True) + { + return true; + } + + if (reader.TokenType == JsonTokenType.False) + { + return false; + } + + if (reader.TokenType == JsonTokenType.Number) + { + if (reader.TryGetInt64(out long l)) + { + return l; + } + + return reader.GetDouble(); + } + + if (reader.TokenType == JsonTokenType.String) + { + if (reader.TryGetDateTime(out DateTime datetime)) + { + return datetime; + } + + return reader.GetString(); + } + + if (reader.TokenType == JsonTokenType.StartArray) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + var dictionary = new Dictionary(); + + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Expecting property name."); + + var propertyName = reader.GetString(); + if (string.IsNullOrWhiteSpace(propertyName)) + throw new JsonException("Unable to retrieve property name."); + + reader.Read(); + dictionary[propertyName] = JsonSerializer.Deserialize(ref reader, options); + + reader.Read(); + } + + return dictionary; + } + + throw new JsonException("Invalid JSON element."); + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + throw new NotSupportedException($"Writing as [{typeof(object).FullName}] is not supported."); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/LocalSerializer.cs b/sdk/dotnet/Pulumi.Automation/Serialization/LocalSerializer.cs new file mode 100644 index 000000000..da7d6b893 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/LocalSerializer.cs @@ -0,0 +1,81 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Text.Json; +using System.Text.Json.Serialization; +using Pulumi.Automation.Serialization.Json; +using Pulumi.Automation.Serialization.Yaml; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Pulumi.Automation.Serialization +{ + internal class LocalSerializer + { + private readonly JsonSerializerOptions _jsonOptions; + private readonly IDeserializer _yamlDeserializer; + private readonly ISerializer _yamlSerializer; + + public LocalSerializer() + { + // configure json + this._jsonOptions = BuildJsonSerializerOptions(); + + // configure yaml + this._yamlDeserializer = BuildYamlDeserializer(); + this._yamlSerializer = BuildYamlSerializer(); + } + + public T DeserializeJson(string content) + => JsonSerializer.Deserialize(content, this._jsonOptions); + + public T DeserializeYaml(string content) + where T : class + => this._yamlDeserializer.Deserialize(content); + + public string SerializeJson(T @object) + => JsonSerializer.Serialize(@object, this._jsonOptions); + + public string SerializeYaml(T @object) + where T : class + => this._yamlSerializer.Serialize(@object); + + public static JsonSerializerOptions BuildJsonSerializerOptions() + { + var options = new JsonSerializerOptions + { + AllowTrailingCommas = true, + IgnoreNullValues = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + options.Converters.Add(new JsonStringEnumConverter(new LowercaseJsonNamingPolicy())); + options.Converters.Add(new SystemObjectJsonConverter()); + options.Converters.Add(new MapToModelJsonConverter()); + options.Converters.Add(new MapToModelJsonConverter()); + options.Converters.Add(new MapToModelJsonConverter()); + options.Converters.Add(new MapToModelJsonConverter()); + options.Converters.Add(new MapToModelJsonConverter()); + options.Converters.Add(new ProjectRuntimeJsonConverter()); + options.Converters.Add(new ResourceChangesJsonConverter()); + options.Converters.Add(new StackSettingsConfigValueJsonConverter()); + + return options; + } + + public static IDeserializer BuildYamlDeserializer() + => new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .WithTypeConverter(new ProjectRuntimeYamlConverter()) + .WithTypeConverter(new StackSettingsConfigValueYamlConverter()) + .Build(); + + public static ISerializer BuildYamlSerializer() + => new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull) + .WithTypeConverter(new ProjectRuntimeYamlConverter()) + .WithTypeConverter(new StackSettingsConfigValueYamlConverter()) + .Build(); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/PluginInfoModel.cs b/sdk/dotnet/Pulumi.Automation/Serialization/PluginInfoModel.cs new file mode 100644 index 000000000..2d45432a7 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/PluginInfoModel.cs @@ -0,0 +1,44 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using Pulumi.Automation.Serialization.Json; + +namespace Pulumi.Automation.Serialization +{ + // necessary for constructor deserialization + internal class PluginInfoModel : IJsonModel + { + public string Name { get; set; } = null!; + + public string? Path { get; set; } + + public string Kind { get; set; } = null!; + + public string? Version { get; set; } + + public long Size { get; set; } + + public DateTimeOffset InstallTime { get; set; } + + public DateTimeOffset LastUsedTime { get; set; } + + public string? ServerUrl { get; set; } + + private PluginKind GetKind() + => string.Equals(this.Kind, "analyzer", StringComparison.OrdinalIgnoreCase) ? PluginKind.Analyzer + : string.Equals(this.Kind, "language", StringComparison.OrdinalIgnoreCase) ? PluginKind.Language + : string.Equals(this.Kind, "resource", StringComparison.OrdinalIgnoreCase) ? PluginKind.Resource + : throw new InvalidOperationException($"Invalid plugin kind: {this.Kind}"); + + public PluginInfo Convert() + => new PluginInfo( + this.Name, + this.Path, + this.GetKind(), + this.Version, + this.Size, + this.InstallTime, + this.LastUsedTime, + this.ServerUrl); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/ProjectSettingsModel.cs b/sdk/dotnet/Pulumi.Automation/Serialization/ProjectSettingsModel.cs new file mode 100644 index 000000000..72e11cd4e --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/ProjectSettingsModel.cs @@ -0,0 +1,43 @@ +// Copyright 2016-2021, Pulumi Corporation + +using Pulumi.Automation.Serialization.Json; + +namespace Pulumi.Automation.Serialization +{ + // necessary for constructor deserialization + internal class ProjectSettingsModel : IJsonModel + { + public string? Name { get; set; } + + public ProjectRuntime? Runtime { get; set; } + + public string? Main { get; set; } + + public string? Description { get; set; } + + public string? Author { get; set; } + + public string? Website { get; set; } + + public string? License { get; set; } + + public string? Config { get; set; } + + public ProjectTemplate? Template { get; set; } + + public ProjectBackend? Backend { get; set; } + + public ProjectSettings Convert() + => new ProjectSettings(this.Name!, this.Runtime ?? new ProjectRuntime(ProjectRuntimeName.NodeJS)) + { + Main = this.Main, + Description = this.Description, + Author = this.Author, + Website = this.Website, + License = this.License, + Config = this.Config, + Template = this.Template, + Backend = this.Backend, + }; + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/StackSummaryModel.cs b/sdk/dotnet/Pulumi.Automation/Serialization/StackSummaryModel.cs new file mode 100644 index 000000000..a7d80d625 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/StackSummaryModel.cs @@ -0,0 +1,32 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using Pulumi.Automation.Serialization.Json; + +namespace Pulumi.Automation.Serialization +{ + // necessary for constructor deserialization + internal class StackSummaryModel : IJsonModel + { + public string Name { get; set; } = null!; + + public bool Current { get; set; } + + public DateTimeOffset? LastUpdate { get; set; } + + public bool UpdateInProgress { get; set; } + + public int? ResourceCount { get; set; } + + public string? Url { get; set; } + + public StackSummary Convert() + => new StackSummary( + this.Name, + this.Current, + this.LastUpdate, + this.UpdateInProgress, + this.ResourceCount, + this.Url); + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/UpdateSummaryModel.cs b/sdk/dotnet/Pulumi.Automation/Serialization/UpdateSummaryModel.cs new file mode 100644 index 000000000..f8c75aa5c --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/UpdateSummaryModel.cs @@ -0,0 +1,56 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using Pulumi.Automation.Serialization.Json; + +namespace Pulumi.Automation.Serialization +{ + // necessary for constructor deserialization + internal class UpdateSummaryModel : IJsonModel + { + // pre-update information + public UpdateKind Kind { get; set; } + + public DateTimeOffset StartTime { get; set; } + + public string? Message { get; set; } + + public Dictionary? Environment { get; set; } + + public Dictionary? Config { get; set; } + + // post-update information + public string? Result { get; set; } + + public DateTimeOffset EndTime { get; set; } + + public int? Version { get; set; } + + public string? Deployment { get; set; } + + public Dictionary? ResourceChanges { get; set; } + + private UpdateState GetResult() + => string.Equals(this.Result, "not-started", StringComparison.OrdinalIgnoreCase) ? UpdateState.NotStarted + : string.Equals(this.Result, "in-progress", StringComparison.OrdinalIgnoreCase) ? UpdateState.InProgress + : string.Equals(this.Result, "succeeded", StringComparison.OrdinalIgnoreCase) ? UpdateState.Succeeded + : string.Equals(this.Result, "failed", StringComparison.OrdinalIgnoreCase) ? UpdateState.Failed + : throw new InvalidOperationException($"Invalid update result: {this.Result}"); + + public UpdateSummary Convert() + { + return new UpdateSummary( + this.Kind, + this.StartTime, + this.Message ?? string.Empty, + this.Environment ?? new Dictionary(), + this.Config ?? new Dictionary(), + this.GetResult(), + this.EndTime, + this.Version, + this.Deployment, + this.ResourceChanges); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeOptionsYamlConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeOptionsYamlConverter.cs new file mode 100644 index 000000000..21afd135c --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeOptionsYamlConverter.cs @@ -0,0 +1,91 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Linq; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Pulumi.Automation.Serialization.Yaml +{ + internal class ProjectRuntimeOptionsYamlConverter : IYamlTypeConverter + { + private static readonly Type Type = typeof(ProjectRuntimeOptions); + private static readonly List PropertyNames = typeof(ProjectRuntimeOptions).GetProperties().Select(x => x.Name).ToList(); + private static readonly Dictionary> Readers = + new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + [nameof(ProjectRuntimeOptions.Binary)] = (x, p, t) => x.Value, + [nameof(ProjectRuntimeOptions.TypeScript)] = (x, p, t) => x.ReadBoolean(p, t), + [nameof(ProjectRuntimeOptions.VirtualEnv)] = (x, p, t) => x.Value, + }; + + public bool Accepts(Type type) + => type == Type; + + public object? ReadYaml(IParser parser, Type type) + { + if (!parser.TryConsume(out _)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting object."); + + var values = PropertyNames.ToDictionary(x => x, x => (object?)null, StringComparer.OrdinalIgnoreCase); + + do + { + if (!parser.TryConsume(out var propertyNameScalar)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting a property name."); + + if (!Readers.TryGetValue(propertyNameScalar.Value, out var readerFunc)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Invalid property [{propertyNameScalar.Value}]."); + + if (!parser.TryConsume(out var propertyValueScalar)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting a scalar value for [{propertyNameScalar.Value}]."); + + values[propertyNameScalar.Value] = readerFunc(propertyValueScalar, propertyNameScalar.Value, type); + } + while (!parser.Accept(out _)); + + parser.MoveNext(); // read final MappingEnd event + return new ProjectRuntimeOptions + { + Binary = (string?)values[nameof(ProjectRuntimeOptions.Binary)], + TypeScript = (bool?)values[nameof(ProjectRuntimeOptions.TypeScript)], + VirtualEnv = (string?)values[nameof(ProjectRuntimeOptions.VirtualEnv)], + }; + } + + public void WriteYaml(IEmitter emitter, object? value, Type type) + { + if (!(value is ProjectRuntimeOptions options)) + return; + + if (string.IsNullOrWhiteSpace(options.Binary) + && options.TypeScript is null + && string.IsNullOrWhiteSpace(options.VirtualEnv)) + return; + + emitter.Emit(new MappingStart(null, null, false, MappingStyle.Block)); + + if (!string.IsNullOrWhiteSpace(options.Binary)) + { + emitter.Emit(new Scalar("binary")); + emitter.Emit(new Scalar(options.Binary)); + } + + if (options.TypeScript != null) + { + emitter.Emit(new Scalar("typescript")); + emitter.Emit(new Scalar(options.TypeScript.ToString()!.ToLower())); + } + + if (!string.IsNullOrWhiteSpace(options.VirtualEnv)) + { + emitter.Emit(new Scalar("virtualenv")); + emitter.Emit(new Scalar(options.VirtualEnv)); + } + + emitter.Emit(new MappingEnd()); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeYamlConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeYamlConverter.cs new file mode 100644 index 000000000..9394826e6 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/ProjectRuntimeYamlConverter.cs @@ -0,0 +1,96 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Pulumi.Automation.Serialization.Yaml +{ + internal class ProjectRuntimeYamlConverter : IYamlTypeConverter + { + private static readonly Type Type = typeof(ProjectRuntime); + private static readonly Type OptionsType = typeof(ProjectRuntimeOptions); + + private readonly IYamlTypeConverter _optionsConverter = new ProjectRuntimeOptionsYamlConverter(); + + public bool Accepts(Type type) + => type == Type; + + public object? ReadYaml(IParser parser, Type type) + { + if (parser.TryConsume(out var nameValueScalar)) + { + var runtimeName = DeserializeName(nameValueScalar, type); + return new ProjectRuntime(runtimeName); + } + + if (!parser.TryConsume(out _)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting string or object."); + + if (!parser.TryConsume(out var namePropertyScalar) + || !string.Equals(nameof(ProjectRuntime.Name), namePropertyScalar.Value, StringComparison.OrdinalIgnoreCase)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting runtime name property."); + + if (!parser.TryConsume(out var nameValueScalar2)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Runtime name property should be a string."); + + var name = DeserializeName(nameValueScalar2, type); + + // early mapping end is ok + if (parser.Accept(out _)) + { + parser.MoveNext(); // read final MappingEnd since Accept doesn't call MoveNext + return new ProjectRuntime(name); + } + + if (!parser.TryConsume(out var optionsPropertyScalar) + || !string.Equals(nameof(ProjectRuntime.Options), optionsPropertyScalar.Value, StringComparison.OrdinalIgnoreCase)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting runtime options property."); + + if (!parser.Accept(out _)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Runtime options property should be an object."); + + var runtimeOptionsObj = this._optionsConverter.ReadYaml(parser, OptionsType); + if (!(runtimeOptionsObj is ProjectRuntimeOptions runtimeOptions)) + throw new YamlException("There was an issue deserializing the runtime options object."); + + parser.MoveNext(); // read final MappingEnd event + return new ProjectRuntime(name) { Options = runtimeOptions }; + + static ProjectRuntimeName DeserializeName(Scalar nameValueScalar, Type type) + { + if (string.IsNullOrWhiteSpace(nameValueScalar.Value)) + throw new YamlException($"A valid runtime name was not provided when deserializing [{type.FullName}]."); + + if (Enum.TryParse(nameValueScalar.Value, true, out var runtimeName)) + return runtimeName; + + throw new YamlException($"Unexpected runtime name of \"{nameValueScalar.Value}\" provided when deserializing [{type.FullName}]."); + } + } + + public void WriteYaml(IEmitter emitter, object? value, Type type) + { + if (!(value is ProjectRuntime runtime)) + return; + + if (runtime.Options is null) + { + emitter.Emit(new Scalar(runtime.Name.ToString().ToLower())); + } + else + { + emitter.Emit(new MappingStart(null, null, false, MappingStyle.Block)); + + emitter.Emit(new Scalar("name")); + emitter.Emit(new Scalar(runtime.Name.ToString().ToLower())); + + emitter.Emit(new Scalar("options")); + this._optionsConverter.WriteYaml(emitter, runtime.Options, OptionsType); + + emitter.Emit(new MappingEnd()); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/StackSettingsConfigValueYamlConverter.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/StackSettingsConfigValueYamlConverter.cs new file mode 100644 index 000000000..d760d83ca --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/StackSettingsConfigValueYamlConverter.cs @@ -0,0 +1,72 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace Pulumi.Automation.Serialization.Yaml +{ + internal class StackSettingsConfigValueYamlConverter : IYamlTypeConverter + { + private static readonly Type Type = typeof(StackSettingsConfigValue); + + public bool Accepts(Type type) + => type == Type; + + public object? ReadYaml(IParser parser, Type type) + { + // check if plain string + if (parser.Accept(out var stringValue)) + { + parser.MoveNext(); + return new StackSettingsConfigValue(stringValue.Value, false); + } + + // confirm it is an object + if (!parser.TryConsume(out _)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting object if not plain string."); + + // get first property name + if (!parser.TryConsume(out var firstPropertyName)) + throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting first property name inside object."); + + // check if secure string + if (string.Equals("Secure", firstPropertyName.Value, StringComparison.OrdinalIgnoreCase)) + { + // secure string + if (!parser.TryConsume(out var securePropertyValue)) + throw new YamlException($"Unable to deserialize [{type.FullName}] as a secure string. Expecting a string secret."); + + // needs to be 1 mapping end and then return + parser.Require(); + parser.MoveNext(); + return new StackSettingsConfigValue(securePropertyValue.Value, true); + } + else + { + throw new NotSupportedException("Automation API does not currently support deserializing complex objects from stack settings."); + } + } + + public void WriteYaml(IEmitter emitter, object? value, Type type) + { + if (!(value is StackSettingsConfigValue configValue)) + return; + + // secure string + if (configValue.IsSecure) + { + emitter.Emit(new MappingStart(null, null, false, MappingStyle.Block)); + emitter.Emit(new Scalar("secure")); + emitter.Emit(new Scalar(configValue.Value)); + emitter.Emit(new MappingEnd()); + } + // plain string + else + { + emitter.Emit(new Scalar(configValue.Value)); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/YamlScalarExtensions.cs b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/YamlScalarExtensions.cs new file mode 100644 index 000000000..d53611b63 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Serialization/Yaml/YamlScalarExtensions.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; + +namespace Pulumi.Automation.Serialization.Yaml +{ + internal static class YamlScalarExtensions + { + public static bool ReadBoolean(this Scalar scalar, string propertyName, Type type) + { + if (bool.TryParse(scalar.Value, out var boolean)) + return boolean; + + throw new YamlException($"Unable to deserialize [{type.FullName}]. Exepecting a boolean for [{propertyName}]."); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/StackSettings.cs b/sdk/dotnet/Pulumi.Automation/StackSettings.cs new file mode 100644 index 000000000..43cf9853e --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/StackSettings.cs @@ -0,0 +1,31 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Collections.Generic; + +namespace Pulumi.Automation +{ + public class StackSettings + { + /// + /// This stack's secrets provider. + /// + public string? SecretsProvider { get; set; } + + /// + /// This is the KMS-encrypted ciphertext for the data key used for secrets + /// encryption. Only used for cloud-based secrets providers. + /// + public string? EncryptedKey { get; set; } + + /// + /// This is this stack's base64 encoded encryption salt. Only used for + /// passphrase-based secrets providers. + /// + public string? EncryptionSalt { get; set; } + + /// + /// This is an optional configuration bag. + /// + public IDictionary? Config { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/StackSettingsConfigValue.cs b/sdk/dotnet/Pulumi.Automation/StackSettingsConfigValue.cs new file mode 100644 index 000000000..77569e2f8 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/StackSettingsConfigValue.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public class StackSettingsConfigValue + { + public string Value { get; } + + public bool IsSecure { get; } + + public StackSettingsConfigValue( + string value, + bool isSecure) + { + this.Value = value; + this.IsSecure = isSecure; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/StackSummary.cs b/sdk/dotnet/Pulumi.Automation/StackSummary.cs new file mode 100644 index 000000000..e3bf3c23c --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/StackSummary.cs @@ -0,0 +1,37 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; + +namespace Pulumi.Automation +{ + public class StackSummary + { + public string Name { get; } + + public bool IsCurrent { get; } + + public DateTimeOffset? LastUpdate { get; } + + public bool IsUpdateInProgress { get; } + + public int? ResourceCount { get; } + + public string? Url { get; } + + internal StackSummary( + string name, + bool isCurrent, + DateTimeOffset? lastUpdate, + bool isUpdateInProgress, + int? resourceCount, + string? url) + { + this.Name = name; + this.IsCurrent = isCurrent; + this.LastUpdate = lastUpdate; + this.IsUpdateInProgress = isUpdateInProgress; + this.ResourceCount = resourceCount; + this.Url = url; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/UpOptions.cs b/sdk/dotnet/Pulumi.Automation/UpOptions.cs new file mode 100644 index 000000000..1b3ddddd1 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/UpOptions.cs @@ -0,0 +1,23 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; + +namespace Pulumi.Automation +{ + /// + /// Options controlling the behavior of an operation. + /// + public sealed class UpOptions : UpdateOptions + { + public bool? ExpectNoChanges { get; set; } + + public List? Replace { get; set; } + + public bool? TargetDependents { get; set; } + + public Action? OnOutput { get; set; } + + public PulumiFn? Program { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/UpResult.cs b/sdk/dotnet/Pulumi.Automation/UpResult.cs new file mode 100644 index 000000000..7ee67aff7 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/UpResult.cs @@ -0,0 +1,21 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Collections.Immutable; + +namespace Pulumi.Automation +{ + public sealed class UpResult : UpdateResult + { + public IImmutableDictionary Outputs { get; } + + internal UpResult( + string standardOutput, + string standardError, + UpdateSummary summary, + IImmutableDictionary outputs) + : base(standardOutput, standardError, summary) + { + this.Outputs = outputs; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/UpdateKind.cs b/sdk/dotnet/Pulumi.Automation/UpdateKind.cs new file mode 100644 index 000000000..3f5a3e995 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/UpdateKind.cs @@ -0,0 +1,14 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public enum UpdateKind + { + Update, + Preview, + Refresh, + Rename, + Destroy, + Import, + } +} diff --git a/sdk/dotnet/Pulumi.Automation/UpdateOptions.cs b/sdk/dotnet/Pulumi.Automation/UpdateOptions.cs new file mode 100644 index 000000000..217b06734 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/UpdateOptions.cs @@ -0,0 +1,19 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System.Collections.Generic; + +namespace Pulumi.Automation +{ + /// + /// Common options controlling the behavior of update actions taken + /// against an instance of . + /// + public class UpdateOptions + { + public int? Parallel { get; set; } + + public string? Message { get; set; } + + public List? Target { get; set; } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/UpdateResult.cs b/sdk/dotnet/Pulumi.Automation/UpdateResult.cs new file mode 100644 index 000000000..208997c9a --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/UpdateResult.cs @@ -0,0 +1,23 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public class UpdateResult + { + public string StandardOutput { get; } + + public string StandardError { get; } + + public UpdateSummary Summary { get; } + + internal UpdateResult( + string standardOutput, + string standardError, + UpdateSummary summary) + { + this.StandardOutput = standardOutput; + this.StandardError = standardError; + this.Summary = summary; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/UpdateState.cs b/sdk/dotnet/Pulumi.Automation/UpdateState.cs new file mode 100644 index 000000000..69bf8c432 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/UpdateState.cs @@ -0,0 +1,12 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public enum UpdateState + { + NotStarted, + InProgress, + Succeeded, + Failed, + } +} diff --git a/sdk/dotnet/Pulumi.Automation/UpdateSummary.cs b/sdk/dotnet/Pulumi.Automation/UpdateSummary.cs new file mode 100644 index 000000000..973cdd49a --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/UpdateSummary.cs @@ -0,0 +1,57 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Pulumi.Automation +{ + public sealed class UpdateSummary + { + // pre-update information + public UpdateKind Kind { get; } + + public DateTimeOffset StartTime { get; } + + public string Message { get; } + + public IImmutableDictionary Environment { get; } + + public IImmutableDictionary Config { get; } + + // post-update information + public UpdateState Result { get; } + + public DateTimeOffset EndTime { get; } + + public int? Version { get; } + + public string? Deployment { get; } + + public IImmutableDictionary? ResourceChanges { get; } + + internal UpdateSummary( + UpdateKind kind, + DateTimeOffset startTime, + string message, + IDictionary environment, + IDictionary config, + UpdateState result, + DateTimeOffset endTime, + int? version, + string? deployment, + IDictionary? resourceChanges) + { + this.Kind = kind; + this.StartTime = startTime; + this.Message = message; + this.Environment = environment.ToImmutableDictionary(); + this.Config = config.ToImmutableDictionary(); + this.Result = result; + this.EndTime = endTime; + this.Version = version; + this.Deployment = deployment; + this.ResourceChanges = resourceChanges?.ToImmutableDictionary(); + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/WhoAmIResult.cs b/sdk/dotnet/Pulumi.Automation/WhoAmIResult.cs new file mode 100644 index 000000000..2a4df8224 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/WhoAmIResult.cs @@ -0,0 +1,14 @@ +// Copyright 2016-2021, Pulumi Corporation + +namespace Pulumi.Automation +{ + public class WhoAmIResult + { + public string User { get; } + + public WhoAmIResult(string user) + { + this.User = user; + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/Workspace.cs b/sdk/dotnet/Pulumi.Automation/Workspace.cs new file mode 100644 index 000000000..eeee3a147 --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/Workspace.cs @@ -0,0 +1,289 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Pulumi.Automation.Commands; +using Pulumi.Automation.Commands.Exceptions; + +namespace Pulumi.Automation +{ + /// + /// Workspace is the execution context containing a single Pulumi project, a program, and multiple stacks. + /// + /// Workspaces are used to manage the execution environment, providing various utilities such as plugin + /// installation, environment configuration ($PULUMI_HOME), and creation, deletion, and listing of Stacks. + /// + public abstract class Workspace : IDisposable + { + private readonly IPulumiCmd _cmd; + + internal Workspace(IPulumiCmd cmd) + { + this._cmd = cmd; + } + + /// + /// The working directory to run Pulumi CLI commands. + /// + public abstract string WorkDir { get; } + + /// + /// The directory override for CLI metadata if set. + /// + /// This customizes the location of $PULUMI_HOME where metadata is stored and plugins are installed. + /// + public abstract string? PulumiHome { get; } + + /// + /// The secrets provider to use for encryption and decryption of stack secrets. + /// + /// See: https://www.pulumi.com/docs/intro/concepts/config/#available-encryption-providers + /// + public abstract string? SecretsProvider { get; } + + /// + /// The inline program to be used for Preview/Update operations if any. + /// + /// If none is specified, the stack will refer to for this information. + /// + public abstract PulumiFn? Program { get; set; } + + /// + /// Environment values scoped to the current workspace. These will be supplied to every Pulumi command. + /// + public abstract IDictionary? EnvironmentVariables { get; set; } + + /// + /// Returns project settings for the current project if any. + /// + public abstract Task GetProjectSettingsAsync(CancellationToken cancellationToken = default); + + /// + /// Overwrites the settings for the current project. + /// + /// There can only be a single project per workspace. Fails if new project name does not match old. + /// + /// The settings object to save. + /// A cancellation token. + public abstract Task SaveProjectSettingsAsync(ProjectSettings settings, CancellationToken cancellationToken = default); + + /// + /// Returns stack settings for the stack matching the specified stack name if any. + /// + /// The name of the stack. + /// A cancellation token. + public abstract Task GetStackSettingsAsync(string stackName, CancellationToken cancellationToken = default); + + /// + /// Overwrite the settings for the stack matching the specified stack name. + /// + /// The name of the stack to operate on. + /// The settings object to save. + /// A cancellation token. + public abstract Task SaveStackSettingsAsync(string stackName, StackSettings settings, CancellationToken cancellationToken = default); + + /// + /// Hook to provide additional args to every CLI command before they are executed. + /// + /// Provided with a stack name, returns an array of args to append to an invoked command ["--config=...", ]. + /// + /// does not utilize this extensibility point. + /// + /// The name of the stack. + /// A cancellation token. + public abstract Task> SerializeArgsForOpAsync(string stackName, CancellationToken cancellationToken = default); + + /// + /// Hook executed after every command. Called with the stack name. + /// + /// An extensibility point to perform workspace cleanup (CLI operations may create/modify a Pulumi.stack.yaml). + /// + /// does not utilize this extensibility point. + /// + /// The name of the stack. + /// A cancellation token. + public abstract Task PostCommandCallbackAsync(string stackName, CancellationToken cancellationToken = default); + + /// + /// Returns the value associated with the specified stack name and key, scoped + /// to the Workspace. + /// + /// The name of the stack to read config from. + /// The key to use for the config lookup. + /// A cancellation token. + public abstract Task GetConfigValueAsync(string stackName, string key, CancellationToken cancellationToken = default); + + /// + /// Returns the config map for the specified stack name, scoped to the current Workspace. + /// + /// The name of the stack to read config from. + /// A cancellation token. + public abstract Task> GetConfigAsync(string stackName, CancellationToken cancellationToken = default); + + /// + /// Sets the specified key-value pair in the provided stack's config. + /// + /// The name of the stack to operate on. + /// The config key to set. + /// The config value to set. + /// A cancellation token. + public abstract Task SetConfigValueAsync(string stackName, string key, ConfigValue value, CancellationToken cancellationToken = default); + + /// + /// Sets all values in the provided config map for the specified stack name. + /// + /// The name of the stack to operate on. + /// The config map to upsert against the existing config. + /// A cancellation token. + public abstract Task SetConfigAsync(string stackName, IDictionary configMap, CancellationToken cancellationToken = default); + + /// + /// Removes the specified key-value pair from the provided stack's config. + /// + /// The name of the stack to operate on. + /// The config key to remove. + /// A cancellation token. + public abstract Task RemoveConfigValueAsync(string stackName, string key, CancellationToken cancellationToken = default); + + /// + /// Removes all values in the provided key collection from the config map for the specified stack name. + /// + /// The name of the stack to operate on. + /// The collection of keys to remove from the underlying config map. + /// A cancellation token. + public abstract Task RemoveConfigAsync(string stackName, IEnumerable keys, CancellationToken cancellationToken = default); + + /// + /// Gets and sets the config map used with the last update for the stack matching the specified stack name. + /// + /// The name of the stack to operate on. + /// A cancellation token. + public abstract Task> RefreshConfigAsync(string stackName, CancellationToken cancellationToken = default); + + /// + /// Returns the currently authenticated user. + /// + public abstract Task WhoAmIAsync(CancellationToken cancellationToken = default); + + /// + /// Returns a summary of the currently selected stack, if any. + /// + public virtual async Task GetStackAsync(CancellationToken cancellationToken = default) + { + var stacks = await this.ListStacksAsync(cancellationToken).ConfigureAwait(false); + return stacks.FirstOrDefault(x => x.IsCurrent); + } + + /// + /// Creates and sets a new stack with the specified stack name, failing if one already exists. + /// + /// The stack to create. + public Task CreateStackAsync(string stackName) + => this.CreateStackAsync(stackName, default); + + /// + /// Creates and sets a new stack with the specified stack name, failing if one already exists. + /// + /// The stack to create. + /// A cancellation token. + /// If a stack already exists by the provided name. + public abstract Task CreateStackAsync(string stackName, CancellationToken cancellationToken); + + /// + /// Selects and sets an existing stack matching the stack name, failing if none exists. + /// + /// The stack to select. + /// If no stack was found by the provided name. + public Task SelectStackAsync(string stackName) + => this.SelectStackAsync(stackName, default); + + /// + /// Selects and sets an existing stack matching the stack name, failing if none exists. + /// + /// The stack to select. + /// A cancellation token. + public abstract Task SelectStackAsync(string stackName, CancellationToken cancellationToken); + + /// + /// Deletes the stack and all associated configuration and history. + /// + /// The stack to remove. + /// A cancellation token. + public abstract Task RemoveStackAsync(string stackName, CancellationToken cancellationToken = default); + + /// + /// Returns all stacks created under the current project. + /// + /// This queries underlying backend and may return stacks not present in the Workspace (as Pulumi.{stack}.yaml files). + /// + public abstract Task> ListStacksAsync(CancellationToken cancellationToken = default); + + /// + /// Installs a plugin in the Workspace, for example to use cloud providers like AWS or GCP. + /// + /// The name of the plugin. + /// The version of the plugin e.g. "v1.0.0". + /// The kind of plugin e.g. "resource". + /// A cancellation token. + public abstract Task InstallPluginAsync(string name, string version, PluginKind kind = PluginKind.Resource, CancellationToken cancellationToken = default); + + /// + /// Removes a plugin from the Workspace matching the specified name and version. + /// + /// The optional name of the plugin. + /// The optional semver range to check when removing plugins matching the given name e.g. "1.0.0", ">1.0.0". + /// The kind of plugin e.g. "resource". + /// A cancellation token. + public abstract Task RemovePluginAsync(string? name = null, string? versionRange = null, PluginKind kind = PluginKind.Resource, CancellationToken cancellationToken = default); + + /// + /// Returns a list of all plugins installed in the Workspace. + /// + public abstract Task> ListPluginsAsync(CancellationToken cancellationToken = default); + + internal async Task RunStackCommandAsync( + string stackName, + IEnumerable args, + Action? onOutput, + CancellationToken cancellationToken) + { + var additionalArgs = await this.SerializeArgsForOpAsync(stackName, cancellationToken).ConfigureAwait(false); + var completeArgs = args.Concat(additionalArgs).ToList(); + + var result = await this.RunCommandAsync(completeArgs, onOutput, cancellationToken).ConfigureAwait(false); + await this.PostCommandCallbackAsync(stackName, cancellationToken).ConfigureAwait(false); + return result; + } + + internal Task RunCommandAsync( + IEnumerable args, + CancellationToken cancellationToken) + => this.RunCommandAsync(args, null, cancellationToken); + + internal Task RunCommandAsync( + IEnumerable args, + Action? onOutput, + CancellationToken cancellationToken) + { + var env = new Dictionary(); + if (!string.IsNullOrWhiteSpace(this.PulumiHome)) + env["PULUMI_HOME"] = this.PulumiHome; + + if (this.EnvironmentVariables != null) + { + foreach (var pair in this.EnvironmentVariables) + env[pair.Key] = pair.Value; + } + + return this._cmd.RunAsync(args, this.WorkDir, env, onOutput, cancellationToken); + } + + public virtual void Dispose() + { + } + } +} diff --git a/sdk/dotnet/Pulumi.Automation/WorkspaceStack.cs b/sdk/dotnet/Pulumi.Automation/WorkspaceStack.cs new file mode 100644 index 000000000..754ad6d0d --- /dev/null +++ b/sdk/dotnet/Pulumi.Automation/WorkspaceStack.cs @@ -0,0 +1,681 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Net; +using System.Runtime.ExceptionServices; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Pulumi.Automation.Commands; +using Pulumi.Automation.Commands.Exceptions; +using Pulumi.Automation.Serialization; + +namespace Pulumi.Automation +{ + /// + /// is an isolated, independently configurable instance of a + /// Pulumi program. exposes methods for the full pulumi lifecycle + /// (up/preview/refresh/destroy), as well as managing configuration. + /// + /// Multiple stacks are commonly used to denote different phases of development + /// (such as development, staging, and production) or feature branches (such as + /// feature-x-dev, jane-feature-x-dev). + /// + /// Will dispose the on . + /// + public sealed class WorkspaceStack : IDisposable + { + private readonly Task _readyTask; + + /// + /// The name identifying the Stack. + /// + public string Name { get; } + + /// + /// The Workspace the Stack was created from. + /// + public Workspace Workspace { get; } + + /// + /// Creates a new stack using the given workspace, and stack name. + /// It fails if a stack with that name already exists. + /// + /// The name identifying the stack. + /// The Workspace the Stack was created from. + /// A cancellation token. + /// If a stack with the provided name already exists. + public static async Task CreateAsync( + string name, + Workspace workspace, + CancellationToken cancellationToken = default) + { + var stack = new WorkspaceStack(name, workspace, WorkspaceStackInitMode.Create, cancellationToken); + await stack._readyTask.ConfigureAwait(false); + return stack; + } + + /// + /// Selects stack using the given workspace, and stack name. + /// It returns an error if the given Stack does not exist. + /// + /// The name identifying the stack. + /// The Workspace the Stack was created from. + /// A cancellation token. + /// If a stack with the provided name does not exists. + public static async Task SelectAsync( + string name, + Workspace workspace, + CancellationToken cancellationToken = default) + { + var stack = new WorkspaceStack(name, workspace, WorkspaceStackInitMode.Select, cancellationToken); + await stack._readyTask.ConfigureAwait(false); + return stack; + } + + /// + /// Tries to create a new Stack using the given workspace, and stack name + /// if the stack does not already exist, or falls back to selecting an + /// existing stack. If the stack does not exist, it will be created and + /// selected. + /// + /// The name of the identifying stack. + /// The Workspace the Stack was created from. + /// A cancellation token. + public static async Task CreateOrSelectAsync( + string name, + Workspace workspace, + CancellationToken cancellationToken = default) + { + var stack = new WorkspaceStack(name, workspace, WorkspaceStackInitMode.CreateOrSelect, cancellationToken); + await stack._readyTask.ConfigureAwait(false); + return stack; + } + + private WorkspaceStack( + string name, + Workspace workspace, + WorkspaceStackInitMode mode, + CancellationToken cancellationToken) + { + this.Name = name; + this.Workspace = workspace; + + switch (mode) + { + case WorkspaceStackInitMode.Create: + this._readyTask = workspace.CreateStackAsync(name, cancellationToken); + break; + case WorkspaceStackInitMode.Select: + this._readyTask = workspace.SelectStackAsync(name, cancellationToken); + break; + case WorkspaceStackInitMode.CreateOrSelect: + this._readyTask = Task.Run(async () => + { + try + { + await workspace.CreateStackAsync(name, cancellationToken).ConfigureAwait(false); + } + catch (StackAlreadyExistsException) + { + await workspace.SelectStackAsync(name, cancellationToken).ConfigureAwait(false); + } + }); + break; + default: + throw new InvalidOperationException($"Unexpected Stack creation mode: {mode}"); + } + } + + /// + /// Returns the config value associated with the specified key. + /// + /// The key to use for the config lookup. + /// A cancellation token. + public Task GetConfigValueAsync(string key, CancellationToken cancellationToken = default) + => this.Workspace.GetConfigValueAsync(this.Name, key, cancellationToken); + + /// + /// Returns the full config map associated with the stack in the Workspace. + /// + /// A cancellation token. + public Task> GetConfigAsync(CancellationToken cancellationToken = default) + => this.Workspace.GetConfigAsync(this.Name, cancellationToken); + + /// + /// Sets the config key-value pair on the Stack in the associated Workspace. + /// + /// The key to set. + /// The config value to set. + /// A cancellation token. + public Task SetConfigValueAsync(string key, ConfigValue value, CancellationToken cancellationToken = default) + => this.Workspace.SetConfigValueAsync(this.Name, key, value, cancellationToken); + + /// + /// Sets all specified config values on the stack in the associated Workspace. + /// + /// The map of config key-value pairs to set. + /// A cancellation token. + public Task SetConfigAsync(IDictionary configMap, CancellationToken cancellationToken = default) + => this.Workspace.SetConfigAsync(this.Name, configMap, cancellationToken); + + /// + /// Removes the specified config key from the Stack in the associated Workspace. + /// + /// The config key to remove. + /// A cancellation token. + public Task RemoveConfigValueAsync(string key, CancellationToken cancellationToken = default) + => this.Workspace.RemoveConfigValueAsync(this.Name, key, cancellationToken); + + /// + /// Removes the specified config keys from the Stack in the associated Workspace. + /// + /// The config keys to remove. + /// A cancellation token. + public Task RemoveConfigAsync(IEnumerable keys, CancellationToken cancellationToken = default) + => this.Workspace.RemoveConfigAsync(this.Name, keys, cancellationToken); + + /// + /// Gets and sets the config map used with the last update. + /// + /// A cancellation token. + public Task> RefreshConfigAsync(CancellationToken cancellationToken = default) + => this.Workspace.RefreshConfigAsync(this.Name, cancellationToken); + + /// + /// Creates or updates the resources in a stack by executing the program in the Workspace. + /// + /// https://www.pulumi.com/docs/reference/cli/pulumi_up/ + /// + /// Options to customize the behavior of the update. + /// A cancellation token. + public async Task UpAsync( + UpOptions? options = null, + CancellationToken cancellationToken = default) + { + await this.Workspace.SelectStackAsync(this.Name, cancellationToken).ConfigureAwait(false); + var execKind = ExecKind.Local; + var program = this.Workspace.Program; + var args = new List() + { + "up", + "--yes", + "--skip-preview", + }; + + if (options != null) + { + if (options.Program != null) + program = options.Program; + + if (!string.IsNullOrWhiteSpace(options.Message)) + { + args.Add("--message"); + args.Add(options.Message); + } + + if (options.ExpectNoChanges is true) + args.Add("--expect-no-changes"); + + if (options.Replace?.Any() == true) + { + foreach (var item in options.Replace) + { + args.Add("--replace"); + args.Add(item); + } + } + + if (options.Target?.Any() == true) + { + foreach (var item in options.Target) + { + args.Add("--target"); + args.Add(item); + } + } + + if (options.TargetDependents is true) + args.Add("--target-dependents"); + + if (options.Parallel.HasValue) + { + args.Add("--parallel"); + args.Add(options.Parallel.Value.ToString()); + } + } + + InlineLanguageHost? inlineHost = null; + try + { + if (program != null) + { + execKind = ExecKind.Inline; + inlineHost = new InlineLanguageHost(program, cancellationToken); + await inlineHost.StartAsync().ConfigureAwait(false); + var port = await inlineHost.GetPortAsync().ConfigureAwait(false); + args.Add($"--client=127.0.0.1:{port}"); + } + + args.Add("--exec-kind"); + args.Add(execKind); + + var upResult = await this.RunCommandAsync(args, options?.OnOutput, cancellationToken).ConfigureAwait(false); + if (inlineHost != null && inlineHost.TryGetExceptionInfo(out var exceptionInfo)) + exceptionInfo.Throw(); + + var output = await this.GetOutputAsync(cancellationToken).ConfigureAwait(false); + var summary = await this.GetInfoAsync(cancellationToken).ConfigureAwait(false); + return new UpResult( + upResult.StandardOutput, + upResult.StandardError, + summary!, + output); + } + finally + { + if (inlineHost != null) + { + await inlineHost.DisposeAsync().ConfigureAwait(false); + } + } + } + + /// + /// Performs a dry-run update to a stack, returning pending changes. + /// + /// https://www.pulumi.com/docs/reference/cli/pulumi_preview/ + /// + /// Options to customize the behavior of the update. + /// A cancellation token. + public async Task PreviewAsync( + PreviewOptions? options = null, + CancellationToken cancellationToken = default) + { + await this.Workspace.SelectStackAsync(this.Name, cancellationToken).ConfigureAwait(false); + var execKind = ExecKind.Local; + var program = this.Workspace.Program; + var args = new List() { "preview" }; + + if (options != null) + { + if (options.Program != null) + program = options.Program; + + if (!string.IsNullOrWhiteSpace(options.Message)) + { + args.Add("--message"); + args.Add(options.Message); + } + + if (options.ExpectNoChanges is true) + args.Add("--expect-no-changes"); + + if (options.Replace?.Any() == true) + { + foreach (var item in options.Replace) + { + args.Add("--replace"); + args.Add(item); + } + } + + if (options.Target?.Any() == true) + { + foreach (var item in options.Target) + { + args.Add("--target"); + args.Add(item); + } + } + + if (options.TargetDependents is true) + args.Add("--target-dependents"); + + if (options.Parallel.HasValue) + { + args.Add("--parallel"); + args.Add(options.Parallel.Value.ToString()); + } + } + + InlineLanguageHost? inlineHost = null; + try + { + if (program != null) + { + execKind = ExecKind.Inline; + inlineHost = new InlineLanguageHost(program, cancellationToken); + await inlineHost.StartAsync().ConfigureAwait(false); + var port = await inlineHost.GetPortAsync().ConfigureAwait(false); + args.Add($"--client=127.0.0.1:{port}"); + } + + args.Add("--exec-kind"); + args.Add(execKind); + + var upResult = await this.RunCommandAsync(args, null, cancellationToken).ConfigureAwait(false); + if (inlineHost != null && inlineHost.TryGetExceptionInfo(out var exceptionInfo)) + exceptionInfo.Throw(); + + var summary = await this.GetInfoAsync(cancellationToken).ConfigureAwait(false); + return new UpdateResult( + upResult.StandardOutput, + upResult.StandardError, + summary!); + } + finally + { + if (inlineHost != null) + { + await inlineHost.DisposeAsync().ConfigureAwait(false); + } + } + } + + /// + /// Compares the current stack’s resource state with the state known to exist in the actual + /// cloud provider. Any such changes are adopted into the current stack. + /// + /// Options to customize the behavior of the refresh. + /// A cancellation token. + public async Task RefreshAsync( + RefreshOptions? options = null, + CancellationToken cancellationToken = default) + { + await this.Workspace.SelectStackAsync(this.Name, cancellationToken).ConfigureAwait(false); + var args = new List() + { + "refresh", + "--yes", + "--skip-preview", + }; + + if (options != null) + { + if (!string.IsNullOrWhiteSpace(options.Message)) + { + args.Add("--message"); + args.Add(options.Message); + } + + if (options.ExpectNoChanges is true) + args.Add("--expect-no-changes"); + + if (options.Target?.Any() == true) + { + foreach (var item in options.Target) + { + args.Add("--target"); + args.Add(item); + } + } + + if (options.Parallel.HasValue) + { + args.Add("--parallel"); + args.Add(options.Parallel.Value.ToString()); + } + } + + var result = await this.RunCommandAsync(args, options?.OnOutput, cancellationToken).ConfigureAwait(false); + var summary = await this.GetInfoAsync(cancellationToken).ConfigureAwait(false); + return new UpdateResult( + result.StandardOutput, + result.StandardError, + summary!); + } + + /// + /// Destroy deletes all resources in a stack, leaving all history and configuration intact. + /// + /// Options to customize the behavior of the destroy. + /// A cancellation token. + public async Task DestroyAsync( + DestroyOptions? options = null, + CancellationToken cancellationToken = default) + { + await this.Workspace.SelectStackAsync(this.Name, cancellationToken).ConfigureAwait(false); + var args = new List() + { + "destroy", + "--yes", + "--skip-preview", + }; + + if (options != null) + { + if (!string.IsNullOrWhiteSpace(options.Message)) + { + args.Add("--message"); + args.Add(options.Message); + } + + if (options.Target?.Any() == true) + { + foreach (var item in options.Target) + { + args.Add("--target"); + args.Add(item); + } + } + + if (options.TargetDependents is true) + args.Add("--target-dependents"); + + if (options.Parallel.HasValue) + { + args.Add("--parallel"); + args.Add(options.Parallel.Value.ToString()); + } + } + + var result = await this.RunCommandAsync(args, options?.OnOutput, cancellationToken).ConfigureAwait(false); + var summary = await this.GetInfoAsync(cancellationToken).ConfigureAwait(false); + return new UpdateResult( + result.StandardOutput, + result.StandardError, + summary!); + } + + /// + /// Gets the current set of Stack outputs from the last . + /// + private async Task> GetOutputAsync(CancellationToken cancellationToken) + { + await this.Workspace.SelectStackAsync(this.Name).ConfigureAwait(false); + + // TODO: do this in parallel after this is fixed https://github.com/pulumi/pulumi/issues/6050 + var maskedResult = await this.RunCommandAsync(new[] { "stack", "output", "--json" }, null, cancellationToken).ConfigureAwait(false); + var plaintextResult = await this.RunCommandAsync(new[] { "stack", "output", "--json", "--show-secrets" }, null, cancellationToken).ConfigureAwait(false); + var jsonOptions = LocalSerializer.BuildJsonSerializerOptions(); + var maskedOutput = JsonSerializer.Deserialize>(maskedResult.StandardOutput, jsonOptions); + var plaintextOutput = JsonSerializer.Deserialize>(plaintextResult.StandardOutput, jsonOptions); + + var output = new Dictionary(); + foreach (var (key, value) in plaintextOutput) + { + var secret = maskedOutput[key] is string maskedValue && maskedValue == "[secret]"; + output[key] = new OutputValue(value, secret); + } + + return output.ToImmutableDictionary(); + } + + /// + /// Returns a list summarizing all previews and current results from Stack lifecycle operations (up/preview/refresh/destroy). + /// + /// Options to customize the behavior of the fetch history action. + /// A cancellation token. + public async Task> GetHistoryAsync( + HistoryOptions? options = null, + CancellationToken cancellationToken = default) + { + var args = new List() + { + "history", + "--json", + "--show-secrets", + }; + + if (options?.PageSize.HasValue == true) + { + if (options.PageSize!.Value < 1) + throw new ArgumentException($"{nameof(options.PageSize)} must be greater than or equal to 1.", nameof(options.PageSize)); + + var page = !options.Page.HasValue ? 1 + : options.Page.Value < 1 ? 1 + : options.Page.Value; + + args.Add("--page-size"); + args.Add(options.PageSize.Value.ToString()); + args.Add("--page"); + args.Add(page.ToString()); + } + + var result = await this.RunCommandAsync(args, null, cancellationToken).ConfigureAwait(false); + var jsonOptions = LocalSerializer.BuildJsonSerializerOptions(); + var list = JsonSerializer.Deserialize>(result.StandardOutput, jsonOptions); + return list.ToImmutableList(); + } + + public async Task GetInfoAsync(CancellationToken cancellationToken = default) + { + var history = await this.GetHistoryAsync( + new HistoryOptions + { + PageSize = 1, + }, + cancellationToken).ConfigureAwait(false); + + return history.FirstOrDefault(); + } + + private Task RunCommandAsync( + IEnumerable args, + Action? onOutput, + CancellationToken cancellationToken) + => this.Workspace.RunStackCommandAsync(this.Name, args, onOutput, cancellationToken); + + public void Dispose() + => this.Workspace.Dispose(); + + private static class ExecKind + { + public const string Local = "auto.local"; + public const string Inline = "auto.inline"; + } + + private enum WorkspaceStackInitMode + { + Create, + Select, + CreateOrSelect + } + + private class InlineLanguageHost : IAsyncDisposable + { + private readonly TaskCompletionSource _portTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly CancellationToken _cancelToken; + private readonly IHost _host; + private readonly CancellationTokenRegistration _portRegistration; + + public InlineLanguageHost( + PulumiFn program, + CancellationToken cancellationToken) + { + this._cancelToken = cancellationToken; + this._host = Host.CreateDefaultBuilder() + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder + .ConfigureKestrel(kestrelOptions => + { + kestrelOptions.Listen(IPAddress.Any, 0, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + }) + .ConfigureServices(services => + { + services.AddLogging(); + + // to be injected into LanguageRuntimeService + var callerContext = new LanguageRuntimeService.CallerContext(program, cancellationToken); + services.AddSingleton(callerContext); + + services.AddGrpc(grpcOptions => + { + grpcOptions.MaxReceiveMessageSize = LanguageRuntimeService.MaxRpcMesageSize; + grpcOptions.MaxSendMessageSize = LanguageRuntimeService.MaxRpcMesageSize; + }); + }) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapGrpcService(); + }); + }); + }) + .Build(); + + // before starting the host, set up this callback to tell us what port was selected + this._portRegistration = this._host.Services.GetRequiredService().ApplicationStarted.Register(() => + { + try + { + var serverFeatures = this._host.Services.GetRequiredService().Features; + var addresses = serverFeatures.Get().Addresses.ToList(); + Debug.Assert(addresses.Count == 1, "Server should only be listening on one address"); + var uri = new Uri(addresses[0]); + this._portTcs.TrySetResult(uri.Port); + } + catch (Exception ex) + { + this._portTcs.TrySetException(ex); + } + }); + } + + public Task StartAsync() + => this._host.StartAsync(this._cancelToken); + + public Task GetPortAsync() + => this._portTcs.Task; + + public bool TryGetExceptionInfo([NotNullWhen(true)] out ExceptionDispatchInfo? info) + { + var callerContext = this._host.Services.GetRequiredService(); + if (callerContext.ExceptionDispatchInfo is null) + { + info = null; + return false; + } + + info = callerContext.ExceptionDispatchInfo; + return true; + } + + public async ValueTask DisposeAsync() + { + this._portRegistration.Unregister(); + await this._host.StopAsync(this._cancelToken).ConfigureAwait(false); + this._host.Dispose(); + } + } + } +} diff --git a/sdk/dotnet/Pulumi.Tests/AssemblyAttributes.cs b/sdk/dotnet/Pulumi.Tests/AssemblyAttributes.cs index 93085d07d..d8917540d 100644 --- a/sdk/dotnet/Pulumi.Tests/AssemblyAttributes.cs +++ b/sdk/dotnet/Pulumi.Tests/AssemblyAttributes.cs @@ -1,4 +1,4 @@ using Xunit; // Unfortunately, we depend on static state. So for now disable parallelization. -[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/sdk/dotnet/Pulumi.Tests/Pulumi.Tests.csproj b/sdk/dotnet/Pulumi.Tests/Pulumi.Tests.csproj index ad9c1a68e..bfa75a827 100644 --- a/sdk/dotnet/Pulumi.Tests/Pulumi.Tests.csproj +++ b/sdk/dotnet/Pulumi.Tests/Pulumi.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 @@ -19,7 +19,9 @@ - + + PreserveNewest + \ No newline at end of file diff --git a/sdk/dotnet/Pulumi/AssemblyAttributes.cs b/sdk/dotnet/Pulumi/AssemblyAttributes.cs index 8fc446652..4266e7d1b 100644 --- a/sdk/dotnet/Pulumi/AssemblyAttributes.cs +++ b/sdk/dotnet/Pulumi/AssemblyAttributes.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Pulumi.Automation")] [assembly: InternalsVisibleTo("Pulumi.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq tests diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment.Runner.cs b/sdk/dotnet/Pulumi/Deployment/Deployment.Runner.cs index 68aec1be3..6f14468bf 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment.Runner.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment.Runner.cs @@ -43,7 +43,7 @@ namespace Pulumi public Task RunAsync() where TStack : Stack, new() => RunAsync(() => new TStack()); - private Task RunAsync(Func stackFactory) where TStack : Stack + public Task RunAsync(Func stackFactory) where TStack : Stack { try { diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Config.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Config.cs index 355e573f0..9373f6e96 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_Config.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Config.cs @@ -1,6 +1,7 @@ // Copyright 2016-2019, Pulumi Corporation using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Text.Json; @@ -24,6 +25,12 @@ namespace Pulumi internal void SetConfig(string key, string value) => AllConfig = AllConfig.Add(key, value); + /// + /// Appends all provided configuration. + /// + internal void SetAllConfig(IDictionary config) + => AllConfig = AllConfig.AddRange(config); + /// /// Returns a configuration variable's value or if it is unset. /// diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Inline.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Inline.cs new file mode 100644 index 000000000..8c6aa6342 --- /dev/null +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Inline.cs @@ -0,0 +1,44 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; + +namespace Pulumi +{ + public partial class Deployment + { + private Deployment(InlineDeploymentSettings settings) + { + if (settings is null) + throw new ArgumentNullException(nameof(settings)); + + _projectName = settings.Project; + _stackName = settings.Stack; + _isDryRun = settings.IsDryRun; + SetAllConfig(settings.Config); + + if (string.IsNullOrEmpty(settings.MonitorAddr) + || string.IsNullOrEmpty(settings.EngineAddr) + || string.IsNullOrEmpty(_projectName) + || string.IsNullOrEmpty(_stackName)) + { + throw new InvalidOperationException("Inline execution was not provided the necessary parameters to run the Pulumi engine."); + } + + Serilog.Log.Debug("Creating Deployment Engine."); + Engine = new GrpcEngine(settings.EngineAddr); + Serilog.Log.Debug("Created Deployment Engine."); + + Serilog.Log.Debug("Creating Deployment Monitor."); + Monitor = new GrpcMonitor(settings.MonitorAddr); + Serilog.Log.Debug("Created Deployment Monitor."); + + _runner = new Runner(this); + _logger = new Logger(this, Engine); + } + + internal static Task RunInlineAsync(InlineDeploymentSettings settings, Func> func) + => func(CreateRunner(() => new Deployment(settings))); + } +} diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Run.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Run.cs index 263c5c153..50084eaea 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_Run.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Run.cs @@ -72,7 +72,7 @@ namespace Pulumi /// Callback that creates stack resources. /// Stack options. public static Task RunAsync(Func>> func, StackOptions? options = null) - => CreateRunner().RunAsync(func, options); + => CreateRunner(() => new Deployment()).RunAsync(func, options); /// /// is an entry-point to a Pulumi @@ -101,7 +101,7 @@ namespace Pulumi /// /// public static Task RunAsync() where TStack : Stack, new() - => CreateRunner().RunAsync(); + => CreateRunner(() => new Deployment()).RunAsync(); /// /// is an entry-point to a Pulumi @@ -131,7 +131,7 @@ namespace Pulumi /// /// public static Task RunAsync(IServiceProvider serviceProvider) where TStack : Stack - => CreateRunner().RunAsync(serviceProvider); + => CreateRunner(() => new Deployment()).RunAsync(serviceProvider); /// /// Entry point to test a Pulumi application. Deployment will @@ -203,7 +203,7 @@ namespace Pulumi } } - private static IRunner CreateRunner() + private static IRunner CreateRunner(Func deploymentFactory) { // Serilog.Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console().CreateLogger(); @@ -214,7 +214,7 @@ namespace Pulumi throw new NotSupportedException("Deployment.Run can only be called a single time."); Serilog.Log.Debug("Creating new Deployment."); - var deployment = new Deployment(); + var deployment = deploymentFactory(); Instance = new DeploymentInstance(deployment); return deployment._runner; } diff --git a/sdk/dotnet/Pulumi/Deployment/IRunner.cs b/sdk/dotnet/Pulumi/Deployment/IRunner.cs index 78575f391..6137b0aa4 100644 --- a/sdk/dotnet/Pulumi/Deployment/IRunner.cs +++ b/sdk/dotnet/Pulumi/Deployment/IRunner.cs @@ -11,6 +11,7 @@ namespace Pulumi void RegisterTask(string description, Task task); Task RunAsync(Func>> func, StackOptions? options); Task RunAsync() where TStack : Stack, new(); + Task RunAsync(Func stackFactory) where TStack : Stack; Task RunAsync(IServiceProvider serviceProvider) where TStack : Stack; } } diff --git a/sdk/dotnet/Pulumi/Deployment/InlineDeploymentSettings.cs b/sdk/dotnet/Pulumi/Deployment/InlineDeploymentSettings.cs new file mode 100644 index 000000000..dd50131ce --- /dev/null +++ b/sdk/dotnet/Pulumi/Deployment/InlineDeploymentSettings.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; + +namespace Pulumi +{ + internal class InlineDeploymentSettings + { + public string EngineAddr { get; } + + public string MonitorAddr { get; } + + public IDictionary Config { get; } + + public string Project { get; } + + public string Stack { get; } + + public int Parallel { get; } + + public bool IsDryRun { get; } + + public InlineDeploymentSettings( + string engineAddr, + string monitorAddr, + IDictionary config, + string project, + string stack, + int parallel, + bool isDryRun) + { + EngineAddr = engineAddr; + MonitorAddr = monitorAddr; + Config = config; + Project = project; + Stack = stack; + Parallel = parallel; + IsDryRun = isDryRun; + } + } +} diff --git a/sdk/dotnet/Pulumi/Serialization/ResourcePackages.cs b/sdk/dotnet/Pulumi/Serialization/ResourcePackages.cs index 59c4fc6de..6a911c458 100644 --- a/sdk/dotnet/Pulumi/Serialization/ResourcePackages.cs +++ b/sdk/dotnet/Pulumi/Serialization/ResourcePackages.cs @@ -113,7 +113,10 @@ namespace Pulumi } } - static bool PossibleMatch(AssemblyName? assembly) => assembly != null && !assembly.FullName.StartsWith("System", StringComparison.Ordinal); + static bool PossibleMatch(AssemblyName? assembly) + => assembly != null + && !assembly.FullName.StartsWith("System", StringComparison.Ordinal) + && assembly.ContentType != AssemblyContentType.WindowsRuntime; } } } diff --git a/sdk/dotnet/dotnet.sln b/sdk/dotnet/dotnet.sln index c07a41fae..771e2e1d5 100644 --- a/sdk/dotnet/dotnet.sln +++ b/sdk/dotnet/dotnet.sln @@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pulumi.Automation", "Pulumi.Automation\Pulumi.Automation.csproj", "{74A15689-FB60-4760-99C8-FC0D89883F3D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pulumi.Automation.Tests", "Pulumi.Automation.Tests\Pulumi.Automation.Tests.csproj", "{A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +66,30 @@ Global {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 + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Debug|x64.ActiveCfg = Debug|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Debug|x64.Build.0 = Debug|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Debug|x86.ActiveCfg = Debug|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Debug|x86.Build.0 = Debug|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Release|Any CPU.Build.0 = Release|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Release|x64.ActiveCfg = Release|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Release|x64.Build.0 = Release|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Release|x86.ActiveCfg = Release|Any CPU + {74A15689-FB60-4760-99C8-FC0D89883F3D}.Release|x86.Build.0 = Release|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Debug|x64.Build.0 = Debug|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Debug|x86.ActiveCfg = Debug|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Debug|x86.Build.0 = Debug|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Release|Any CPU.Build.0 = Release|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Release|x64.ActiveCfg = Release|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Release|x64.Build.0 = Release|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Release|x86.ActiveCfg = Release|Any CPU + {A1E69FAC-B6C2-4EA2-8A2D-397536CE35B2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE