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