OPC # 0001: Extract OPC into standalone repo

This commit is contained in:
amadzarak
2026-04-25 17:26:42 -04:00
commit 42383bdc03
170 changed files with 21365 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ControlPlane.Core.Models;
public enum BuildStatus { Running, Succeeded, Failed }
public enum BuildKind { DockerImage, DotnetProject, NpmProject }
/// <summary>
/// Persisted record of a single build run — image build, dotnet build, or npm build.
/// Stored in ClientAssets/builds.json.
/// </summary>
public class BuildRecord
{
public string Id { get; set; } = Guid.NewGuid().ToString("N")[..8];
public BuildKind Kind { get; set; }
public string Target { get; set; } = string.Empty; // image name or project path
public BuildStatus Status { get; set; } = BuildStatus.Running;
public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? FinishedAt { get; set; }
public int? DurationMs { get; set; }
public string? ImageDigest { get; set; } // populated for DockerImage builds
public List<string> Log { get; set; } = [];
}
+26
View File
@@ -0,0 +1,26 @@
using System.Text.Json.Serialization;
namespace ControlPlane.Core.Models;
/// <summary>
/// Defines where a specific infrastructure component (Postgres, Keycloak, Vault, MinIO)
/// is hosted for a given tenant. Each component in a StackConfig is configured independently.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ComponentMode
{
/// <summary>Shared platform instance — logical slice only (realm, schema, bucket, namespace).</summary>
SharedPlatform,
/// <summary>Baked into the app image itself via supervisord. Trial tier only.</summary>
Bundled,
/// <summary>Own sidecar container on ControlPlane's shared Docker host.</summary>
OwnContainer,
/// <summary>Own VM with the component running inside Docker on it.</summary>
VpsDocker,
/// <summary>Own VM with the component running as a native OS process (no Docker).</summary>
VpsBareMetal
}
+10
View File
@@ -0,0 +1,10 @@
namespace ControlPlane.Core.Models;
public record GitCommit(
string Hash,
string ShortHash,
string Author,
string Date,
string Subject,
string[] Files
);
+63
View File
@@ -0,0 +1,63 @@
namespace ControlPlane.Core.Models;
// ── Repository ────────────────────────────────────────────────────────────────
public record GiteaRepo(
long Id,
string Name,
string FullName,
string DefaultBranch,
string CloneUrl,
string SshUrl,
bool Private
);
// ── Branch ────────────────────────────────────────────────────────────────────
public record GiteaBranch(
string Name,
string CommitSha,
bool Protected
);
// ── Pull Request ──────────────────────────────────────────────────────────────
public record GiteaPullRequest(
long Number,
string Title,
string State, // open | closed | merged
string HeadBranch,
string BaseBranch,
string HtmlUrl,
string CreatedAt,
string UpdatedAt,
GiteaUser? User,
GiteaMergeInfo? MergeInfo
);
public record GiteaUser(string Login, string AvatarUrl);
public record GiteaMergeInfo(bool Mergeable, bool Merged, string? MergedAt);
// ── Tag ───────────────────────────────────────────────────────────────────────
public record GiteaTag(string Name, string CommitSha, string ZipUrl);
// ── Webhook ───────────────────────────────────────────────────────────────────
public record GiteaWebhook(long Id, string Url, bool Active, string[] Events);
// ── Request shapes ────────────────────────────────────────────────────────────
public record CreateBranchRequest(string OpcNumber, string OpcTitle, string From = "master");
public record CreatePullRequestRequest(
string Title,
string Head,
string Base,
string Body
);
public record CreateTagRequest(string TagName, string Message, string CommitSha);
public record CreateWebhookRequest(string TargetUrl, string[] Events);
+73
View File
@@ -0,0 +1,73 @@
namespace ControlPlane.Core.Models;
public record OpcRecord(
Guid Id,
string Number,
string Title,
string Description,
string Type,
string Status,
string Priority,
string Assignee,
DateTime CreatedAt,
DateTime UpdatedAt
);
public record OpcNote(
Guid Id,
Guid OpcId,
string Author,
string Content,
DateTime CreatedAt
);
public record OpcArtifact(
Guid Id,
Guid OpcId,
string ArtifactType,
string Title,
string Content,
DateTime CreatedAt,
DateTime UpdatedAt
);
// Request / response shapes used by the API endpoints
public record CreateOpcRequest(
string Title,
string Type,
string Priority,
string Assignee,
string Description
);
public record UpdateOpcRequest(
string? Title,
string? Description,
string? Type,
string? Status,
string? Priority,
string? Assignee
);
public record AddNoteRequest(string Author, string Content);
public record UpsertArtifactRequest(
string ArtifactType,
string Title,
string Content
);
public record AiAssistRequest(string Prompt, string? Context);
public record OpcPinnedCommit(
Guid OpcId,
string Hash,
string ShortHash,
string Subject,
string Author,
DateTime PinnedAt,
string PinnedBy
);
public record PinCommitRequest(string Hash, string PinnedBy);
@@ -0,0 +1,22 @@
namespace ControlPlane.Core.Models;
public enum PromotionStatus { Pending, Running, Succeeded, Failed }
/// <summary>
/// Represents a request to promote (merge) one environment branch into the next.
/// e.g. develop → staging, staging → uat, uat → main
/// </summary>
public class PromotionRequest
{
public string Id { get; set; } = Guid.NewGuid().ToString("N")[..8];
public string FromBranch { get; set; } = string.Empty;
public string ToBranch { get; set; } = string.Empty;
public string RequestedBy { get; set; } = "system";
public string? Note { get; set; }
public PromotionStatus Status { get; set; } = PromotionStatus.Pending;
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? CompletedAt { get; set; }
public List<string> Log { get; set; } = [];
public int CommitCount { get; set; } // commits in from that are not in to
public string[] CommitLines { get; set; } = []; // oneline summary of those commits
}
@@ -0,0 +1,47 @@
namespace ControlPlane.Core.Models;
public enum ProvisioningStatus
{
Pending,
Running,
Compensating,
Failed,
Completed
}
[Flags]
public enum CompletedSteps
{
None = 0,
InfrastructureProvisioned = 1 << 0,
KeycloakProvisioned = 1 << 1,
VaultVerified = 1 << 2,
DatabaseMigrated = 1 << 3,
HandoffSent = 1 << 4
}
public class ProvisioningJob
{
public Guid Id { get; set; } = Guid.NewGuid();
public string ClientName { get; set; } = string.Empty;
public string StateCode { get; set; } = string.Empty;
public string Subdomain { get; set; } = string.Empty;
public string AdminEmail { get; set; } = string.Empty;
public string SiteCode { get; set; } = string.Empty;
public string Environment { get; set; } = "fdev";
public TenantTier Tier { get; set; } = TenantTier.Shared;
/// <summary>
/// Snapshot of the StackConfig at the time provisioning was requested.
/// Immutable after the job is created.
/// </summary>
public StackConfig StackConfig { get; set; } = StackConfig.DefaultForTier(TenantTier.Shared);
public ProvisioningStatus Status { get; set; } = ProvisioningStatus.Pending;
public CompletedSteps CompletedSteps { get; set; } = CompletedSteps.None;
public string? FailureReason { get; set; }
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? CompletedAt { get; set; }
}
@@ -0,0 +1,18 @@
namespace ControlPlane.Core.Models;
public class ProvisioningRequest
{
public string ClientName { get; set; } = string.Empty;
public string StateCode { get; set; } = string.Empty;
public string Subdomain { get; set; } = string.Empty;
public string AdminEmail { get; set; } = string.Empty;
public string SiteCode { get; set; } = string.Empty;
public string Environment { get; set; } = "fdev";
public TenantTier Tier { get; set; } = TenantTier.Shared;
/// <summary>
/// Per-component infrastructure configuration. Defaults to the standard profile
/// for the selected tier if not explicitly specified.
/// </summary>
public StackConfig StackConfig { get; set; } = StackConfig.DefaultForTier(TenantTier.Shared);
}
+27
View File
@@ -0,0 +1,27 @@
namespace ControlPlane.Core.Models;
public enum ReleaseStatus { Running, Succeeded, PartialFailure, Failed }
/// <summary>
/// Persisted record of a release — a coordinated redeploy of all tenant containers
/// in a target environment to the latest clarity-server image.
/// Stored in ClientAssets/releases.json.
/// </summary>
public class ReleaseRecord
{
public string Id { get; set; } = Guid.NewGuid().ToString("N")[..8];
public string Environment { get; set; } = string.Empty; // fdev | uat | prod | all
public string ImageName { get; set; } = string.Empty;
public ReleaseStatus Status { get; set; } = ReleaseStatus.Running;
public DateTimeOffset StartedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? FinishedAt { get; set; }
public List<TenantReleaseResult> Tenants { get; set; } = [];
}
public class TenantReleaseResult
{
public string Subdomain { get; set; } = string.Empty;
public string ContainerName { get; set; } = string.Empty;
public bool Success { get; set; }
public string? Error { get; set; }
}
+51
View File
@@ -0,0 +1,51 @@
namespace ControlPlane.Core.Models;
/// <summary>
/// Defines the exact infrastructure composition for a provisioned tenant.
/// Each component is configured independently — the TenantTier gates which
/// ComponentMode values are available in the UI.
///
/// Allowed modes per tier:
///
/// | Trial | Shared | Dedicated | Enterprise |
/// SharedPlatform | ✅ | ✅ | ✅ | ✅ |
/// Bundled | ✅ | ❌ | ❌ | ❌ |
/// OwnContainer | ❌ | ❌ | ✅ | ✅ |
/// VpsDocker | ❌ | ❌ | ❌ | ✅ |
/// VpsBareMetal | ❌ | ❌ | ❌ | ✅ |
/// </summary>
public class StackConfig
{
public ComponentMode Postgres { get; set; } = ComponentMode.SharedPlatform;
public ComponentMode Keycloak { get; set; } = ComponentMode.SharedPlatform;
public ComponentMode Vault { get; set; } = ComponentMode.SharedPlatform;
public ComponentMode Minio { get; set; } = ComponentMode.SharedPlatform;
/// <summary>Returns a default StackConfig for the given tier.</summary>
public static StackConfig DefaultForTier(TenantTier tier) => tier switch
{
TenantTier.Trial => new StackConfig
{
Postgres = ComponentMode.Bundled,
Keycloak = ComponentMode.SharedPlatform,
Vault = ComponentMode.SharedPlatform,
Minio = ComponentMode.SharedPlatform
},
TenantTier.Shared => new StackConfig(),
TenantTier.Dedicated => new StackConfig
{
Postgres = ComponentMode.OwnContainer,
Keycloak = ComponentMode.OwnContainer,
Vault = ComponentMode.OwnContainer,
Minio = ComponentMode.OwnContainer
},
TenantTier.Enterprise => new StackConfig
{
Postgres = ComponentMode.VpsDocker,
Keycloak = ComponentMode.VpsDocker,
Vault = ComponentMode.VpsDocker,
Minio = ComponentMode.VpsDocker
},
_ => new StackConfig()
};
}
+135
View File
@@ -0,0 +1,135 @@
using System.Xml.Serialization;
namespace ControlPlane.Core.Models;
[XmlRoot("Tenant")]
public class TenantRecord
{
// ── Identity ──────────────────────────────────────────────────────────
[XmlAttribute]
public string Subdomain { get; set; } = string.Empty;
[XmlElement]
public string ClientName { get; set; } = string.Empty;
[XmlElement]
public string StateCode { get; set; } = string.Empty;
[XmlElement]
public string AdminEmail { get; set; } = string.Empty;
[XmlElement]
public string SiteCode { get; set; } = string.Empty;
[XmlElement]
public string Environment { get; set; } = "fdev";
[XmlElement]
public string Tier { get; set; } = string.Empty;
[XmlElement]
public string Status { get; set; } = "Provisioning";
[XmlElement]
public string ProvisionedAt { get; set; } = DateTimeOffset.UtcNow.ToString("o");
[XmlElement]
public string JobId { get; set; } = string.Empty;
// ── Container (written by InfrastructureStep / LaunchStep) ────────────
[XmlElement(IsNullable = true)]
public string? ContainerName { get; set; }
[XmlElement(IsNullable = true)]
public string? ContainerPort { get; set; }
[XmlElement(IsNullable = true)]
public string? ContainerImage { get; set; }
[XmlElement(IsNullable = true)]
public string? ContainerNetwork { get; set; }
[XmlElement(IsNullable = true)]
public string? NginxConfPath { get; set; }
[XmlElement(IsNullable = true)]
public string? ApiBaseUrl { get; set; }
[XmlElement(IsNullable = true)]
public string? PublicUrl { get; set; }
[XmlElement(IsNullable = true)]
public string? LastProvisioningStep { get; set; }
[XmlElement(IsNullable = true)]
public string? ProvisioningNotes { get; set; }
// ── web.config-style sections ─────────────────────────────────────────
[XmlElement("ConnectionStrings")]
public ConnectionStringsSection ConnectionStrings { get; set; } = new();
[XmlElement("AppSettings")]
public AppSettingsSection AppSettings { get; set; } = new();
// ── Helpers ───────────────────────────────────────────────────────────
public void SetConnectionString(string name, string connectionString)
{
var existing = ConnectionStrings.Entries.FirstOrDefault(e => e.Name == name);
if (existing is not null)
existing.ConnectionString = connectionString;
else
ConnectionStrings.Entries.Add(new ConnectionStringEntry { Name = name, ConnectionString = connectionString });
}
public string? GetConnectionString(string name) =>
ConnectionStrings.Entries.FirstOrDefault(e => e.Name == name)?.ConnectionString;
public void SetAppSetting(string key, string value)
{
var existing = AppSettings.Entries.FirstOrDefault(e => e.Key == key);
if (existing is not null)
existing.Value = value;
else
AppSettings.Entries.Add(new AppSettingEntry { Key = key, Value = value });
}
public string? GetAppSetting(string key) =>
AppSettings.Entries.FirstOrDefault(e => e.Key == key)?.Value;
}
// ── Section types ──────────────────────────────────────────────────────────
public class ConnectionStringsSection
{
[XmlElement("add")]
public List<ConnectionStringEntry> Entries { get; set; } = [];
}
public class AppSettingsSection
{
[XmlElement("add")]
public List<AppSettingEntry> Entries { get; set; } = [];
}
public class ConnectionStringEntry
{
[XmlAttribute("name")]
public string Name { get; set; } = string.Empty;
[XmlAttribute("connectionString")]
public string ConnectionString { get; set; } = string.Empty;
[XmlAttribute("providerName")]
public string ProviderName { get; set; } = "System.Data.SqlClient";
}
public class AppSettingEntry
{
[XmlAttribute("key")]
public string Key { get; set; } = string.Empty;
[XmlAttribute("value")]
public string Value { get; set; } = string.Empty;
}
+21
View File
@@ -0,0 +1,21 @@
using System.Text.Json.Serialization;
namespace ControlPlane.Core.Models;
/// <summary>
/// Defines the billing and support level for a provisioned tenant.
/// The tier gates which ComponentMode values are available per component in the StackConfig.
///
/// Trial - ephemeral sandbox, all-in-one image, no persistent data guarantee.
/// Shared - real production data, shared platform infrastructure (logical slices only).
/// Dedicated - full container isolation per component, still on ControlPlane's shared host.
/// Enterprise - full VM isolation per component (VpsDocker or VpsBareMetal), Pulumi provisioned.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum TenantTier
{
Trial,
Shared,
Dedicated,
Enterprise
}