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
///