Skip to content

Commit

Permalink
Add install command to automation api
Browse files Browse the repository at this point in the history
- Add install command, with relevant version checks
- Add Mock PulumiCommand for tests
  • Loading branch information
JasonWhall committed Dec 12, 2024
1 parent daeaab7 commit 5dd718e
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 10 deletions.
41 changes: 41 additions & 0 deletions sdk/Pulumi.Automation.Tests/LocalWorkspaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<InvalidOperationException>(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<InvalidOperationException>(async () => await workspace.InstallAsync(installOptions));
}
}
}
52 changes: 52 additions & 0 deletions sdk/Pulumi.Automation.Tests/Mocks/PulumiCommandMock.cs
Original file line number Diff line number Diff line change
@@ -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<CommandResult> RunAsync(
IList<string> args,
string workingDir,
IDictionary<string, string?> additionalEnv,
Action<string>? onStandardOutput = null,
Action<string>? onStandardError = null,
Action<EngineEvent>? onEngineEvent = null,
CancellationToken cancellationToken = default)
{
this.RecordedArgs = args;
return Task.FromResult(this.CommandResult);
}

public override Task<CommandResult> RunInputAsync(
IList<string> args,
string workingDir,
IDictionary<string, string?> additionalEnv,
Action<string>? onStandardOutput = null,
Action<string>? onStandardError = null,
string? stdIn = null,
Action<EngineEvent>? onEngineEvent = null,
CancellationToken cancellationToken = default)
{
this.RecordedArgs = args;
return Task.FromResult(this.CommandResult);
}

public override SemVersion? Version { get; }

public IList<string> RecordedArgs { get; private set; } = new List<string>();
}
}
25 changes: 25 additions & 0 deletions sdk/Pulumi.Automation/InstallOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Pulumi.Automation
{
public class InstallOptions
{
/// <summary>
/// Skip installing plugins.
/// </summary>
public bool NoPlugins { get; set; }

/// <summary>
/// Skip installing dependencies.
/// </summary>
public bool NoDependencies { get; set; }

/// <summary>
/// Reinstall a plugin even if it already exists.
/// </summary>
public bool Reinstall { get; set; }

/// <summary>
/// Use language version tools to setup and install the language runtime.
/// </summary>
public bool UseLanguageVersionTools { get; set; }
}
}
55 changes: 45 additions & 10 deletions sdk/Pulumi.Automation/LocalWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -669,15 +669,8 @@ public override async Task<ImmutableDictionary<string, ConfigValue>> RefreshConf
/// <inheritdoc/>
public override async Task<WhoAmIResult> 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);
Expand Down Expand Up @@ -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<string> { "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);
Expand Down Expand Up @@ -978,15 +1007,21 @@ internal IReadOnlyList<string> 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.");
}
}

}
}
28 changes: 28 additions & 0 deletions sdk/Pulumi.Automation/Pulumi.Automation.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions sdk/Pulumi.Automation/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,14 @@ public Task InstallPluginAsync(string name, string version, PluginKind kind, Can
/// <param name="cancellationToken">A cancellation token.</param>
public abstract Task ChangeSecretsProviderAsync(string stackName, string newSecretsProvider, SecretsProviderOptions? secretsProviderOptions = null, CancellationToken cancellationToken = default);

/// <summary>
/// Install packages and plugins for the current program or policy pack.
/// </summary>
/// <param name="options">The options to customize the install.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns></returns>
public abstract Task InstallAsync(InstallOptions? options = null, CancellationToken cancellationToken = default);

internal async Task<CommandResult> RunStackCommandAsync(
string stackName,
IList<string> args,
Expand Down

0 comments on commit 5dd718e

Please sign in to comment.