* add runtime stack type pulumi fn implementation * add exception propagation test and update changelog * slight refactor to just bring the identical service provider code into the new PulumiFn implementation. * get tests passing, need service to be registered as transient so that the stack is instantiated each time because pulumi internal rely on instantiation * check version command was failing because of additional white space it wasn't accounting for
1303 lines
51 KiB
C#
1303 lines
51 KiB
C#
// Copyright 2016-2021, Pulumi Corporation
|
|
|
|
using Semver;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Pulumi.Automation.Exceptions;
|
|
using Pulumi.Automation.Commands.Exceptions;
|
|
using Pulumi.Automation.Events;
|
|
using Xunit;
|
|
using System.Collections.Immutable;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace Pulumi.Automation.Tests
|
|
{
|
|
public class LocalWorkspaceTests
|
|
{
|
|
private static readonly string _pulumiOrg = GetTestOrg();
|
|
|
|
private static string GetTestSuffix()
|
|
{
|
|
var random = new Random();
|
|
var result = 100000 + random.Next(0, 900000);
|
|
return result.ToString();
|
|
}
|
|
|
|
private static string RandomStackName()
|
|
{
|
|
const string chars = "abcdefghijklmnopqrstuvwxyz";
|
|
return new string(Enumerable.Range(1, 8).Select(_ => chars[new Random().Next(chars.Length)]).ToArray());
|
|
}
|
|
|
|
private static string GetTestOrg() =>
|
|
Environment.GetEnvironmentVariable("PULUMI_TEST_ORG") ?? "pulumi-test";
|
|
|
|
private static string FullyQualifiedStackName(string org, string project, string stack) =>
|
|
$"{org}/{project}/{stack}";
|
|
|
|
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 = ResourcePath(Path.Combine("Data", 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 = ResourcePath(Path.Combine("Data", 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("create_select_remove_stack_test", ProjectRuntimeName.NodeJS);
|
|
using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions
|
|
{
|
|
ProjectSettings = projectSettings,
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
|
|
var stacks = await workspace.ListStacksAsync();
|
|
if (stacks.Any(s => s.Name == stackName))
|
|
{
|
|
await workspace.RemoveStackAsync(stackName);
|
|
stacks = await workspace.ListStacksAsync();
|
|
Assert.DoesNotContain(stacks, s => s.Name == stackName);
|
|
}
|
|
|
|
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.DoesNotContain(stacks, s => s.Name == stackName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ImportExportStack()
|
|
{
|
|
var workingDir = ResourcePath(Path.Combine("Data", "testproj"));
|
|
var projectSettings = new ProjectSettings("testproj", ProjectRuntimeName.Go)
|
|
{
|
|
Description = "A minimal Go Pulumi program"
|
|
};
|
|
using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions
|
|
{
|
|
WorkDir = workingDir,
|
|
ProjectSettings = projectSettings
|
|
});
|
|
|
|
StackDeployment deployment;
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
try
|
|
{
|
|
var stack = await WorkspaceStack.CreateAsync(stackName, workspace);
|
|
|
|
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);
|
|
|
|
deployment = await workspace.ExportStackAsync(stackName);
|
|
Assert.True(deployment.Version > 0);
|
|
|
|
var previewBeforeDestroy = await stack.PreviewAsync();
|
|
Assert.Equal(1, previewBeforeDestroy.ChangeSummary[OperationType.Same]);
|
|
|
|
await stack.DestroyAsync();
|
|
|
|
var previewAfterDestroy = await stack.PreviewAsync();
|
|
Assert.Equal(1, previewAfterDestroy.ChangeSummary[OperationType.Create]);
|
|
|
|
await workspace.ImportStackAsync(stackName, deployment);
|
|
|
|
// After we imported before-destroy deployment,
|
|
// preview is back to reporting the before-destroy
|
|
// state.
|
|
|
|
var previewAfterImport = await stack.PreviewAsync();
|
|
Assert.Equal(1, previewAfterImport.ChangeSummary[OperationType.Same]);
|
|
}
|
|
finally
|
|
{
|
|
await workspace.RemoveStackAsync(stackName);
|
|
}
|
|
}
|
|
|
|
|
|
[Fact]
|
|
public async Task ManipulateConfig()
|
|
{
|
|
var projectName = "manipulate_config_test";
|
|
var projectSettings = new ProjectSettings(projectName, ProjectRuntimeName.NodeJS);
|
|
|
|
using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions
|
|
{
|
|
ProjectSettings = projectSettings,
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var stack = await WorkspaceStack.CreateAsync(stackName, workspace);
|
|
|
|
var config = new Dictionary<string, ConfigValue>()
|
|
{
|
|
["plain"] = new ConfigValue("abc"),
|
|
["secret"] = new ConfigValue("def", isSecret: true),
|
|
};
|
|
|
|
var plainKey = NormalizeConfigKey("plain", projectName);
|
|
var secretKey = NormalizeConfigKey("secret", projectName);
|
|
|
|
try
|
|
{
|
|
await Assert.ThrowsAsync<CommandException>(
|
|
() => stack.GetConfigAsync(plainKey));
|
|
|
|
var values = await stack.GetAllConfigAsync();
|
|
Assert.Empty(values);
|
|
|
|
await stack.SetAllConfigAsync(config);
|
|
values = await stack.GetAllConfigAsync();
|
|
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);
|
|
|
|
// Get individual configuration values
|
|
plainValue = await stack.GetConfigAsync(plainKey);
|
|
Assert.Equal("abc", plainValue!.Value);
|
|
Assert.False(plainValue.IsSecret);
|
|
|
|
secretValue = await stack.GetConfigAsync(secretKey);
|
|
Assert.Equal("def", secretValue!.Value);
|
|
Assert.True(secretValue.IsSecret);
|
|
|
|
await stack.RemoveConfigAsync("plain");
|
|
values = await stack.GetAllConfigAsync();
|
|
Assert.Single(values);
|
|
|
|
await stack.SetConfigAsync("foo", new ConfigValue("bar"));
|
|
values = await stack.GetAllConfigAsync();
|
|
Assert.Equal(2, values.Count);
|
|
}
|
|
finally
|
|
{
|
|
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<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var stackNames = new List<string>();
|
|
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("check_stack_status_test", ProjectRuntimeName.NodeJS);
|
|
using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions
|
|
{
|
|
ProjectSettings = projectSettings,
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
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 = $"{RandomStackName()}";
|
|
var workingDir = ResourcePath(Path.Combine("Data", "testproj"));
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new LocalProgramArgs(stackName, workingDir)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var config = new Dictionary<string, ConfigValue>()
|
|
{
|
|
["bar"] = new ConfigValue("abc"),
|
|
["buzz"] = new ConfigValue("secret", isSecret: true),
|
|
};
|
|
try
|
|
{
|
|
await stack.SetAllConfigAsync(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
|
|
var previewResult = await stack.PreviewAsync();
|
|
Assert.True(previewResult.ChangeSummary.TryGetValue(OperationType.Same, out var sameCount));
|
|
Assert.Equal(1, sameCount);
|
|
|
|
// 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);
|
|
}
|
|
finally
|
|
{
|
|
await stack.Workspace.RemoveStackAsync(stackName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StackLifecycleInlineProgram()
|
|
{
|
|
var program = PulumiFn.Create(() =>
|
|
{
|
|
var config = new Pulumi.Config();
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["exp_static"] = "foo",
|
|
["exp_cfg"] = config.Get("bar"),
|
|
["exp_secret"] = config.GetSecret("buzz"),
|
|
};
|
|
});
|
|
Assert.IsType<PulumiFnInline>(program);
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var projectName = "inline_node";
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var config = new Dictionary<string, ConfigValue>()
|
|
{
|
|
["bar"] = new ConfigValue("abc"),
|
|
["buzz"] = new ConfigValue("secret", isSecret: true),
|
|
};
|
|
try
|
|
{
|
|
await stack.SetAllConfigAsync(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
|
|
var previewResult = await stack.PreviewAsync();
|
|
Assert.True(previewResult.ChangeSummary.TryGetValue(OperationType.Same, out var sameCount));
|
|
Assert.Equal(1, sameCount);
|
|
|
|
// 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);
|
|
}
|
|
finally
|
|
{
|
|
await stack.Workspace.RemoveStackAsync(stackName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SupportsStackOutputs()
|
|
{
|
|
var program = PulumiFn.Create(() =>
|
|
{
|
|
var config = new Config();
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["exp_static"] = "foo",
|
|
["exp_cfg"] = config.Get("bar"),
|
|
["exp_secret"] = config.GetSecret("buzz"),
|
|
};
|
|
});
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var projectName = "inline_node";
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var config = new Dictionary<string, ConfigValue>()
|
|
{
|
|
["bar"] = new ConfigValue("abc"),
|
|
["buzz"] = new ConfigValue("secret", isSecret: true),
|
|
};
|
|
|
|
try
|
|
{
|
|
await stack.SetAllConfigAsync(config);
|
|
|
|
var initialOutputs = await stack.GetOutputsAsync();
|
|
Assert.Empty(initialOutputs);
|
|
|
|
// pulumi up
|
|
var upResult = await stack.UpAsync();
|
|
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
|
AssertOutputs(upResult.Outputs);
|
|
|
|
var outputsAfterUp = await stack.GetOutputsAsync();
|
|
AssertOutputs(outputsAfterUp);
|
|
|
|
// pulumi destroy
|
|
var destroyResult = await stack.DestroyAsync();
|
|
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
|
|
|
var outputsAfterDestroy = await stack.GetOutputsAsync();
|
|
Assert.Empty(outputsAfterDestroy);
|
|
}
|
|
finally
|
|
{
|
|
await stack.Workspace.RemoveStackAsync(stack.Name);
|
|
}
|
|
|
|
static void AssertOutputs(IImmutableDictionary<string, OutputValue> outputs)
|
|
{
|
|
Assert.Equal(3, outputs.Count);
|
|
|
|
// exp_static
|
|
Assert.True(outputs.TryGetValue("exp_static", out var expStaticValue));
|
|
Assert.Equal("foo", expStaticValue!.Value);
|
|
Assert.False(expStaticValue.IsSecret);
|
|
|
|
// exp_cfg
|
|
Assert.True(outputs.TryGetValue("exp_cfg", out var expConfigValue));
|
|
Assert.Equal("abc", expConfigValue!.Value);
|
|
Assert.False(expConfigValue.IsSecret);
|
|
|
|
// exp_secret
|
|
Assert.True(outputs.TryGetValue("exp_secret", out var expSecretValue));
|
|
Assert.Equal("secret", expSecretValue!.Value);
|
|
Assert.True(expSecretValue.IsSecret);
|
|
}
|
|
}
|
|
|
|
[Fact(Skip = "Breaking builds")]
|
|
public async Task StackReferenceDestroyDiscardsWithTwoInlinePrograms()
|
|
{
|
|
var programA = PulumiFn.Create(() =>
|
|
{
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["exp_static"] = "foo",
|
|
};
|
|
});
|
|
|
|
var programB = PulumiFn.Create(() =>
|
|
{
|
|
var config = new Config();
|
|
var stackRef = new StackReference(config.Require("Ref"));
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["exp_static"] = stackRef.GetOutput("exp_static"),
|
|
};
|
|
});
|
|
|
|
var stackNameA = $"{RandomStackName()}";
|
|
var stackNameB = $"{RandomStackName()}";
|
|
var projectName = "inline_stack_reference";
|
|
|
|
var stackA = await SetupStack(projectName, stackNameA, programA, new Dictionary<string, ConfigValue>());
|
|
|
|
var stackB = await SetupStack(projectName, stackNameB, programB, new Dictionary<string, ConfigValue>()
|
|
{
|
|
["Ref"] = new ConfigValue(FullyQualifiedStackName(_pulumiOrg, projectName, stackNameA)),
|
|
});
|
|
|
|
try
|
|
{
|
|
// Update the first stack
|
|
{
|
|
var upResult = await stackA.UpAsync();
|
|
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
|
Assert.Equal(1, upResult.Outputs.Count);
|
|
|
|
// exp_static
|
|
Assert.True(upResult.Outputs.TryGetValue("exp_static", out var expStaticValue));
|
|
Assert.Equal("foo", expStaticValue!.Value);
|
|
Assert.False(expStaticValue.IsSecret);
|
|
}
|
|
|
|
// Update the second stack which references the first
|
|
{
|
|
var upResult = await stackB.UpAsync();
|
|
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
|
Assert.Equal(1, upResult.Outputs.Count);
|
|
|
|
// exp_static
|
|
Assert.True(upResult.Outputs.TryGetValue("exp_static", out var expStaticValue));
|
|
Assert.Equal("foo", expStaticValue!.Value);
|
|
Assert.False(expStaticValue.IsSecret);
|
|
}
|
|
|
|
// Destroy stacks in reverse order
|
|
{
|
|
var destroyResult = await stackB.DestroyAsync();
|
|
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
|
}
|
|
|
|
{
|
|
var destroyResult = await stackA.DestroyAsync();
|
|
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
|
}
|
|
}
|
|
// Ensure stacks are deleted even if some of the operations fail
|
|
finally
|
|
{
|
|
await stackA.Workspace.RemoveStackAsync(stackNameA);
|
|
await stackB.Workspace.RemoveStackAsync(stackNameB);
|
|
}
|
|
|
|
static async Task<WorkspaceStack> SetupStack(string project, string stackName, PulumiFn program, Dictionary<string, ConfigValue> configMap)
|
|
{
|
|
var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(project, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
await stack.SetAllConfigAsync(configMap);
|
|
|
|
return stack;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OutputStreamAndDelegateIsWritten()
|
|
{
|
|
var program = PulumiFn.Create(() =>
|
|
{
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["test"] = "test",
|
|
};
|
|
});
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var projectName = "inline_output";
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
try
|
|
{
|
|
var outputCalled = false;
|
|
|
|
// pulumi preview
|
|
outputCalled = false;
|
|
var previewResult = await stack.PreviewAsync(new PreviewOptions { OnStandardOutput = (str) => outputCalled = true });
|
|
Assert.False(string.IsNullOrEmpty(previewResult.StandardOutput));
|
|
Assert.True(outputCalled);
|
|
|
|
// pulumi up
|
|
outputCalled = false;
|
|
var upResult = await stack.UpAsync(new UpOptions { OnStandardOutput = (str) => outputCalled = true });
|
|
Assert.False(string.IsNullOrEmpty(upResult.StandardOutput));
|
|
Assert.True(outputCalled);
|
|
|
|
// pulumi refresh
|
|
outputCalled = false;
|
|
var refreshResult = await stack.RefreshAsync(new RefreshOptions { OnStandardOutput = (str) => outputCalled = true });
|
|
Assert.False(string.IsNullOrEmpty(refreshResult.StandardOutput));
|
|
Assert.True(outputCalled);
|
|
|
|
// pulumi destroy
|
|
outputCalled = false;
|
|
var destroyResult = await stack.DestroyAsync(new DestroyOptions { OnStandardOutput = (str) => outputCalled = true });
|
|
Assert.False(string.IsNullOrEmpty(destroyResult.StandardOutput));
|
|
Assert.True(outputCalled);
|
|
}
|
|
finally
|
|
{
|
|
await stack.Workspace.RemoveStackAsync(stack.Name);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandlesEvents()
|
|
{
|
|
var program = PulumiFn.Create(() =>
|
|
{
|
|
return new Dictionary<string, object?>
|
|
{
|
|
["exp_static"] = "foo",
|
|
};
|
|
});
|
|
var projectName = "event_test";
|
|
var stackName = $"inline_events{GetTestSuffix()}";
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
try
|
|
{
|
|
// pulumi preview
|
|
var previewResult = await RunCommand<PreviewResult, PreviewOptions>(stack.PreviewAsync, "preview");
|
|
Assert.True(previewResult.ChangeSummary.TryGetValue(OperationType.Create, out var createCount));
|
|
Assert.Equal(1, createCount);
|
|
|
|
// pulumi up
|
|
var upResult = await RunCommand<UpResult, UpOptions>(stack.UpAsync, "up");
|
|
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
|
|
|
|
// pulumi preview
|
|
var previewResultAgain = await RunCommand<PreviewResult, PreviewOptions>(stack.PreviewAsync, "preview");
|
|
Assert.True(previewResultAgain.ChangeSummary.TryGetValue(OperationType.Same, out var sameCount));
|
|
Assert.Equal(1, sameCount);
|
|
|
|
// pulumi refresh
|
|
var refreshResult = await RunCommand<UpdateResult, RefreshOptions>(stack.RefreshAsync, "refresh");
|
|
Assert.Equal(UpdateKind.Refresh, refreshResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, refreshResult.Summary.Result);
|
|
|
|
// pulumi destroy
|
|
var destroyResult = await RunCommand<UpdateResult, DestroyOptions>(stack.DestroyAsync, "destroy");
|
|
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
|
|
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
|
|
}
|
|
finally
|
|
{
|
|
await stack.Workspace.RemoveStackAsync(stackName);
|
|
}
|
|
|
|
static async Task<T> RunCommand<T, TOptions>(Func<TOptions, CancellationToken, Task<T>> func, string command)
|
|
where TOptions : UpdateOptions, new()
|
|
{
|
|
var events = new List<EngineEvent>();
|
|
|
|
var result = await func(new TOptions() { OnEvent = events.Add }, CancellationToken.None);
|
|
|
|
var seenSummaryEvent = events.Any(@event => @event.SummaryEvent != null);
|
|
var seenCancelEvent = events.Any(@event => @event.CancelEvent != null);
|
|
|
|
Assert.True(events.Any(), $"No Events found for '{command}'");
|
|
Assert.True(events.SequenceEqual(events.OrderBy(@event => @event.Sequence)), $"Events should be received in the sequence order for '{command}'");
|
|
Assert.True(seenSummaryEvent, $"No SummaryEvent for '{command}'");
|
|
Assert.True(seenCancelEvent, $"No CancelEvent for '{command}'");
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private class ValidStack : Stack
|
|
{
|
|
[Output("exp_static")]
|
|
public Output<string> ExpStatic { get; set; }
|
|
|
|
[Output("exp_cfg")]
|
|
public Output<string> ExpConfig { get; set; }
|
|
|
|
[Output("exp_secret")]
|
|
public Output<string> 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<ValidStack>();
|
|
Assert.IsType<PulumiFn<ValidStack>>(program);
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var projectName = "inline_tstack_node";
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var config = new Dictionary<string, ConfigValue>()
|
|
{
|
|
["bar"] = new ConfigValue("abc"),
|
|
["buzz"] = new ConfigValue("secret", isSecret: true),
|
|
};
|
|
try
|
|
{
|
|
await stack.SetAllConfigAsync(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
|
|
var previewResult = await stack.PreviewAsync();
|
|
Assert.True(previewResult.ChangeSummary.TryGetValue(OperationType.Same, out var sameCount));
|
|
Assert.Equal(1, sameCount);
|
|
|
|
// 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);
|
|
}
|
|
finally
|
|
{
|
|
await stack.Workspace.RemoveStackAsync(stackName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StackLifecycleInlineProgramWithServiceProvider()
|
|
{
|
|
using var provider = new ServiceCollection()
|
|
.AddTransient<ValidStack>() // must be transient so it is instantiated each time
|
|
.BuildServiceProvider();
|
|
|
|
var program = PulumiFn.Create<ValidStack>(provider);
|
|
Assert.IsType<PulumiFnServiceProvider>(program);
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var projectName = "inline_serviceprovider_node";
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var config = new Dictionary<string, ConfigValue>()
|
|
{
|
|
["bar"] = new ConfigValue("abc"),
|
|
["buzz"] = new ConfigValue("secret", isSecret: true),
|
|
};
|
|
try
|
|
{
|
|
await stack.SetAllConfigAsync(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
|
|
var previewResult = await stack.PreviewAsync();
|
|
Assert.True(previewResult.ChangeSummary.TryGetValue(OperationType.Same, out var sameCount));
|
|
Assert.Equal(1, sameCount);
|
|
|
|
// 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);
|
|
}
|
|
finally
|
|
{
|
|
await stack.Workspace.RemoveStackAsync(stackName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InlineProgramExceptionPropagatesToCaller()
|
|
{
|
|
const string projectName = "exception_inline_node";
|
|
var stackName = $"{RandomStackName()}";
|
|
var program = PulumiFn.Create((Action)(() => throw new FileNotFoundException()));
|
|
Assert.IsType<PulumiFnInline>(program);
|
|
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var previewTask = stack.PreviewAsync();
|
|
await Assert.ThrowsAsync<FileNotFoundException>(
|
|
() => previewTask);
|
|
|
|
var upTask = stack.UpAsync();
|
|
await Assert.ThrowsAsync<FileNotFoundException>(
|
|
() => 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 = $"{RandomStackName()}";
|
|
var program = PulumiFn.Create<FileNotFoundStack>();
|
|
Assert.IsType<PulumiFn<FileNotFoundStack>>(program);
|
|
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var previewTask = stack.PreviewAsync();
|
|
await Assert.ThrowsAsync<FileNotFoundException>(
|
|
() => previewTask);
|
|
|
|
var upTask = stack.UpAsync();
|
|
await Assert.ThrowsAsync<FileNotFoundException>(
|
|
() => upTask);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InlineProgramExceptionPropagatesToCallerWithServiceProvider()
|
|
{
|
|
using var provider = new ServiceCollection()
|
|
.AddTransient<FileNotFoundStack>() // must be transient so it is instantiated each time
|
|
.BuildServiceProvider();
|
|
|
|
const string projectName = "exception_inline_serviceprovider_node";
|
|
var stackName = $"{RandomStackName()}";
|
|
var program = PulumiFn.Create<FileNotFoundStack>(provider);
|
|
Assert.IsType<PulumiFnServiceProvider>(program);
|
|
|
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
var previewTask = stack.PreviewAsync();
|
|
await Assert.ThrowsAsync<FileNotFoundException>(
|
|
() => previewTask);
|
|
|
|
var upTask = stack.UpAsync();
|
|
await Assert.ThrowsAsync<FileNotFoundException>(
|
|
() => upTask);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InlineProgramAllowsParallelExecution()
|
|
{
|
|
const string projectNameOne = "parallel_inline_node1";
|
|
const string projectNameTwo = "parallel_inline_node2";
|
|
var stackNameOne = $"{RandomStackName()}";
|
|
var stackNameTwo = $"{RandomStackName()}";
|
|
|
|
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<string, object?>
|
|
{
|
|
["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<string, object?>
|
|
{
|
|
["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<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
using var stackTwo = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectNameTwo, stackNameTwo, programTwo)
|
|
{
|
|
EnvironmentVariables = new Dictionary<string, string?>()
|
|
{
|
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|
}
|
|
});
|
|
|
|
await stackOne.SetAllConfigAsync(new Dictionary<string, ConfigValue>()
|
|
{
|
|
["bar"] = new ConfigValue("1"),
|
|
["buzz"] = new ConfigValue("1", isSecret: true),
|
|
});
|
|
|
|
await stackTwo.SetAllConfigAsync(new Dictionary<string, ConfigValue>()
|
|
{
|
|
["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);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WorkspaceStackSupportsCancel()
|
|
{
|
|
var workingDir = ResourcePath(Path.Combine("Data", "testproj"));
|
|
using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions
|
|
{
|
|
WorkDir = workingDir
|
|
});
|
|
var stackName = "teststack";
|
|
var stack = await WorkspaceStack.CreateAsync(stackName, workspace);
|
|
try
|
|
{
|
|
await stack.UpAsync();
|
|
|
|
// Race destroy and cancel. We do not know for sure
|
|
// which one will win.
|
|
var destroyTask = stack.DestroyAsync();
|
|
|
|
// Empirically 1s delay makes cancel task win
|
|
// sometimes. YMMV.
|
|
await Task.Delay(1000);
|
|
|
|
var cancelTask = stack.CancelAsync();
|
|
|
|
try
|
|
{
|
|
Task.WaitAll(new Task[] { destroyTask, cancelTask });
|
|
}
|
|
catch (AggregateException)
|
|
{
|
|
}
|
|
|
|
if (destroyTask.IsFaulted && !cancelTask.IsFaulted)
|
|
{
|
|
// This is what we want to happen at least
|
|
// sometimes to test Cancel functionality.
|
|
}
|
|
else if (cancelTask.IsFaulted && !destroyTask.IsFaulted)
|
|
{
|
|
// This may happen if destroyTask wins the race.
|
|
}
|
|
else
|
|
{
|
|
// This should not happen.
|
|
Assert.True(destroyTask.IsFaulted, "destroyTask must fail");
|
|
Assert.True(!cancelTask.IsFaulted, "cancelTask must not fail");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
await workspace.RemoveStackAsync(stackName);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PulumiVersionTest()
|
|
{
|
|
using var workspace = await LocalWorkspace.CreateAsync();
|
|
Assert.Matches("(\\d+\\.)(\\d+\\.)(\\d+)(-.*)?", workspace.PulumiVersion);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("100.0.0", true, false)]
|
|
[InlineData("1.0.0", true, false)]
|
|
[InlineData("2.22.0", false, false)]
|
|
[InlineData("2.1.0", true, false)]
|
|
[InlineData("2.21.2", false, false)]
|
|
[InlineData("2.21.1", false, false)]
|
|
[InlineData("2.21.0", true, false)]
|
|
// Note that prerelease < release so this case should error
|
|
[InlineData("2.21.1-alpha.1234", true, false)]
|
|
[InlineData("2.20.0", false, true)]
|
|
[InlineData("2.22.0", false, true)]
|
|
public void ValidVersionTheory(string currentVersion, bool errorExpected, bool optOut)
|
|
{
|
|
var testMinVersion = SemVersion.Parse("2.21.1");
|
|
if (errorExpected)
|
|
{
|
|
Action act = () => LocalWorkspace.ValidatePulumiVersion(testMinVersion, currentVersion, optOut);
|
|
Assert.Throws<InvalidOperationException>(act);
|
|
}
|
|
else
|
|
{
|
|
LocalWorkspace.ValidatePulumiVersion(testMinVersion, currentVersion, optOut);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RespectsProjectSettingsTest()
|
|
{
|
|
var program = PulumiFn.Create<ValidStack>();
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var projectName = "project_was_overwritten";
|
|
|
|
var workdir = ResourcePath(Path.Combine("Data", "correct_project"));
|
|
|
|
var stack = await LocalWorkspace.CreateStackAsync(
|
|
new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
WorkDir = workdir
|
|
});
|
|
|
|
var settings = await stack.Workspace.GetProjectSettingsAsync();
|
|
Assert.Equal("correct_project", settings!.Name);
|
|
Assert.Equal("This is a description", settings.Description);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DetectsProjectSettingConflictTest()
|
|
{
|
|
var program = PulumiFn.Create<ValidStack>();
|
|
|
|
var stackName = $"{RandomStackName()}";
|
|
var projectName = "project_was_overwritten";
|
|
|
|
var workdir = ResourcePath(Path.Combine("Data", "correct_project"));
|
|
|
|
var projectSettings = ProjectSettings.Default(projectName);
|
|
projectSettings.Description = "non-standard description";
|
|
|
|
await Assert.ThrowsAsync<ProjectSettingsConflictException>(() =>
|
|
LocalWorkspace.CreateStackAsync(
|
|
new InlineProgramArgs(projectName, stackName, program)
|
|
{
|
|
WorkDir = workdir,
|
|
ProjectSettings = projectSettings
|
|
})
|
|
);
|
|
}
|
|
|
|
private string ResourcePath(string path, [CallerFilePath] string pathBase = "LocalWorkspaceTests.cs")
|
|
{
|
|
var dir = Path.GetDirectoryName(pathBase) ?? ".";
|
|
return Path.Combine(dir, path);
|
|
}
|
|
}
|
|
}
|