From fad8857fa8fd44129c669dbd786349056e1bc6aa Mon Sep 17 00:00:00 2001 From: JasonWhall <42138928+JasonWhall@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:11:32 +0000 Subject: [PATCH 1/3] Add install command to automation api - Add install command, with relevant version checks - Add Mock PulumiCommand for tests --- .../LocalWorkspaceTests.cs | 41 ++++++++++++++ .../Mocks/PulumiCommandMock.cs | 52 ++++++++++++++++++ sdk/Pulumi.Automation/InstallOptions.cs | 25 +++++++++ sdk/Pulumi.Automation/LocalWorkspace.cs | 55 +++++++++++++++---- sdk/Pulumi.Automation/Pulumi.Automation.xml | 28 ++++++++++ sdk/Pulumi.Automation/Workspace.cs | 8 +++ 6 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 sdk/Pulumi.Automation.Tests/Mocks/PulumiCommandMock.cs create mode 100644 sdk/Pulumi.Automation/InstallOptions.cs diff --git a/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs b/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs index dfad438d..60982c1e 100644 --- a/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs +++ b/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs @@ -22,6 +22,9 @@ using ILogger = Microsoft.Extensions.Logging.ILogger; using static Pulumi.Automation.Tests.Utility; +using Pulumi.Automation.Tests.Mocks; +using Pulumi.Automation.Commands; +using Semver; namespace Pulumi.Automation.Tests { @@ -2402,5 +2405,43 @@ public async Task ChangeSecretsProvider() await workspace.RemoveStackAsync(stackName); } } + + [Fact] + public async Task InstallRunsSuccessfully() + { + var mockCommand = new PulumiCommandMock(new SemVersion(3, 130, 0), new CommandResult(0, "", "")); + var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions { PulumiCommand = mockCommand }); + + await workspace.InstallAsync(new InstallOptions + { + NoDependencies = true, + NoPlugins = true, + Reinstall = true, + UseLanguageVersionTools = true + }); + + Assert.Equal(5, mockCommand.RecordedArgs.Count); + Assert.Equal("install", mockCommand.RecordedArgs[0]); + } + + [Fact] + public async Task InstallRequiresSupportedVersion() + { + var mockCommand = new PulumiCommandMock(new SemVersion(3, 0, 0), new CommandResult(0, "", "")); + var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions { PulumiCommand = mockCommand }); + + await Assert.ThrowsAsync(async () => await workspace.InstallAsync()); + } + + [Fact] + public async Task InstallLanguageVersionToolsRequiresSupportedVersion() + { + var mockCommand = new PulumiCommandMock(new SemVersion(3, 91, 0), new CommandResult(0, "", "")); + var installOptions = new InstallOptions { UseLanguageVersionTools = true }; + + var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions { PulumiCommand = mockCommand }); + + await Assert.ThrowsAsync(async () => await workspace.InstallAsync(installOptions)); + } } } diff --git a/sdk/Pulumi.Automation.Tests/Mocks/PulumiCommandMock.cs b/sdk/Pulumi.Automation.Tests/Mocks/PulumiCommandMock.cs new file mode 100644 index 00000000..3b7ab784 --- /dev/null +++ b/sdk/Pulumi.Automation.Tests/Mocks/PulumiCommandMock.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Pulumi.Automation.Events; +using Pulumi.Automation.Commands; +using Semver; + +namespace Pulumi.Automation.Tests.Mocks +{ + class PulumiCommandMock : PulumiCommand + { + private readonly CommandResult CommandResult; + + public PulumiCommandMock(SemVersion version, CommandResult commandResult) + { + this.Version = version; + this.CommandResult = commandResult; + } + + public override Task RunAsync( + IList args, + string workingDir, + IDictionary additionalEnv, + Action? onStandardOutput = null, + Action? onStandardError = null, + Action? onEngineEvent = null, + CancellationToken cancellationToken = default) + { + this.RecordedArgs = args; + return Task.FromResult(this.CommandResult); + } + + public override Task RunInputAsync( + IList args, + string workingDir, + IDictionary additionalEnv, + Action? onStandardOutput = null, + Action? onStandardError = null, + string? stdIn = null, + Action? onEngineEvent = null, + CancellationToken cancellationToken = default) + { + this.RecordedArgs = args; + return Task.FromResult(this.CommandResult); + } + + public override SemVersion? Version { get; } + + public IList RecordedArgs { get; private set; } = new List(); + } +} diff --git a/sdk/Pulumi.Automation/InstallOptions.cs b/sdk/Pulumi.Automation/InstallOptions.cs new file mode 100644 index 00000000..ad2e0d37 --- /dev/null +++ b/sdk/Pulumi.Automation/InstallOptions.cs @@ -0,0 +1,25 @@ +namespace Pulumi.Automation +{ + public class InstallOptions + { + /// + /// Skip installing plugins. + /// + public bool NoPlugins { get; set; } + + /// + /// Skip installing dependencies. + /// + public bool NoDependencies { get; set; } + + /// + /// Reinstall a plugin even if it already exists. + /// + public bool Reinstall { get; set; } + + /// + /// Use language version tools to setup and install the language runtime. + /// + public bool UseLanguageVersionTools { get; set; } + } +} diff --git a/sdk/Pulumi.Automation/LocalWorkspace.cs b/sdk/Pulumi.Automation/LocalWorkspace.cs index 27295b50..7e043923 100644 --- a/sdk/Pulumi.Automation/LocalWorkspace.cs +++ b/sdk/Pulumi.Automation/LocalWorkspace.cs @@ -669,15 +669,8 @@ public override async Task> RefreshConf /// public override async Task WhoAmIAsync(CancellationToken cancellationToken = default) { - var version = _cmd.Version; - if (version == null) - { - // Assume an old version. Doesn't really matter what this is as long as it's pre-3.58. - version = new SemVersion(3, 0); - } - // 3.58 added the --json flag (https://github.com/pulumi/pulumi/releases/tag/v3.58.0) - if (version >= new SemVersion(3, 58)) + if (SupportsCommand(new SemVersion(3, 58))) { // Use the new --json style var result = await this.RunCommandAsync(new[] { "whoami", "--json" }, cancellationToken).ConfigureAwait(false); @@ -866,6 +859,42 @@ public override Task ChangeSecretsProviderAsync(string stackName, string newSecr return this.RunCommandAsync(args, cancellationToken); } + public override Task InstallAsync(InstallOptions? options = default, CancellationToken cancellationToken = default) + { + if (!SupportsCommand(new SemVersion(3, 91, 0))) + { + throw new InvalidOperationException("The Pulumi CLI version does not support the install command. Please update the Pulumi CLI."); + }; + + var args = new List { "install" }; + + if (options is null) + { + return this.RunCommandAsync(args, cancellationToken); + } + + if (options.UseLanguageVersionTools) + { + if (!SupportsCommand(new SemVersion(3, 130, 0))) + { + throw new InvalidOperationException($"The Pulumi CLI version does not support {nameof(options.UseLanguageVersionTools)}. Please update the Pulumi CLI."); + } + + args.Add("--use-language-version-tools"); + } + + if (options.NoDependencies) + args.Add("--no-dependencies"); + + if (options.Reinstall) + args.Add("--reinstall"); + + if (options.NoPlugins) + args.Add("--no-plugins"); + + return this.RunCommandAsync(args, cancellationToken); + } + protected override void Dispose(bool disposing) { base.Dispose(disposing); @@ -978,15 +1007,21 @@ internal IReadOnlyList GetRemoteArgs() return args; } - private void CheckSupportsEnvironmentsCommands() + private bool SupportsCommand(SemVersion minSupportedVersion) { var version = _cmd.Version ?? new SemVersion(3, 0); + return version >= minSupportedVersion; + } + + private void CheckSupportsEnvironmentsCommands() + { // 3.95 added this command (https://github.com/pulumi/pulumi/releases/tag/v3.95.0) - if (version < new SemVersion(3, 95)) + if (!SupportsCommand(new SemVersion(3, 95))) { throw new InvalidOperationException("The Pulumi CLI version does not support env operations on a stack. Please update the Pulumi CLI."); } } + } } diff --git a/sdk/Pulumi.Automation/Pulumi.Automation.xml b/sdk/Pulumi.Automation/Pulumi.Automation.xml index faab4619..b104d315 100644 --- a/sdk/Pulumi.Automation/Pulumi.Automation.xml +++ b/sdk/Pulumi.Automation/Pulumi.Automation.xml @@ -433,6 +433,26 @@ The ID of the resource to import. The format of the ID is specific to the resource type + + + Skip installing plugins. + + + + + Skip installing dependencies. + + + + + Reinstall a plugin even if it already exists. + + + + + Use language version tools to setup and install the language runtime. + + Description of a stack backed by pre-existing local Pulumi CLI program. @@ -1861,6 +1881,14 @@ The options to change the secrets provider. A cancellation token. + + + Install packages and plugins for the current program or policy pack. + + The options to customize the install. + A cancellation token. + + is an isolated, independently configurable instance of a diff --git a/sdk/Pulumi.Automation/Workspace.cs b/sdk/Pulumi.Automation/Workspace.cs index af3959a1..94fc3880 100644 --- a/sdk/Pulumi.Automation/Workspace.cs +++ b/sdk/Pulumi.Automation/Workspace.cs @@ -407,6 +407,14 @@ public Task InstallPluginAsync(string name, string version, PluginKind kind, Can /// A cancellation token. public abstract Task ChangeSecretsProviderAsync(string stackName, string newSecretsProvider, SecretsProviderOptions? secretsProviderOptions = null, CancellationToken cancellationToken = default); + /// + /// Install packages and plugins for the current program or policy pack. + /// + /// The options to customize the install. + /// A cancellation token. + /// + public abstract Task InstallAsync(InstallOptions? options = null, CancellationToken cancellationToken = default); + internal async Task RunStackCommandAsync( string stackName, IList args, From 9a00872830460ad05a73615440f3687e2ddf36e2 Mon Sep 17 00:00:00 2001 From: JasonWhall <42138928+JasonWhall@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:16:16 +0000 Subject: [PATCH 2/3] Add changelog entry --- .changes/unreleased/Improvements-426.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/Improvements-426.yaml diff --git a/.changes/unreleased/Improvements-426.yaml b/.changes/unreleased/Improvements-426.yaml new file mode 100644 index 00000000..88d462ea --- /dev/null +++ b/.changes/unreleased/Improvements-426.yaml @@ -0,0 +1,6 @@ +component: sdk/auto +kind: Improvements +body: Add `pulumi install` to Automation Api +time: 2024-12-10T23:15:49.339423812Z +custom: + PR: "426" From 8a5d26abd7e612e22fdc6f538455e411d3c6193f Mon Sep 17 00:00:00 2001 From: Fraser Waters Date: Thu, 27 Feb 2025 10:57:03 +0000 Subject: [PATCH 3/3] format --- sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs b/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs index a56a204d..79975115 100644 --- a/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs +++ b/sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs @@ -2443,7 +2443,7 @@ public async Task TestLifecycleRefresh() await stack.Workspace.RemoveStackAsync(stackName); } } - + [Fact] public async Task InstallRunsSuccessfully() {