diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d3dcb02..2b9b8c9 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1 @@
-* @sei-ebram @sei-tspencer @sei-chershberger @sei-aschlackman @sei-noconnor @sei-byoung
+* @cmu-sei/crucible-code-owners
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 101e726..d26a484 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -7,12 +7,12 @@ name: "Security - CodeQL"
on:
push:
- branches: [development, main, staging]
+ branches: [main]
pull_request:
# The branches below must be a subset of the branches above
- branches: [development]
+ branches: [main]
schedule:
- - cron: '0 22 * * 0'
+ - cron: "0 5 * * 5"
jobs:
analyze:
@@ -24,36 +24,40 @@ jobs:
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
- language: ['csharp']
+ language: ["csharp"]
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- - name: Checkout repository
- uses: actions/checkout@v2
- with:
- # We must fetch at least the immediate parents so that if this is
- # a pull request then we can checkout the head.
- fetch-depth: 2
-
- # If this run was triggered by a pull request event, then checkout
- # the head of the pull request instead of the merge commit.
- - run: git checkout HEAD^2
- if: ${{ github.event_name == 'pull_request' }}
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v1
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
- # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
- - run: |
- cd src/Caster.Api
- dotnet build -c Release
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ - name: Checkout repository
+ uses: actions/checkout@v5
+ with:
+ # We must fetch at least the immediate parents so that if this is
+ # a pull request then we can checkout the head.
+ fetch-depth: 2
+ - name: Load dotnet
+ uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: "10.0.x"
+
+ # If this run was triggered by a pull request event, then checkout
+ # the head of the pull request instead of the merge commit.
+ - run: git checkout HEAD^2
+ if: ${{ github.event_name == 'pull_request' }}
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ - run: |
+ cd Caster.Api
+ dotnet build -c Release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
diff --git a/Dockerfile b/Dockerfile
index d0683f2..fff41c9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
# Adapted from https://github.com/dotnet/dotnet-docker/blob/main/samples/aspnetapp/Dockerfile.chiseled
# Build stage
-FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG TARGETARCH
WORKDIR /source
@@ -17,7 +17,7 @@ WORKDIR /source/src/Caster.Api
RUN dotnet publish -a $TARGETARCH --no-restore -o /app
# Production Stage
-FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS prod
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS prod
ARG commit
ENV COMMIT=$commit
ENV DOTNET_HOSTBUILDER__RELOADCONFIGCHANGE=false
diff --git a/global.json b/global.json
index 4570606..dcf907b 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.304",
+ "version": "10.0",
"allowPrerelease": false,
"rollForward": "latestMinor"
}
diff --git a/src/Caster.Api/Caster.Api.csproj b/src/Caster.Api/Caster.Api.csproj
index fff9d92..3361561 100644
--- a/src/Caster.Api/Caster.Api.csproj
+++ b/src/Caster.Api/Caster.Api.csproj
@@ -1,36 +1,46 @@
- net8.0
+ net10.0
bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
1591
{CE177C12-4188-4D93-ABC9-1464D6E5AE81}
+ latest
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/src/Caster.Api/Data/Migrations/20251113191753_Version10UpgradeSync.Designer.cs b/src/Caster.Api/Data/Migrations/20251113191753_Version10UpgradeSync.Designer.cs
new file mode 100644
index 0000000..f739466
--- /dev/null
+++ b/src/Caster.Api/Data/Migrations/20251113191753_Version10UpgradeSync.Designer.cs
@@ -0,0 +1,1350 @@
+//
+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("20251113191753_Version10UpgradeSync")]
+ partial class Version10UpgradeSync
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.0")
+ .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.Group", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("uuid_generate_v4()");
+
+ b.Property("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("groups");
+ });
+
+ modelBuilder.Entity("Caster.Api.Domain.Models.GroupMembership", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("uuid_generate_v4()");
+
+ b.Property("GroupId")
+ .HasColumnType("uuid")
+ .HasColumnName("group_id");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("GroupId", "UserId")
+ .IsUnique();
+
+ b.ToTable("group_memberships");
+ });
+
+ 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.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.ProjectMembership", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("uuid_generate_v4()");
+
+ b.Property("GroupId")
+ .HasColumnType("uuid")
+ .HasColumnName("group_id");
+
+ b.Property("ProjectId")
+ .HasColumnType("uuid")
+ .HasColumnName("project_id");
+
+ b.Property("RoleId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasDefaultValue(new Guid("f870d8ee-7332-4f7f-8ee0-63bd07cfd7e4"))
+ .HasColumnName("role_id");
+
+ b.Property("UserId")
+ .HasColumnType("uuid")
+ .HasColumnName("user_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GroupId");
+
+ b.HasIndex("RoleId");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("ProjectId", "UserId", "GroupId")
+ .IsUnique();
+
+ b.ToTable("project_memberships");
+ });
+
+ modelBuilder.Entity("Caster.Api.Domain.Models.ProjectRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("uuid_generate_v4()");
+
+ b.Property("AllPermissions")
+ .HasColumnType("boolean")
+ .HasColumnName("all_permissions");
+
+ b.Property("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.PrimitiveCollection("Permissions")
+ .HasColumnType("integer[]")
+ .HasColumnName("permissions");
+
+ b.HasKey("Id");
+
+ b.ToTable("project_roles");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("1a3f26cd-9d99-4b98-b914-12931e786198"),
+ AllPermissions = true,
+ Description = "Can perform all actions on the Project",
+ Name = "Manager",
+ Permissions = new int[0]
+ },
+ new
+ {
+ Id = new Guid("39aa296e-05ba-4fb0-8d74-c92cf3354c6f"),
+ AllPermissions = false,
+ Description = "Has read only access to the Project",
+ Name = "Observer",
+ Permissions = new[] { 0 }
+ },
+ new
+ {
+ Id = new Guid("f870d8ee-7332-4f7f-8ee0-63bd07cfd7e4"),
+ AllPermissions = false,
+ Description = "Has read only access to the Project",
+ Name = "Member",
+ Permissions = new[] { 0, 1, 3 }
+ });
+ });
+
+ 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.SystemRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasDefaultValueSql("uuid_generate_v4()");
+
+ b.Property("AllPermissions")
+ .HasColumnType("boolean")
+ .HasColumnName("all_permissions");
+
+ b.Property("Description")
+ .HasColumnType("text")
+ .HasColumnName("description");
+
+ b.Property("Immutable")
+ .HasColumnType("boolean")
+ .HasColumnName("immutable");
+
+ b.Property("Name")
+ .HasColumnType("text")
+ .HasColumnName("name");
+
+ b.PrimitiveCollection("Permissions")
+ .HasColumnType("integer[]")
+ .HasColumnName("permissions");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("system_roles");
+
+ b.HasData(
+ new
+ {
+ Id = new Guid("f35e8fff-f996-4cba-b303-3ba515ad8d2f"),
+ AllPermissions = true,
+ Description = "Can perform all actions.",
+ Immutable = true,
+ Name = "Administrator",
+ Permissions = new int[0]
+ },
+ new
+ {
+ Id = new Guid("d80b73c3-95d7-4468-8650-c62bbd082507"),
+ AllPermissions = false,
+ Description = "Can create and manage their own Projects.",
+ Immutable = false,
+ Name = "Content Developer",
+ Permissions = new[] { 0 }
+ },
+ new
+ {
+ Id = new Guid("1da3027e-725d-4753-9455-a836ed9bdb1e"),
+ AllPermissions = false,
+ Description = "Can perform all View actions, but not make any changes.",
+ Immutable = false,
+ Name = "Observer",
+ Permissions = new[] { 1, 7, 9, 11, 13, 15, 17, 19 }
+ });
+ });
+
+ 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.Property("RoleId")
+ .HasColumnType("uuid")
+ .HasColumnName("role_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("users");
+ });
+
+ 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("ReservedEditable")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(true)
+ .HasColumnName("reserved_editable");
+
+ 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.GroupMembership", b =>
+ {
+ b.HasOne("Caster.Api.Domain.Models.Group", "Group")
+ .WithMany("Memberships")
+ .HasForeignKey("GroupId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Caster.Api.Domain.Models.User", "User")
+ .WithMany("GroupMemberships")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Group");
+
+ b.Navigation("User");
+ });
+
+ 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.ProjectMembership", b =>
+ {
+ b.HasOne("Caster.Api.Domain.Models.Group", "Group")
+ .WithMany("ProjectMemberships")
+ .HasForeignKey("GroupId");
+
+ b.HasOne("Caster.Api.Domain.Models.Project", "Project")
+ .WithMany("Memberships")
+ .HasForeignKey("ProjectId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Caster.Api.Domain.Models.ProjectRole", "Role")
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Caster.Api.Domain.Models.User", "User")
+ .WithMany("ProjectMemberships")
+ .HasForeignKey("UserId");
+
+ b.Navigation("Group");
+
+ b.Navigation("Project");
+
+ b.Navigation("Role");
+
+ b.Navigation("User");
+ });
+
+ 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.User", b =>
+ {
+ b.HasOne("Caster.Api.Domain.Models.SystemRole", "Role")
+ .WithMany()
+ .HasForeignKey("RoleId");
+
+ b.Navigation("Role");
+ });
+
+ 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.Group", b =>
+ {
+ b.Navigation("Memberships");
+
+ b.Navigation("ProjectMemberships");
+ });
+
+ 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.Pool", b =>
+ {
+ b.Navigation("Partitions");
+
+ b.Navigation("Vlans");
+ });
+
+ modelBuilder.Entity("Caster.Api.Domain.Models.Project", b =>
+ {
+ b.Navigation("Directories");
+
+ b.Navigation("Memberships");
+ });
+
+ modelBuilder.Entity("Caster.Api.Domain.Models.Run", b =>
+ {
+ b.Navigation("Apply");
+
+ b.Navigation("Plan");
+ });
+
+ modelBuilder.Entity("Caster.Api.Domain.Models.User", b =>
+ {
+ b.Navigation("GroupMemberships");
+
+ b.Navigation("ProjectMemberships");
+ });
+
+ 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/20251113191753_Version10UpgradeSync.cs b/src/Caster.Api/Data/Migrations/20251113191753_Version10UpgradeSync.cs
new file mode 100644
index 0000000..2fcbdfa
--- /dev/null
+++ b/src/Caster.Api/Data/Migrations/20251113191753_Version10UpgradeSync.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Caster.Api.Data.Migrations
+{
+ ///
+ public partial class Version10UpgradeSync : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+
+ }
+ }
+}
diff --git a/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs b/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs
index bff5ed2..15c5896 100644
--- a/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs
+++ b/src/Caster.Api/Data/Migrations/CasterContextModelSnapshot.cs
@@ -1,8 +1,3 @@
-/*
-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;
@@ -22,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("ProductVersion", "10.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "uuid-ossp");
@@ -53,7 +48,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("RunId")
.IsUnique();
- b.ToTable("applies", (string)null);
+ b.ToTable("applies");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Design", b =>
@@ -80,7 +75,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("DirectoryId");
- b.ToTable("designs", (string)null);
+ b.ToTable("designs");
});
modelBuilder.Entity("Caster.Api.Domain.Models.DesignModule", b =>
@@ -121,7 +116,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("ModuleId");
- b.ToTable("design_modules", (string)null);
+ b.ToTable("design_modules");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Directory", b =>
@@ -174,7 +169,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("ProjectId");
- b.ToTable("directories", (string)null);
+ b.ToTable("directories");
});
modelBuilder.Entity("Caster.Api.Domain.Models.File", b =>
@@ -231,7 +226,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WorkspaceId");
- b.ToTable("files", (string)null);
+ b.ToTable("files");
});
modelBuilder.Entity("Caster.Api.Domain.Models.FileVersion", b =>
@@ -282,7 +277,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("TaggedById");
- b.ToTable("file_versions", (string)null);
+ b.ToTable("file_versions");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Group", b =>
@@ -306,7 +301,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("Name")
.IsUnique();
- b.ToTable("groups", (string)null);
+ b.ToTable("groups");
});
modelBuilder.Entity("Caster.Api.Domain.Models.GroupMembership", b =>
@@ -332,7 +327,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("GroupId", "UserId")
.IsUnique();
- b.ToTable("group_memberships", (string)null);
+ b.ToTable("group_memberships");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Host", b =>
@@ -371,7 +366,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("ProjectId");
- b.ToTable("hosts", (string)null);
+ b.ToTable("hosts");
});
modelBuilder.Entity("Caster.Api.Domain.Models.HostMachine", b =>
@@ -400,7 +395,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WorkspaceId");
- b.ToTable("host_machines", (string)null);
+ b.ToTable("host_machines");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Module", b =>
@@ -429,7 +424,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
- b.ToTable("modules", (string)null);
+ b.ToTable("modules");
});
modelBuilder.Entity("Caster.Api.Domain.Models.ModuleVersion", b =>
@@ -468,7 +463,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("ModuleId");
- b.ToTable("module_versions", (string)null);
+ b.ToTable("module_versions");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Partition", b =>
@@ -495,7 +490,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("PoolId");
- b.ToTable("partitions", (string)null);
+ b.ToTable("partitions");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Plan", b =>
@@ -523,7 +518,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("RunId")
.IsUnique();
- b.ToTable("plans", (string)null);
+ b.ToTable("plans");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Pool", b =>
@@ -544,7 +539,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
- b.ToTable("pools", (string)null);
+ b.ToTable("pools");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Project", b =>
@@ -567,7 +562,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("PartitionId");
- b.ToTable("projects", (string)null);
+ b.ToTable("projects");
});
modelBuilder.Entity("Caster.Api.Domain.Models.ProjectMembership", b =>
@@ -607,7 +602,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("ProjectId", "UserId", "GroupId")
.IsUnique();
- b.ToTable("project_memberships", (string)null);
+ b.ToTable("project_memberships");
});
modelBuilder.Entity("Caster.Api.Domain.Models.ProjectRole", b =>
@@ -630,13 +625,13 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("text")
.HasColumnName("name");
- b.Property("Permissions")
+ b.PrimitiveCollection("Permissions")
.HasColumnType("integer[]")
.HasColumnName("permissions");
b.HasKey("Id");
- b.ToTable("project_roles", (string)null);
+ b.ToTable("project_roles");
b.HasData(
new
@@ -673,7 +668,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasKey("Id");
- b.ToTable("removed_resources", (string)null);
+ b.ToTable("removed_resources");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Run", b =>
@@ -730,7 +725,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("WorkspaceId");
- b.ToTable("runs", (string)null);
+ b.ToTable("runs");
});
modelBuilder.Entity("Caster.Api.Domain.Models.SystemRole", b =>
@@ -757,7 +752,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("text")
.HasColumnName("name");
- b.Property("Permissions")
+ b.PrimitiveCollection("Permissions")
.HasColumnType("integer[]")
.HasColumnName("permissions");
@@ -766,7 +761,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("Name")
.IsUnique();
- b.ToTable("system_roles", (string)null);
+ b.ToTable("system_roles");
b.HasData(
new
@@ -818,7 +813,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("RoleId");
- b.ToTable("users", (string)null);
+ b.ToTable("users");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Variable", b =>
@@ -849,7 +844,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("DesignId");
- b.ToTable("variables", (string)null);
+ b.ToTable("variables");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Vlan", b =>
@@ -898,7 +893,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("VlanId");
- b.ToTable("vlans", (string)null);
+ b.ToTable("vlans");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Workspace", b =>
@@ -959,7 +954,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.HasIndex("HostId");
- b.ToTable("workspaces", (string)null);
+ b.ToTable("workspaces");
});
modelBuilder.Entity("Caster.Api.Domain.Models.Apply", b =>
diff --git a/src/Caster.Api/Domain/Services/TelemetryService.cs b/src/Caster.Api/Domain/Services/TelemetryService.cs
new file mode 100644
index 0000000..f4abd4d
--- /dev/null
+++ b/src/Caster.Api/Domain/Services/TelemetryService.cs
@@ -0,0 +1,26 @@
+// Copyright 2025 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.Diagnostics.Metrics;
+
+namespace Caster.Api.Domain.Services
+{
+ public interface ITelemetryService
+ {
+ }
+
+ public class TelemetryService : ITelemetryService
+ {
+ public const string CasterMeterName = "cmu_sei_crucible_caster";
+ public readonly Meter CasterMeter = new Meter(CasterMeterName, "1.0");
+ public Gauge Projects;
+ public Gauge Workspaces;
+
+ public TelemetryService()
+ {
+ Projects = CasterMeter.CreateGauge("caster_projects");
+ Workspaces = CasterMeter.CreateGauge("caster_workspaces");
+ }
+
+ }
+}
diff --git a/src/Caster.Api/Features/Projects/EventHandlers/ProjectEventHandler.cs b/src/Caster.Api/Features/Projects/EventHandlers/ProjectEventHandler.cs
new file mode 100644
index 0000000..adb4df3
--- /dev/null
+++ b/src/Caster.Api/Features/Projects/EventHandlers/ProjectEventHandler.cs
@@ -0,0 +1,47 @@
+// Copyright 2025 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.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using Caster.Api.Data;
+using Caster.Api.Domain.Events;
+using Caster.Api.Domain.Services;
+using Caster.Api.Hubs;
+using MediatR;
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.EntityFrameworkCore;
+
+namespace Caster.Api.Features.Projects.EventHandlers;
+
+public class ProjectCreatedSignalRHandler(CasterContext _db, TelemetryService telemetryService) :
+ ProjectBaseSignalRHandler(_db, telemetryService),
+ INotificationHandler>
+{
+ public async Task Handle(EntityCreated notification, CancellationToken cancellationToken)
+ {
+ await base.Handle(notification.Entity, cancellationToken);
+ }
+}
+
+public class ProjectDeletedSignalRHandler(CasterContext _db, TelemetryService telemetryService) :
+ ProjectBaseSignalRHandler(_db, telemetryService),
+ INotificationHandler>
+{
+ public async Task Handle(EntityDeleted notification, CancellationToken cancellationToken)
+ {
+ await base.Handle(notification.Entity, cancellationToken);
+ }
+}
+
+public class ProjectBaseSignalRHandler(CasterContext db, TelemetryService telemetryService)
+{
+ protected async Task Handle(Domain.Models.Project entity, CancellationToken cancellationToken)
+ {
+ var count = await db.Projects.LongCountAsync(cancellationToken);
+ telemetryService.Projects.Record((int)count);
+
+ }
+}
diff --git a/src/Caster.Api/Features/Projects/Requests/Create.cs b/src/Caster.Api/Features/Projects/Requests/Create.cs
index 79255fa..a1d425d 100644
--- a/src/Caster.Api/Features/Projects/Requests/Create.cs
+++ b/src/Caster.Api/Features/Projects/Requests/Create.cs
@@ -2,21 +2,20 @@
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
using System.Runtime.Serialization;
-using System.Security.Claims;
-using System.Security.Principal;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using AutoMapper;
-using Microsoft.AspNetCore.Authorization;
+using System.Collections.Generic;
using Caster.Api.Data;
using Caster.Api.Infrastructure.Authorization;
-using Caster.Api.Infrastructure.Exceptions;
using Caster.Api.Infrastructure.Identity;
using Caster.Api.Infrastructure.Extensions;
using Caster.Api.Domain.Models;
-using Microsoft.Extensions.DependencyInjection;
using Caster.Api.Features.Shared;
+using Microsoft.EntityFrameworkCore;
+using Caster.Api.Domain.Services;
namespace Caster.Api.Features.Projects
{
@@ -32,7 +31,12 @@ public class Command : IRequest
public string Name { get; set; }
}
- public class Handler(ICasterAuthorizationService authorizationService, IMapper mapper, CasterContext dbContext, IIdentityResolver identityResolver) : BaseHandler
+ public class Handler(
+ ICasterAuthorizationService authorizationService,
+ IMapper mapper,
+ CasterContext dbContext,
+ TelemetryService telemetryService,
+ IIdentityResolver identityResolver) : BaseHandler
{
public override async Task Authorize(Command request, CancellationToken cancellationToken) =>
await authorizationService.Authorize([SystemPermission.CreateProjects], cancellationToken);
diff --git a/src/Caster.Api/Features/Projects/Requests/Delete.cs b/src/Caster.Api/Features/Projects/Requests/Delete.cs
index f7eaf90..023a251 100644
--- a/src/Caster.Api/Features/Projects/Requests/Delete.cs
+++ b/src/Caster.Api/Features/Projects/Requests/Delete.cs
@@ -19,6 +19,7 @@
using Caster.Api.Infrastructure.Identity;
using Caster.Api.Features.Shared;
using Caster.Api.Domain.Models;
+using Caster.Api.Domain.Services;
namespace Caster.Api.Features.Projects
{
@@ -30,7 +31,10 @@ public class Command : IRequest
public Guid Id { get; set; }
}
- public class Handler(ICasterAuthorizationService authorizationService, CasterContext dbContext) : BaseHandler
+ public class Handler(
+ ICasterAuthorizationService authorizationService,
+ TelemetryService telemetryService,
+ CasterContext dbContext) : BaseHandler
{
public override async Task Authorize(Command request, CancellationToken cancellationToken) =>
await authorizationService.Authorize(request.Id, [SystemPermission.EditProjects], [ProjectPermission.EditProject], cancellationToken);
diff --git a/src/Caster.Api/Features/Workspaces/EventHandlers/TelemetryEventHandler.cs b/src/Caster.Api/Features/Workspaces/EventHandlers/TelemetryEventHandler.cs
new file mode 100644
index 0000000..fb52472
--- /dev/null
+++ b/src/Caster.Api/Features/Workspaces/EventHandlers/TelemetryEventHandler.cs
@@ -0,0 +1,64 @@
+// Copyright 2025 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.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Caster.Api.Data;
+using Caster.Api.Domain.Events;
+using MediatR;
+using Microsoft.EntityFrameworkCore;
+using Caster.Api.Domain.Services;
+
+namespace Caster.Api.Features.Workspaces.EventHandlers;
+
+public class WorkspaceCreatedTelemetryHandler(CasterContext _db, TelemetryService telemetryService) :
+ WorkspaceBaseTelemetryHandler(_db, telemetryService),
+ INotificationHandler>
+{
+ public async Task Handle(EntityCreated notification, CancellationToken cancellationToken)
+ {
+ await base.Handle(notification.Entity, cancellationToken);
+ }
+}
+
+public class WorkspaceDeletedTelemetryHandler(CasterContext _db, TelemetryService telemetryService) :
+ WorkspaceBaseTelemetryHandler(_db, telemetryService),
+ INotificationHandler>
+{
+ public async Task Handle(EntityDeleted notification, CancellationToken cancellationToken)
+ {
+ await base.Handle(notification.Entity, cancellationToken);
+ }
+}
+
+public class WorkspaceBaseTelemetryHandler(CasterContext _db, TelemetryService telemetryService)
+{
+ protected async Task Handle(Domain.Models.Workspace entity, CancellationToken cancellationToken)
+ {
+ var directoryMetrics = await _db.Directories.Select(d => new
+ {
+ Directory = d.Name,
+ DirectoryId = d.Id,
+ Project = d.Project.Name,
+ ProjectId = d.ProjectId,
+ Count = d.Workspaces.Count
+ }).SingleOrDefaultAsync(m => m.DirectoryId == entity.DirectoryId);
+ telemetryService.Workspaces.Record(directoryMetrics.Count,
+ new KeyValuePair("project", directoryMetrics.Project),
+ new KeyValuePair("project_id", directoryMetrics.ProjectId),
+ new KeyValuePair("directory", directoryMetrics.Directory),
+ new KeyValuePair("directory_id", directoryMetrics.DirectoryId)
+ );
+ var projectMetrics = await _db.Projects.Select(p => new
+ {
+ Project = p.Name,
+ ProjectId = p.Id,
+ Count = p.Directories.Select(d => d.Workspaces.Count).Sum()
+ }).SingleOrDefaultAsync(m => m.ProjectId == entity.Directory.ProjectId);
+ telemetryService.Workspaces.Record(projectMetrics.Count,
+ new KeyValuePair("project", projectMetrics.Project),
+ new KeyValuePair("project_id", projectMetrics.ProjectId)
+ ); }
+}
diff --git a/src/Caster.Api/Features/Workspaces/Requests/Create.cs b/src/Caster.Api/Features/Workspaces/Requests/Create.cs
index 859032c..2e1b4b1 100644
--- a/src/Caster.Api/Features/Workspaces/Requests/Create.cs
+++ b/src/Caster.Api/Features/Workspaces/Requests/Create.cs
@@ -17,6 +17,10 @@
using Caster.Api.Features.Shared;
using Caster.Api.Features.Shared.Validators;
using Caster.Api.Domain.Models;
+using Caster.Api.Domain.Services;
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
+using System.Collections.Generic;
namespace Caster.Api.Features.Workspaces
{
@@ -83,7 +87,8 @@ public class Handler(
ICasterAuthorizationService authorizationService,
IMapper mapper,
CasterContext dbContext,
- TerraformOptions terraformOptions) : BaseHandler
+ TerraformOptions terraformOptions,
+ TelemetryService telemetryService) : BaseHandler
{
public override async Task Authorize(Command request, CancellationToken cancellationToken) =>
await authorizationService.Authorize(request.DirectoryId, [SystemPermission.EditProjects], [ProjectPermission.EditProject], cancellationToken);
diff --git a/src/Caster.Api/Features/Workspaces/Requests/Delete.cs b/src/Caster.Api/Features/Workspaces/Requests/Delete.cs
index 78031a8..a4002ee 100644
--- a/src/Caster.Api/Features/Workspaces/Requests/Delete.cs
+++ b/src/Caster.Api/Features/Workspaces/Requests/Delete.cs
@@ -2,6 +2,7 @@
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -13,6 +14,7 @@
using Caster.Api.Domain.Services;
using Caster.Api.Domain.Models;
using Caster.Api.Features.Shared;
+using Microsoft.EntityFrameworkCore;
namespace Caster.Api.Features.Workspaces
{
@@ -24,14 +26,18 @@ public class Command : IRequest
public Guid Id { get; set; }
}
- public class Handler(ICasterAuthorizationService authorizationService, CasterContext dbContext, ILockService lockService) : BaseHandler
+ public class Handler(
+ ICasterAuthorizationService authorizationService,
+ CasterContext dbContext,
+ TelemetryService telemetryService,
+ ILockService lockService) : BaseHandler
{
public override async Task Authorize(Command request, CancellationToken cancellationToken) =>
await authorizationService.Authorize(request.Id, [SystemPermission.EditProjects], [ProjectPermission.EditProject], cancellationToken);
public override async Task HandleRequest(Command request, CancellationToken cancellationToken)
{
- var workspace = await dbContext.Workspaces.FindAsync([request.Id], cancellationToken);
+ var workspace = await dbContext.Workspaces.Include(m => m.Directory).SingleOrDefaultAsync(m => m.Id == request.Id, cancellationToken);
if (workspace == null)
throw new EntityNotFoundException();
diff --git a/src/Caster.Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs b/src/Caster.Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs
index 6d6f63d..b1c9a77 100644
--- a/src/Caster.Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Caster.Api/Infrastructure/Extensions/ServiceCollectionExtensions.cs
@@ -18,11 +18,11 @@
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Microsoft.OpenApi.Models;
using Polly;
using Polly.Extensions.Http;
using Player.Vm.Api;
using Caster.Api.Infrastructure.Swashbuckle.DocumentFilters;
+using Microsoft.OpenApi;
namespace Caster.Api.Infrastructure.Extensions
{
@@ -149,29 +149,38 @@ public static void AddSwagger(this IServiceCollection services, AuthorizationOpt
}
});
-
- c.AddSecurityRequirement(new OpenApiSecurityRequirement()
+ c.AddSecurityRequirement((document) => new OpenApiSecurityRequirement
{
- {
- new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = "oauth2"
- },
- Scheme = "oauth2"
- },
- new[] {authOptions.AuthorizationScope}
- }
+ { new OpenApiSecuritySchemeReference("oauth2", document), [authOptions.AuthorizationScope] }
});
c.EnableAnnotations();
c.IncludeXmlComments(commentsFilePath);
- c.MapType>(() => new OpenApiSchema { Type = "string", Format = "uuid" });
- c.MapType>(() => new OpenApiSchema { Type = "integer", Format = "int32", Nullable = true });
- c.MapType(() => new OpenApiSchema { Type = "object", Nullable = true });
+ c.MapType>(() => new OpenApiSchema
+ {
+ OneOf = new List
+ {
+ new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid" },
+ new OpenApiSchema { Type = JsonSchemaType.Null }
+ }
+ });
+ c.MapType>(() => new OpenApiSchema
+ {
+ OneOf = new List
+ {
+ new OpenApiSchema { Type = JsonSchemaType.Integer, Format = "int32" },
+ new OpenApiSchema { Type = JsonSchemaType.Null }
+ }
+ });
+ c.MapType(() => new OpenApiSchema
+ {
+ OneOf = new List
+ {
+ new OpenApiSchema { Type = JsonSchemaType.Object },
+ new OpenApiSchema { Type = JsonSchemaType.Null }
+ }
+ });
});
}
diff --git a/src/Caster.Api/Infrastructure/Options/TelemetryOptions.cs b/src/Caster.Api/Infrastructure/Options/TelemetryOptions.cs
new file mode 100644
index 0000000..d4b0d71
--- /dev/null
+++ b/src/Caster.Api/Infrastructure/Options/TelemetryOptions.cs
@@ -0,0 +1,17 @@
+// Copyright 2025 Carnegie Mellon University. All Rights Reserved.
+// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
+
+namespace Caster.Api.Infrastructure.Options
+{
+ public class TelemetryOptions
+ {
+ public bool AddRuntimeInstrumentation { get; set; }
+ public bool AddProcessInstrumentation { get; set; }
+ public bool AddAspNetCoreInstrumentation { get; set; }
+ public bool AddHttpClientInstrumentation { get; set; }
+ public bool UseMeterMicrosoftAspNetCoreHosting { get; set; }
+ public bool UseMeterMicrosoftAspNetCoreServerKestrel { get; set; }
+ public bool UseMeterSystemNetHttp { get; set; }
+ public bool UseMeterSystemNetNameResolution { get; set; }
+ }
+}
diff --git a/src/Caster.Api/Infrastructure/Swashbuckle/DocumentFilters/ValidationProblemDetailsDocumentFilter.cs b/src/Caster.Api/Infrastructure/Swashbuckle/DocumentFilters/ValidationProblemDetailsDocumentFilter.cs
index d3032d8..2f0e562 100644
--- a/src/Caster.Api/Infrastructure/Swashbuckle/DocumentFilters/ValidationProblemDetailsDocumentFilter.cs
+++ b/src/Caster.Api/Infrastructure/Swashbuckle/DocumentFilters/ValidationProblemDetailsDocumentFilter.cs
@@ -1,10 +1,10 @@
/*
-Copyright 2021 Carnegie Mellon University. All Rights Reserved.
+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.AspNetCore.Mvc;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Caster.Api.Infrastructure.Swashbuckle.DocumentFilters
@@ -16,4 +16,4 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
context.SchemaGenerator.GenerateSchema(typeof(ValidationProblemDetails), context.SchemaRepository);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/DefaultResponseOperationFilter.cs b/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/DefaultResponseOperationFilter.cs
index 3b90544..53c8cb0 100644
--- a/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/DefaultResponseOperationFilter.cs
+++ b/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/DefaultResponseOperationFilter.cs
@@ -3,8 +3,7 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.OpenApi.Models;
-using Swashbuckle.AspNetCore.Swagger;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Caster.Api.Infrastructure.Swashbuckle.OperationFilters
diff --git a/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/JsonIgnoreQueryOperationFilter.cs b/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/JsonIgnoreQueryOperationFilter.cs
index 6a21645..88df96f 100644
--- a/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/JsonIgnoreQueryOperationFilter.cs
+++ b/src/Caster.Api/Infrastructure/Swashbuckle/OperationFilters/JsonIgnoreQueryOperationFilter.cs
@@ -4,7 +4,7 @@
using System.Linq;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Caster.Api.Infrastructure.Swashbuckle.OperationFilters
diff --git a/src/Caster.Api/Infrastructure/Swashbuckle/ParameterFilters/AutoRestEnumParameterFilter.cs b/src/Caster.Api/Infrastructure/Swashbuckle/ParameterFilters/AutoRestEnumParameterFilter.cs
index a95097e..68df774 100644
--- a/src/Caster.Api/Infrastructure/Swashbuckle/ParameterFilters/AutoRestEnumParameterFilter.cs
+++ b/src/Caster.Api/Infrastructure/Swashbuckle/ParameterFilters/AutoRestEnumParameterFilter.cs
@@ -1,28 +1,28 @@
// 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.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+
+using System.Text.Json.Nodes;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Caster.Api.Infrastructure.Swashbuckle.ParameterFilters
{
public class AutoRestEnumParameterFilter : IParameterFilter
{
- public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
+ public void Apply(IOpenApiParameter parameter, ParameterFilterContext context)
{
var type = context.ApiParameterDescription.Type;
if (type != null && type.IsEnum)
{
- parameter.Extensions.Add(
- "x-ms-enum",
- new OpenApiObject
- {
- ["name"] = new OpenApiString(type.Name),
- ["modelAsString"] = new OpenApiBoolean(true)
- }
- );
+ var extensionData = new JsonObject
+ {
+ ["name"] = JsonValue.Create(type.Name),
+ ["modelAsString"] = JsonValue.Create(true)
+ };
+
+ parameter.Extensions.Add("x-ms-enum", new JsonNodeExtension(extensionData));
};
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Caster.Api/Infrastructure/Swashbuckle/SchemaFilters/AutoRestEnumSchemaFilter.cs b/src/Caster.Api/Infrastructure/Swashbuckle/SchemaFilters/AutoRestEnumSchemaFilter.cs
index 4af1ba3..d4ab2c2 100644
--- a/src/Caster.Api/Infrastructure/Swashbuckle/SchemaFilters/AutoRestEnumSchemaFilter.cs
+++ b/src/Caster.Api/Infrastructure/Swashbuckle/SchemaFilters/AutoRestEnumSchemaFilter.cs
@@ -1,27 +1,27 @@
// 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.OpenApi.Any;
-using Microsoft.OpenApi.Models;
+
+using System.Text.Json.Nodes;
+using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Caster.Api.Infrastructure.Swashbuckle.SchemaFilters
{
public class AutoRestEnumSchemaFilter : ISchemaFilter
{
- public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
{
var type = context.Type;
if (type.IsEnum)
{
- schema.Extensions.Add(
- "x-ms-enum",
- new OpenApiObject
- {
- ["name"] = new OpenApiString(type.Name),
- ["modelAsString"] = new OpenApiBoolean(true)
- }
- );
+ var extensionData = new JsonObject
+ {
+ ["name"] = JsonValue.Create(type.Name),
+ ["modelAsString"] = JsonValue.Create(true)
+ };
+
+ schema.Extensions.Add("x-ms-enum", new JsonNodeExtension(extensionData));
};
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Caster.Api/Startup.cs b/src/Caster.Api/Startup.cs
index 1985199..eb3eba1 100644
--- a/src/Caster.Api/Startup.cs
+++ b/src/Caster.Api/Startup.cs
@@ -39,6 +39,8 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
[assembly: ApiController]
namespace Caster.Api
@@ -52,7 +54,7 @@ public class Startup
private readonly TerraformOptions _terraformOptions = new TerraformOptions();
private readonly ILoggerFactory _loggerFactory;
private string _pathbase;
-
+ private readonly TelemetryOptions _telemetryOptions = new();
private const string _routePrefix = "api";
public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
@@ -61,6 +63,7 @@ public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
Configuration.GetSection("Authorization").Bind(_authOptions);
Configuration.GetSection("Client").Bind(_clientOptions);
Configuration.GetSection("Terraform").Bind(_terraformOptions);
+ Configuration.GetSection("Telemetry").Bind(_telemetryOptions);
_pathbase = Configuration["PathBase"] ?? "";
_loggerFactory = loggerFactory;
@@ -237,6 +240,52 @@ public void ConfigureServices(IServiceCollection services)
services.AddScoped();
services.AddTransient();
+
+ services.AddSingleton();
+ var metricsBuilder = services.AddOpenTelemetry()
+ .WithMetrics(builder =>
+ {
+ builder
+ .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("TelemetryService"))
+ .AddMeter
+ (
+ TelemetryService.CasterMeterName
+ )
+ .AddPrometheusExporter();
+ if (_telemetryOptions.AddRuntimeInstrumentation)
+ {
+ builder.AddRuntimeInstrumentation();
+ }
+ if (_telemetryOptions.AddProcessInstrumentation)
+ {
+ builder.AddProcessInstrumentation();
+ }
+ if (_telemetryOptions.AddAspNetCoreInstrumentation)
+ {
+ builder.AddAspNetCoreInstrumentation();
+ }
+ if (_telemetryOptions.AddHttpClientInstrumentation)
+ {
+ builder.AddHttpClientInstrumentation();
+ }
+ if (_telemetryOptions.UseMeterMicrosoftAspNetCoreHosting)
+ {
+ builder.AddMeter("Microsoft.AspNetCore.Hosting");
+ }
+ if (_telemetryOptions.UseMeterMicrosoftAspNetCoreServerKestrel)
+ {
+ builder.AddMeter("Microsoft.AspNetCore.Server.Kestrel");
+ }
+ if (_telemetryOptions.UseMeterSystemNetHttp)
+ {
+ builder.AddMeter("System.Net.Http");
+ }
+ if (_telemetryOptions.UseMeterSystemNetNameResolution)
+ {
+ builder.AddMeter("System.Net.NameResolution");
+ }
+ }
+ );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -264,6 +313,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
Predicate = (check) => check.Tags.Contains("live"),
});
endpoints.MapHub("/hubs/project");
+ endpoints.MapPrometheusScrapingEndpoint().RequireAuthorization();
});
app.UseSwagger();
diff --git a/src/Caster.Api/appsettings.json b/src/Caster.Api/appsettings.json
index dbf234e..c01fdc5 100644
--- a/src/Caster.Api/appsettings.json
+++ b/src/Caster.Api/appsettings.json
@@ -71,6 +71,16 @@
"DaysToSaveAllUntaggedVersions": 7,
"DaysToSaveDailyUntaggedVersions": 31
},
+ "Telemetry": {
+ "AddRuntimeInstrumentation": false,
+ "AddProcessInstrumentation": false,
+ "AddAspNetCoreInstrumentation": false,
+ "AddHttpClientInstrumentation": false,
+ "UseMeterMicrosoftAspNetCoreHosting": false,
+ "UseMeterMicrosoftAspNetCoreServerKestrel": false,
+ "UseMeterSystemNetHttp": false,
+ "UseMeterSystemNetNameResolution": false
+ },
"SeedData": {
"Roles": [
// {
diff --git a/test/Caster.Api.Tests/Caster.Api.Tests.csproj b/test/Caster.Api.Tests/Caster.Api.Tests.csproj
index a5ba2ba..891d5b4 100644
--- a/test/Caster.Api.Tests/Caster.Api.Tests.csproj
+++ b/test/Caster.Api.Tests/Caster.Api.Tests.csproj
@@ -1,13 +1,17 @@
- net8.0
+ net10.0
false
+ latest
-
-
-
-
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+