647 lines
25 KiB
C#
647 lines
25 KiB
C#
|
// 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<string, string>()
|
|||
|
{
|
|||
|
["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<string, string>()
|
|||
|
{
|
|||
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var stackName = $"int_test{GetTestSuffix()}";
|
|||
|
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);
|
|||
|
|
|||
|
await Assert.ThrowsAsync<CommandException>(
|
|||
|
() => 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<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("node_test", ProjectRuntimeName.NodeJS);
|
|||
|
using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions
|
|||
|
{
|
|||
|
ProjectSettings = projectSettings,
|
|||
|
EnvironmentVariables = new Dictionary<string, string>()
|
|||
|
{
|
|||
|
["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<string, string>()
|
|||
|
{
|
|||
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var config = new Dictionary<string, ConfigValue>()
|
|||
|
{
|
|||
|
["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<string, object?>
|
|||
|
{
|
|||
|
["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<string, string>()
|
|||
|
{
|
|||
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var config = new Dictionary<string, ConfigValue>()
|
|||
|
{
|
|||
|
["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<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>();
|
|||
|
|
|||
|
var stackName = $"int_test{GetTestSuffix()}";
|
|||
|
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),
|
|||
|
};
|
|||
|
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<string, string>()
|
|||
|
{
|
|||
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
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 = $"int_test_{GetTestSuffix()}";
|
|||
|
var program = PulumiFn.Create<FileNotFoundStack>();
|
|||
|
|
|||
|
using var stack = await LocalWorkspace.CreateStackAsync(new InlineProgramArgs(projectName, stackName, program)
|
|||
|
{
|
|||
|
EnvironmentVariables = new Dictionary<string, string>()
|
|||
|
{
|
|||
|
["PULUMI_CONFIG_PASSPHRASE"] = "test",
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
var upTask = stack.UpAsync();
|
|||
|
await Assert.ThrowsAsync<FileNotFoundException>(
|
|||
|
() => 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<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.SetConfigAsync(new Dictionary<string, ConfigValue>()
|
|||
|
{
|
|||
|
["bar"] = new ConfigValue("1"),
|
|||
|
["buzz"] = new ConfigValue("1", isSecret: true),
|
|||
|
});
|
|||
|
|
|||
|
await stackTwo.SetConfigAsync(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);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|