From 3d64c1704f67c07d7456342b2f26f292365dc335 Mon Sep 17 00:00:00 2001 From: Tim Spencer <72101647+sei-tspencer@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:03:39 -0500 Subject: [PATCH] add replace resource option (#55) --- ...241210162042_replace_addresses.Designer.cs | 1145 +++++++++++++++++ .../20241210162042_replace_addresses.cs | 33 + .../Migrations/CasterContextModelSnapshot.cs | 6 +- src/Caster.Api/Domain/Models/Run.cs | 9 + .../Domain/Services/TerraformService.cs | 9 +- .../Runs/EventHandlers/RunAddedHandler.cs | 4 +- .../Features/Runs/Requests/Create.cs | 6 + src/Caster.Api/Features/Runs/Run.cs | 5 + 8 files changed, 1212 insertions(+), 5 deletions(-) create mode 100644 src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.Designer.cs create mode 100644 src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.cs diff --git a/src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.Designer.cs b/src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.Designer.cs new file mode 100644 index 0000000..8ecc507 --- /dev/null +++ b/src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.Designer.cs @@ -0,0 +1,1145 @@ +/* +Copyright 2021 Carnegie Mellon University. All Rights Reserved. + Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. +*/ + +// +using System; +using Caster.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Caster.Api.Data.Migrations +{ + [DbContext(typeof(CasterContext))] + [Migration("20241210162042_replace_addresses")] + partial class replace_addresses + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Caster.Api.Domain.Models.Apply", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("Output") + .HasColumnType("text") + .HasColumnName("output"); + + b.Property("RunId") + .HasColumnType("uuid") + .HasColumnName("run_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("RunId") + .IsUnique(); + + b.ToTable("applies"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Design", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("DirectoryId") + .HasColumnType("uuid") + .HasColumnName("directory_id"); + + b.Property("Enabled") + .HasColumnType("boolean") + .HasColumnName("enabled"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryId"); + + b.ToTable("designs"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.DesignModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("DesignId") + .HasColumnType("uuid") + .HasColumnName("design_id"); + + b.Property("Enabled") + .HasColumnType("boolean") + .HasColumnName("enabled"); + + b.Property("ModuleId") + .HasColumnType("uuid") + .HasColumnName("module_id"); + + b.Property("ModuleVersion") + .HasColumnType("text") + .HasColumnName("module_version"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ValuesJson") + .HasColumnType("text") + .HasColumnName("values_json"); + + b.HasKey("Id"); + + b.HasIndex("DesignId"); + + b.HasIndex("ModuleId"); + + b.ToTable("design_modules"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Directory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("AzureDestroyFailureThreshold") + .HasColumnType("integer") + .HasColumnName("azure_destroy_failure_threshold"); + + b.Property("AzureDestroyFailureThresholdEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("azure_destroy_failure_threshold_enabled"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Parallelism") + .HasColumnType("integer") + .HasColumnName("parallelism"); + + b.Property("ParentId") + .HasColumnType("uuid") + .HasColumnName("parent_id"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TerraformVersion") + .HasColumnType("text") + .HasColumnName("terraform_version"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.HasIndex("Path"); + + b.HasIndex("ProjectId"); + + b.ToTable("directories"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("AdministrativelyLocked") + .HasColumnType("boolean") + .HasColumnName("administratively_locked"); + + b.Property("Content") + .HasColumnType("text") + .HasColumnName("content"); + + b.Property("DateSaved") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_saved"); + + b.Property("DirectoryId") + .HasColumnType("uuid") + .HasColumnName("directory_id"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LockedById") + .HasColumnType("uuid") + .HasColumnName("locked_by_id"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkspaceId") + .HasColumnType("uuid") + .HasColumnName("workspace_id"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryId"); + + b.HasIndex("LockedById"); + + b.HasIndex("ModifiedById"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("files"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.FileVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("Content") + .HasColumnType("text") + .HasColumnName("content"); + + b.Property("DateSaved") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_saved"); + + b.Property("DateTagged") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_tagged"); + + b.Property("FileId") + .HasColumnType("uuid") + .HasColumnName("file_id"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Tag") + .HasColumnType("text") + .HasColumnName("tag"); + + b.Property("TaggedById") + .HasColumnType("uuid") + .HasColumnName("tagged_by_id"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.HasIndex("ModifiedById"); + + b.HasIndex("TaggedById"); + + b.ToTable("file_versions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Host", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("Datastore") + .HasColumnType("text") + .HasColumnName("datastore"); + + b.Property("Development") + .HasColumnType("boolean") + .HasColumnName("development"); + + b.Property("Enabled") + .HasColumnType("boolean") + .HasColumnName("enabled"); + + b.Property("MaximumMachines") + .HasColumnType("integer") + .HasColumnName("maximum_machines"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("hosts"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.HostMachine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("HostId") + .HasColumnType("uuid") + .HasColumnName("host_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("WorkspaceId") + .HasColumnType("uuid") + .HasColumnName("workspace_id"); + + b.HasKey("Id"); + + b.HasIndex("HostId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("host_machines"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Module", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_modified"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.HasKey("Id"); + + b.ToTable("modules"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.ModuleVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_created"); + + b.Property("ModuleId") + .HasColumnType("uuid") + .HasColumnName("module_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Outputs") + .HasColumnType("text") + .HasColumnName("outputs"); + + b.Property("UrlLink") + .HasColumnType("text") + .HasColumnName("url_link"); + + b.Property("Variables") + .HasColumnType("text") + .HasColumnName("variables"); + + b.HasKey("Id"); + + b.HasIndex("ModuleId"); + + b.ToTable("module_versions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Partition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasColumnName("is_default"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PoolId") + .HasColumnType("uuid") + .HasColumnName("pool_id"); + + b.HasKey("Id"); + + b.HasIndex("PoolId"); + + b.ToTable("partitions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Key") + .HasColumnType("text") + .HasColumnName("key"); + + b.Property("ReadOnly") + .HasColumnType("boolean") + .HasColumnName("read_only"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id"); + + b.HasIndex("Key", "Value") + .IsUnique(); + + b.ToTable("permissions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Plan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("Output") + .HasColumnType("text") + .HasColumnName("output"); + + b.Property("RunId") + .HasColumnType("uuid") + .HasColumnName("run_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("RunId") + .IsUnique(); + + b.ToTable("plans"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Pool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasColumnName("is_default"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.ToTable("pools"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("PartitionId") + .HasColumnType("uuid") + .HasColumnName("partition_id"); + + b.HasKey("Id"); + + b.HasIndex("PartitionId"); + + b.ToTable("projects"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.RemovedResource", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.HasKey("Id"); + + b.ToTable("removed_resources"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Run", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("IsDestroy") + .HasColumnType("boolean") + .HasColumnName("is_destroy"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("ReplaceAddresses") + .HasColumnType("text") + .HasColumnName("replace_addresses"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.Property("Targets") + .HasColumnType("text") + .HasColumnName("targets"); + + b.Property("WorkspaceId") + .HasColumnType("uuid") + .HasColumnName("workspace_id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ModifiedById"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("runs"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.HasKey("Id"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.UserPermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("PermissionId") + .HasColumnType("uuid") + .HasColumnName("permission_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id"); + + b.HasIndex("PermissionId"); + + b.HasIndex("UserId", "PermissionId") + .IsUnique(); + + b.ToTable("user_permissions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Variable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("DefaultValue") + .HasColumnType("text") + .HasColumnName("default_value"); + + b.Property("DesignId") + .HasColumnType("uuid") + .HasColumnName("design_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.HasKey("Id"); + + b.HasIndex("DesignId"); + + b.ToTable("variables"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Vlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("InUse") + .HasColumnType("boolean") + .HasColumnName("in_use"); + + b.Property("PartitionId") + .HasColumnType("uuid") + .HasColumnName("partition_id"); + + b.Property("PoolId") + .HasColumnType("uuid") + .HasColumnName("pool_id"); + + b.Property("Reserved") + .HasColumnType("boolean") + .HasColumnName("reserved"); + + b.Property("Tag") + .HasColumnType("text") + .HasColumnName("tag"); + + b.Property("VlanId") + .HasColumnType("integer") + .HasColumnName("vlan_id"); + + b.HasKey("Id"); + + b.HasIndex("PartitionId"); + + b.HasIndex("PoolId"); + + b.HasIndex("VlanId"); + + b.ToTable("vlans"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Workspace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("uuid_generate_v4()"); + + b.Property("AzureDestroyFailureThreshold") + .HasColumnType("integer") + .HasColumnName("azure_destroy_failure_threshold"); + + b.Property("DirectoryId") + .HasColumnType("uuid") + .HasColumnName("directory_id"); + + b.Property("DynamicHost") + .HasColumnType("boolean") + .HasColumnName("dynamic_host"); + + b.Property("HostId") + .HasColumnType("uuid") + .HasColumnName("host_id"); + + b.Property("LastSynced") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_synced"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Parallelism") + .HasColumnType("integer") + .HasColumnName("parallelism"); + + b.Property("State") + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("StateBackup") + .HasColumnType("text") + .HasColumnName("state_backup"); + + b.Property("SyncErrors") + .HasColumnType("text") + .HasColumnName("sync_errors"); + + b.Property("TerraformVersion") + .HasColumnType("text") + .HasColumnName("terraform_version"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryId"); + + b.HasIndex("HostId"); + + b.ToTable("workspaces"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Apply", b => + { + b.HasOne("Caster.Api.Domain.Models.Run", "Run") + .WithOne("Apply") + .HasForeignKey("Caster.Api.Domain.Models.Apply", "RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Design", b => + { + b.HasOne("Caster.Api.Domain.Models.Directory", "Directory") + .WithMany("Designs") + .HasForeignKey("DirectoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Directory"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.DesignModule", b => + { + b.HasOne("Caster.Api.Domain.Models.Design", "Design") + .WithMany("Modules") + .HasForeignKey("DesignId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Caster.Api.Domain.Models.Module", "Module") + .WithMany() + .HasForeignKey("ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Design"); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Directory", b => + { + b.HasOne("Caster.Api.Domain.Models.Directory", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Caster.Api.Domain.Models.Project", "Project") + .WithMany("Directories") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Parent"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.File", b => + { + b.HasOne("Caster.Api.Domain.Models.Directory", "Directory") + .WithMany("Files") + .HasForeignKey("DirectoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Caster.Api.Domain.Models.User", "LockedBy") + .WithMany() + .HasForeignKey("LockedById"); + + b.HasOne("Caster.Api.Domain.Models.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById"); + + b.HasOne("Caster.Api.Domain.Models.Workspace", "Workspace") + .WithMany("Files") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Directory"); + + b.Navigation("LockedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.FileVersion", b => + { + b.HasOne("Caster.Api.Domain.Models.File", "File") + .WithMany("FileVersions") + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Caster.Api.Domain.Models.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById"); + + b.HasOne("Caster.Api.Domain.Models.User", "TaggedBy") + .WithMany() + .HasForeignKey("TaggedById"); + + b.Navigation("File"); + + b.Navigation("ModifiedBy"); + + b.Navigation("TaggedBy"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Host", b => + { + b.HasOne("Caster.Api.Domain.Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.HostMachine", b => + { + b.HasOne("Caster.Api.Domain.Models.Host", "Host") + .WithMany("Machines") + .HasForeignKey("HostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Caster.Api.Domain.Models.Workspace", "Workspace") + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Host"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.ModuleVersion", b => + { + b.HasOne("Caster.Api.Domain.Models.Module", "Module") + .WithMany("Versions") + .HasForeignKey("ModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Module"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Partition", b => + { + b.HasOne("Caster.Api.Domain.Models.Pool", "Pool") + .WithMany("Partitions") + .HasForeignKey("PoolId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Pool"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Plan", b => + { + b.HasOne("Caster.Api.Domain.Models.Run", "Run") + .WithOne("Plan") + .HasForeignKey("Caster.Api.Domain.Models.Plan", "RunId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Run"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Project", b => + { + b.HasOne("Caster.Api.Domain.Models.Partition", "Partition") + .WithMany() + .HasForeignKey("PartitionId"); + + b.Navigation("Partition"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Run", b => + { + b.HasOne("Caster.Api.Domain.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("Caster.Api.Domain.Models.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById"); + + b.HasOne("Caster.Api.Domain.Models.Workspace", "Workspace") + .WithMany("Runs") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.UserPermission", b => + { + b.HasOne("Caster.Api.Domain.Models.Permission", "Permission") + .WithMany("UserPermissions") + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Caster.Api.Domain.Models.User", "User") + .WithMany("UserPermissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Variable", b => + { + b.HasOne("Caster.Api.Domain.Models.Design", "Design") + .WithMany("Variables") + .HasForeignKey("DesignId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Design"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Vlan", b => + { + b.HasOne("Caster.Api.Domain.Models.Partition", null) + .WithMany("Vlans") + .HasForeignKey("PartitionId"); + + b.HasOne("Caster.Api.Domain.Models.Pool", null) + .WithMany("Vlans") + .HasForeignKey("PoolId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Workspace", b => + { + b.HasOne("Caster.Api.Domain.Models.Directory", "Directory") + .WithMany("Workspaces") + .HasForeignKey("DirectoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Caster.Api.Domain.Models.Host", "Host") + .WithMany() + .HasForeignKey("HostId"); + + b.Navigation("Directory"); + + b.Navigation("Host"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Design", b => + { + b.Navigation("Modules"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Directory", b => + { + b.Navigation("Children"); + + b.Navigation("Designs"); + + b.Navigation("Files"); + + b.Navigation("Workspaces"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.File", b => + { + b.Navigation("FileVersions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Host", b => + { + b.Navigation("Machines"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Module", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Partition", b => + { + b.Navigation("Vlans"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Permission", b => + { + b.Navigation("UserPermissions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Pool", b => + { + b.Navigation("Partitions"); + + b.Navigation("Vlans"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Project", b => + { + b.Navigation("Directories"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Run", b => + { + b.Navigation("Apply"); + + b.Navigation("Plan"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.User", b => + { + b.Navigation("UserPermissions"); + }); + + modelBuilder.Entity("Caster.Api.Domain.Models.Workspace", b => + { + b.Navigation("Files"); + + b.Navigation("Runs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.cs b/src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.cs new file mode 100644 index 0000000..032a479 --- /dev/null +++ b/src/Caster.Api/Data/Migrations/20241210162042_replace_addresses.cs @@ -0,0 +1,33 @@ +/* +Copyright 2021 Carnegie Mellon University. All Rights Reserved. + Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information. +*/ + +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Caster.Api.Data.Migrations +{ + /// + public partial class replace_addresses : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "replace_addresses", + table: "runs", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "replace_addresses", + table: "runs"); + } + } +} diff --git a/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs b/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs index 900faca..6f122fe 100644 --- a/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs +++ b/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs @@ -22,7 +22,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("ProductVersion", "8.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp"); @@ -591,6 +591,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("modified_by_id"); + b.Property("ReplaceAddresses") + .HasColumnType("text") + .HasColumnName("replace_addresses"); + b.Property("Status") .HasColumnType("integer") .HasColumnName("status"); diff --git a/src/Caster.Api/Domain/Models/Run.cs b/src/Caster.Api/Domain/Models/Run.cs index 27b2b57..cf0e9da 100644 --- a/src/Caster.Api/Domain/Models/Run.cs +++ b/src/Caster.Api/Domain/Models/Run.cs @@ -30,6 +30,8 @@ public class Run public string[] Targets { get; set; } + public string[] ReplaceAddresses { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public Guid? CreatedById { get; set; } public User CreatedBy { get; set; } @@ -86,6 +88,13 @@ public void Configure(EntityTypeBuilder builder) str => str.Split('\n', StringSplitOptions.RemoveEmptyEntries) ); + builder + .Property(r => r.ReplaceAddresses) + .HasConversion( + list => String.Join('\n', list), + str => str.Split('\n', StringSplitOptions.RemoveEmptyEntries) + ); + builder .HasOne(r => r.Plan) .WithOne(p => p.Run) diff --git a/src/Caster.Api/Domain/Services/TerraformService.cs b/src/Caster.Api/Domain/Services/TerraformService.cs index f5789f1..3e0dea1 100644 --- a/src/Caster.Api/Domain/Services/TerraformService.cs +++ b/src/Caster.Api/Domain/Services/TerraformService.cs @@ -21,7 +21,7 @@ public interface ITerraformService TerraformResult InitializeWorkspace(Workspace workspace, DataReceivedEventHandler outputHandler); TerraformResult Init(Workspace workspace, DataReceivedEventHandler outputHandler); TerraformResult SelectWorkspace(Workspace workspace, DataReceivedEventHandler outputHandler); - TerraformResult Plan(Workspace workspace, bool destroy, string[] targets, DataReceivedEventHandler outputHandler); + TerraformResult Plan(Workspace workspace, bool destroy, string[] targets, string[] replaceAddresses, DataReceivedEventHandler outputHandler); TerraformResult Apply(Workspace workspace, DataReceivedEventHandler outputHandler); TerraformResult Show(Workspace workspace); TerraformResult Taint(Workspace workspace, string address, string statePath); @@ -190,7 +190,7 @@ public TerraformResult SelectWorkspace(Workspace workspace, DataReceivedEventHan return this.Run(workspace, args, outputHandler); } - public TerraformResult Plan(Workspace workspace, bool destroy, string[] targets, DataReceivedEventHandler outputHandler) + public TerraformResult Plan(Workspace workspace, bool destroy, string[] targets, string[] replaceAddresses, DataReceivedEventHandler outputHandler) { List args = new List { "plan", "-input=false", "-out=plan" }; @@ -209,6 +209,11 @@ public TerraformResult Plan(Workspace workspace, bool destroy, string[] targets, args.Add($"--target={target}"); } + foreach (string address in replaceAddresses) + { + args.Add($"-replace={address}"); + } + return this.Run(workspace, args, outputHandler); } diff --git a/src/Caster.Api/Features/Runs/EventHandlers/RunAddedHandler.cs b/src/Caster.Api/Features/Runs/EventHandlers/RunAddedHandler.cs index 72e56c2..986e1bc 100644 --- a/src/Caster.Api/Features/Runs/EventHandlers/RunAddedHandler.cs +++ b/src/Caster.Api/Features/Runs/EventHandlers/RunAddedHandler.cs @@ -154,7 +154,7 @@ private async Task DoWork(Domain.Models.Run run) } // Plan - var planResult = _terraformService.Plan(workspace, run.IsDestroy, run.Targets, OutputHandler); + var planResult = _terraformService.Plan(workspace, run.IsDestroy, run.Targets, run.ReplaceAddresses, OutputHandler); isError = planResult.IsError; } @@ -280,7 +280,7 @@ private void OnTimedEvent(Object source, ElapsedEventArgs e) /// /// Attempts to mitigate common issues with destroying Azure workspaces. - /// If previous destroys failed, remove from the state any resources that exist within an + /// If previous destroys failed, remove from the state any resources that exist within an /// azurerm_resource_group managed by this workspace. They will be destroyed implicitly when destroying the workspace and /// can avoid errors with destroying those resources directly. /// diff --git a/src/Caster.Api/Features/Runs/Requests/Create.cs b/src/Caster.Api/Features/Runs/Requests/Create.cs index 5140910..0fd3e09 100644 --- a/src/Caster.Api/Features/Runs/Requests/Create.cs +++ b/src/Caster.Api/Features/Runs/Requests/Create.cs @@ -46,6 +46,12 @@ public class Command : IRequest /// [DataMember] public string[] Targets { get; set; } + + /// + /// Optional list of resources to replace on this Run + /// + [DataMember] + public string[] ReplaceAddresses { get; set; } } public class Handler : IRequestHandler diff --git a/src/Caster.Api/Features/Runs/Run.cs b/src/Caster.Api/Features/Runs/Run.cs index ead1701..a6f30b2 100644 --- a/src/Caster.Api/Features/Runs/Run.cs +++ b/src/Caster.Api/Features/Runs/Run.cs @@ -34,6 +34,11 @@ public class Run /// public string[] Targets { get; set; } + /// + /// Optional list of resources to replace on this Run + /// + public string[] ReplaceAddresses { get; set; } + /// /// The Plan for this Run, if one exists. Null if not requested ///