From 5dd718ef536f4dc7c9346f4139d4b4cb65368730 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] 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 ee45bad8..4fc9ad77 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 e799e1b8..a294fcfa 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. @@ -1855,6 +1875,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,