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); } }
public async Task StackAlreadyExistsExceptionIsThrown() { var projectSettings = new ProjectSettings("command_exception_test", ProjectRuntimeName.NodeJS); using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions { ProjectSettings = projectSettings, }); var stackName = $"already_existing_stack{GetTestSuffix()}"; await workspace.CreateStackAsync(stackName); try { var createTask = workspace.CreateStackAsync(stackName); await Assert.ThrowsAsync <StackAlreadyExistsException>( () => createTask); } finally { await workspace.RemoveStackAsync(stackName); } }
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); }
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); }
public async Task ConcurrentUpdateExceptionIsThrown() { var projectSettings = new ProjectSettings("command_exception_test", ProjectRuntimeName.NodeJS); using var workspace = await LocalWorkspace.CreateAsync(new LocalWorkspaceOptions { ProjectSettings = projectSettings, }); var stackName = $"concurrent_update_stack{GetTestSuffix()}"; await workspace.CreateStackAsync(stackName); try { var stack = await WorkspaceStack.SelectAsync(stackName, workspace); var hitSemaphore = false; using var semaphore = new SemaphoreSlim(0, 1); var program = PulumiFn.Create(() => { hitSemaphore = true; // ReSharper disable once AccessToDisposedClosure semaphore.Wait(); return(new Dictionary <string, object?> { ["test"] = "doesnt matter", }); }); var upTask = stack.UpAsync(new UpOptions { Program = program, }); // wait until we hit semaphore while (!hitSemaphore) { await Task.Delay(TimeSpan.FromSeconds(2)); if (upTask.IsFaulted) { throw upTask.Exception !; } else if (upTask.IsCompleted) { throw new Exception("never hit semaphore in first UP task"); } } // before releasing the semaphore, ensure another up throws var concurrentTask = stack.UpAsync(new UpOptions { Program = program, // should never make it into this }); await Assert.ThrowsAsync <ConcurrentUpdateException>( () => concurrentTask); // finish first up call semaphore.Release(); await upTask; } finally { await workspace.RemoveStackAsync(stackName); } }
static async Task <int> DeployStack( string stack, string name, string tier, string track, string version, string image, int percentage ) { var currentDir = Directory.GetCurrentDirectory(); Information("Starting with {Name} {Stack} in {CurrentDir}", name, stack, currentDir); var customStackPath = Path.Combine(currentDir, "deploy"); var usingCustomStack = Directory.Exists(customStackPath); if (usingCustomStack) { Information("Using the custom stack in {Directory}", customStackPath); } LocalWorkspaceOptions stackArgs = usingCustomStack ? new LocalProgramArgs(stack, customStackPath) : new InlineProgramArgs(name, stack, PulumiFn.Create <DefaultStack>()); using var workspace = await LocalWorkspace.CreateAsync(stackArgs); var appStack = await WorkspaceStack.CreateOrSelectAsync(stack, workspace); await appStack.RefreshAsync(); Information("Configuring stack {Stack}", stack); var appSettings = new AutoDevOpsSettings.AppSettings(name, tier, track, version); var deploymentSettings = await Settings.GetDeploymentSettings(); await appStack.SetJsonConfig("gitlab", Settings.GitLabSettings()); await appStack.SetJsonConfig("registry", Settings.RegistrySettings(), true); await appStack.SetJsonConfig("app", appSettings); await appStack.SetJsonConfig("deploy", Settings.DeploySettings(image, percentage)); await appStack.SetJsonConfig("service", deploymentSettings.Service); await appStack.SetJsonConfig("ingress", deploymentSettings.Ingress); await appStack.SetJsonConfig("prometheus", deploymentSettings.Prometheus); Information("Installing plugins"); await appStack.Workspace.InstallPluginAsync("kubernetes", "v3.3.0"); Information("Deploying stack {Stack}", stack); var result = await appStack.UpAsync( new UpOptions { OnStandardOutput = Information, OnStandardError = Error } ); Information("Deployment result: {Result}", result.Summary.Message); if (!Env.EnvironmentUrl.IsEmpty()) { Information("Environment URL: {EnvironmentUrl}", Env.EnvironmentUrl); } return(result.Summary.Result == UpdateState.Succeeded ? 0 : -1); }
static async Task Main(string[] args) { // define our pulumi program "inline" var program = PulumiFn.Create(() => { // get default vpc var defaultVpc = Output.Create(Pulumi.Aws.Ec2.GetVpc.InvokeAsync(new Pulumi.Aws.Ec2.GetVpcArgs { Default = true, })); // get public subnets var publicSubnetIds = defaultVpc.Apply(vpc => { return(Output.Create(Pulumi.Aws.Ec2.GetSubnetIds.InvokeAsync(new Pulumi.Aws.Ec2.GetSubnetIdsArgs { VpcId = vpc.Id, }))); }); var subnetGroup = new Pulumi.Aws.Rds.SubnetGroup( "db-subnet", new Pulumi.Aws.Rds.SubnetGroupArgs { SubnetIds = publicSubnetIds.Apply(x => x.Ids), }); // make a public security group for our cluster for the migration var securityGroup = new Pulumi.Aws.Ec2.SecurityGroup( "public-security-group", new Pulumi.Aws.Ec2.SecurityGroupArgs { Ingress = new Pulumi.Aws.Ec2.Inputs.SecurityGroupIngressArgs { Protocol = "-1", FromPort = 0, ToPort = 0, CidrBlocks = "0.0.0.0/0" }, Egress = new Pulumi.Aws.Ec2.Inputs.SecurityGroupEgressArgs { Protocol = "-1", FromPort = 0, ToPort = 0, CidrBlocks = "0.0.0.0/0", }, }); // for example, you should change this var dbName = "hellosql"; var dbUser = "******"; var dbPassword = "******"; // provision our db var cluster = new Pulumi.Aws.Rds.Cluster( "db", new Pulumi.Aws.Rds.ClusterArgs { Engine = Pulumi.Aws.Rds.EngineType.AuroraMysql, EngineVersion = "5.7.mysql_aurora.2.03.2", DatabaseName = dbName, MasterUsername = dbUser, MasterPassword = dbPassword, SkipFinalSnapshot = true, DbSubnetGroupName = subnetGroup.Name, VpcSecurityGroupIds = securityGroup.Id, }); var clusterInstance = new Pulumi.Aws.Rds.ClusterInstance( "db-instance", new Pulumi.Aws.Rds.ClusterInstanceArgs { ClusterIdentifier = cluster.ClusterIdentifier, InstanceClass = Pulumi.Aws.Rds.InstanceType.T3_Small, Engine = Pulumi.Aws.Rds.EngineType.AuroraMysql.ToString(), EngineVersion = "5.7.mysql_aurora.2.03.2", PubliclyAccessible = true, DbSubnetGroupName = subnetGroup.Name, }); // export the website url return(new Dictionary <string, object?> { ["host"] = cluster.Endpoint, ["db_name"] = dbName, ["db_user"] = dbUser, ["db_pass"] = dbPassword, }); }); // to destroy our program, we can run "dotnet run destroy" var destroy = args.Any() && args[0] == "destroy"; var projectName = "database_migration_project"; var stackName = "dev"; // create or select a stack matching the specified name and project // this will set up a workspace with everything necessary to run our inline program (program) var stackArgs = new InlineProgramArgs(projectName, stackName, program); var stack = await LocalWorkspace.CreateOrSelectStackAsync(stackArgs); Console.WriteLine("successfully initialized stack"); // for inline programs, we must manage plugins ourselves Console.WriteLine("installing plugins..."); await stack.Workspace.InstallPluginAsync("aws", "v4.24.1"); Console.WriteLine("plugins installed"); // set stack configuration specifying the region to deploy Console.WriteLine("setting up config..."); await stack.SetConfigAsync("aws:region", new ConfigValue("us-west-2")); Console.WriteLine("config set"); Console.WriteLine("refreshing stack..."); await stack.RefreshAsync(new RefreshOptions { OnStandardOutput = Console.WriteLine }); Console.WriteLine("refresh complete"); if (destroy) { Console.WriteLine("destroying stack..."); await stack.DestroyAsync(new DestroyOptions { OnStandardOutput = Console.WriteLine }); Console.WriteLine("stack destroy complete"); return; } Console.WriteLine("updating stack..."); var result = await stack.UpAsync(new UpOptions { OnStandardOutput = Console.WriteLine }); if (result.Summary.ResourceChanges != null) { Console.WriteLine("update summary:"); foreach (var change in result.Summary.ResourceChanges) { Console.WriteLine($" {change.Key}: {change.Value}"); } } Console.WriteLine($"db host url: {result.Outputs["host"].Value}"); Console.WriteLine("configuring db..."); var connectionStr = $"Server={result.Outputs["host"].Value};Database={result.Outputs["db_name"].Value};Uid={result.Outputs["db_user"].Value};Pwd={result.Outputs["db_pass"].Value};"; using var connection = new MySqlConnection(connectionStr); await connection.OpenAsync(); Console.WriteLine("db configured!"); // make sure the table exists const string createTableQuery = @" CREATE TABLE IF NOT EXISTS hello_pulumi( id int(9) NOT NULL PRIMARY KEY, color varchar(14) NOT NULL);"; using var createCommand = new MySqlCommand(createTableQuery, connection); await createCommand.ExecuteNonQueryAsync(); // seed the table with some data to start const string seedTableQuery = @" INSERT IGNORE INTO hello_pulumi (id, color) VALUES (1, 'Purple'), (2, 'Violet'), (3, 'Plum');"; using var seedCommand = new MySqlCommand(seedTableQuery, connection); await seedCommand.ExecuteNonQueryAsync(); Console.WriteLine("rows inserted!"); Console.WriteLine("querying to verify data..."); const string readTableQuery = "SELECT COUNT(*) FROM hello_pulumi;"; using var readCommand = new MySqlCommand(readTableQuery, connection); var readResult = await readCommand.ExecuteScalarAsync(); Console.WriteLine($"Result: {readResult} rows"); Console.WriteLine("database, table, and rows successfully configured"); }
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); } }
static async Task Main(string[] args) { // define our pulumi program "inline" var program = PulumiFn.Create(() => { // create a bucket and expose a website index document var siteBucket = new Pulumi.Aws.S3.Bucket( "s3-website-bucket", new Pulumi.Aws.S3.BucketArgs { Website = new Pulumi.Aws.S3.Inputs.BucketWebsiteArgs { IndexDocument = "index.html", }, }); const string indexContent = @" <html> <head><titl>Hello S3</title><meta charset=""UTF-8""></head> <body> <p>Hello, world!</p> <p>Made with ❤️ with <a href=""https://pulumi.com"">Pulumi</a></p> </body> </html> "; // write our index.html into the site bucket var @object = new Pulumi.Aws.S3.BucketObject( "index", new Pulumi.Aws.S3.BucketObjectArgs { Bucket = siteBucket.BucketName, // reference to the s3 bucket object Content = indexContent, Key = "index.html", // set the key of the object ContentType = "text/html; charset=utf-8", // set the MIME type of the file }); var bucketPolicyDocument = siteBucket.Arn.Apply(bucketArn => { return(Output.Create(Pulumi.Aws.Iam.GetPolicyDocument.InvokeAsync( new Pulumi.Aws.Iam.GetPolicyDocumentArgs { Statements = new List <Pulumi.Aws.Iam.Inputs.GetPolicyDocumentStatementArgs> { new Pulumi.Aws.Iam.Inputs.GetPolicyDocumentStatementArgs { Effect = "Allow", Principals = new List <Pulumi.Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalArgs> { new Pulumi.Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalArgs { Identifiers = new List <string> { "*" }, Type = "AWS", }, }, Actions = new List <string> { "s3:GetObject" }, Resources = new List <string> { $"{bucketArn}/*" }, }, }, }))); }); // set the access policy for the bucket so all objects are readable new Pulumi.Aws.S3.BucketPolicy( "bucket-policy", new Pulumi.Aws.S3.BucketPolicyArgs { Bucket = siteBucket.BucketName, Policy = bucketPolicyDocument.Apply(x => x.Json), }); // export the website url return(new Dictionary <string, object?> { ["secret"] = Pulumi.Output.CreateSecret("hello world"), ["website_url"] = siteBucket.WebsiteEndpoint, }); }); // to destroy our program, we can run "dotnet run destroy" var destroy = args.Any() && args[0] == "destroy"; var projectName = "inline_s3_project"; var stackName = "dev"; var secretsProvider = "awskms://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee?region=us-west-2"; var keyId = System.Environment.GetEnvironmentVariable("KMS_KEY"); if (keyId != null) { secretsProvider = "awskms://" + keyId + "?region=" + System.Environment.GetEnvironmentVariable("AWS_REGION"); } if (secretsProvider == "awskms://aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee?region=us-west-2") { throw new Exception("Update secretsProvider to use an actual AWS KMS key."); } // create or select a stack matching the specified name and project // this will set up a workspace with everything necessary to run our inline program (program) var stackArgs = new InlineProgramArgs(projectName, stackName, program); stackArgs.SecretsProvider = secretsProvider; stackArgs.StackSettings = new Dictionary <string, StackSettings>() { [stackName] = new StackSettings() { SecretsProvider = secretsProvider } }; var stack = await LocalWorkspace.CreateOrSelectStackAsync(stackArgs); Console.WriteLine("successfully initialized stack"); // for inline programs, we must manage plugins ourselves Console.WriteLine("installing plugins..."); await stack.Workspace.InstallPluginAsync("aws", "v4.24.1"); Console.WriteLine("plugins installed"); // set stack configuration specifying the region to deploy Console.WriteLine("setting up config..."); await stack.SetConfigAsync("aws:region", new ConfigValue("us-west-2")); Console.WriteLine("config set"); Console.WriteLine("refreshing stack..."); await stack.RefreshAsync(new RefreshOptions { OnStandardOutput = Console.WriteLine }); Console.WriteLine("refresh complete"); if (destroy) { Console.WriteLine("destroying stack..."); await stack.DestroyAsync(new DestroyOptions { OnStandardOutput = Console.WriteLine }); Console.WriteLine("stack destroy complete"); } else { Console.WriteLine("updating stack..."); var result = await stack.UpAsync(new UpOptions { OnStandardOutput = Console.WriteLine }); if (result.Summary.ResourceChanges != null) { Console.WriteLine("update summary:"); foreach (var change in result.Summary.ResourceChanges) { Console.WriteLine($" {change.Key}: {change.Value}"); } } Console.WriteLine($"website url: {result.Outputs["website_url"].Value}"); } }