refactor(scripts): move Scripts API from scheduler to release-orchestrator
- Fix dual-schema violation (scheduler was writing to scheduler + scripts) - Move ScriptsDataSource, PostgresScriptStore, script endpoints - Update gateway routes and UI references - Each service now owns exactly one schema Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -688,10 +688,10 @@ services:
|
|||||||
labels: *release-labels
|
labels: *release-labels
|
||||||
|
|
||||||
# --- Slot 10: Excititor ----------------------------------------------------
|
# --- Slot 10: Excititor ----------------------------------------------------
|
||||||
excititor:
|
excititor-web:
|
||||||
<<: *resources-medium
|
<<: *resources-medium
|
||||||
image: stellaops/excititor:dev
|
image: stellaops/excititor-web:dev
|
||||||
container_name: stellaops-excititor
|
container_name: stellaops-excititor-web
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
ASPNETCORE_URLS: "http://+:8080"
|
ASPNETCORE_URLS: "http://+:8080"
|
||||||
@@ -962,6 +962,9 @@ services:
|
|||||||
Authority__ResourceServer__BypassNetworks__2: "::1/128"
|
Authority__ResourceServer__BypassNetworks__2: "::1/128"
|
||||||
Authority__ResourceServer__BypassNetworks__3: "0.0.0.0/0"
|
Authority__ResourceServer__BypassNetworks__3: "0.0.0.0/0"
|
||||||
Authority__ResourceServer__BypassNetworks__4: "::/0"
|
Authority__ResourceServer__BypassNetworks__4: "::/0"
|
||||||
|
# Scripts schema (moved from scheduler to release-orchestrator)
|
||||||
|
Scripts__Postgres__ConnectionString: "${STELLAOPS_POSTGRES_CONNECTION}"
|
||||||
|
Scripts__Postgres__SchemaName: "scripts"
|
||||||
Router__Enabled: "${RELEASE_ORCHESTRATOR_ROUTER_ENABLED:-true}"
|
Router__Enabled: "${RELEASE_ORCHESTRATOR_ROUTER_ENABLED:-true}"
|
||||||
Router__Messaging__ConsumerGroup: "release-orchestrator"
|
Router__Messaging__ConsumerGroup: "release-orchestrator"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -1709,10 +1712,10 @@ services:
|
|||||||
labels: *release-labels
|
labels: *release-labels
|
||||||
|
|
||||||
# --- Slot 40: ExportCenter -------------------------------------------------
|
# --- Slot 40: ExportCenter -------------------------------------------------
|
||||||
export:
|
export-web:
|
||||||
<<: *resources-light
|
<<: *resources-light
|
||||||
image: stellaops/export:dev
|
image: stellaops/export-web:dev
|
||||||
container_name: stellaops-export
|
container_name: stellaops-export-web
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
ASPNETCORE_URLS: "http://+:8080"
|
ASPNETCORE_URLS: "http://+:8080"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
CREATE SCHEMA IF NOT EXISTS scanner;
|
CREATE SCHEMA IF NOT EXISTS scanner;
|
||||||
CREATE SCHEMA IF NOT EXISTS vex;
|
CREATE SCHEMA IF NOT EXISTS vex;
|
||||||
CREATE SCHEMA IF NOT EXISTS scheduler;
|
CREATE SCHEMA IF NOT EXISTS scheduler;
|
||||||
|
CREATE SCHEMA IF NOT EXISTS scripts;
|
||||||
CREATE SCHEMA IF NOT EXISTS policy;
|
CREATE SCHEMA IF NOT EXISTS policy;
|
||||||
CREATE SCHEMA IF NOT EXISTS notify;
|
CREATE SCHEMA IF NOT EXISTS notify;
|
||||||
CREATE SCHEMA IF NOT EXISTS notifier;
|
CREATE SCHEMA IF NOT EXISTS notifier;
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
{ "Type": "Microservice", "Path": "^/api/v2/topology(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v2/topology$1" },
|
{ "Type": "Microservice", "Path": "^/api/v2/topology(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v2/topology$1" },
|
||||||
{ "Type": "Microservice", "Path": "^/api/v2/evidence(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v2/evidence$1" },
|
{ "Type": "Microservice", "Path": "^/api/v2/evidence(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v2/evidence$1" },
|
||||||
{ "Type": "Microservice", "Path": "^/api/v2/integrations(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v2/integrations$1" },
|
{ "Type": "Microservice", "Path": "^/api/v2/integrations(.*)", "IsRegex": true, "TranslatesTo": "http://platform.stella-ops.local/api/v2/integrations$1" },
|
||||||
{ "Type": "Microservice", "Path": "^/api/v2/scripts(.*)", "IsRegex": true, "TranslatesTo": "http://scheduler.stella-ops.local/api/v2/scripts$1" },
|
{ "Type": "Microservice", "Path": "^/api/v2/scripts(.*)", "IsRegex": true, "TranslatesTo": "http://release-orchestrator.stella-ops.local/api/v2/scripts$1" },
|
||||||
|
|
||||||
{ "Type": "Microservice", "Path": "^/api/v1/([^/]+)(.*)", "IsRegex": true, "TranslatesTo": "http://$1.stella-ops.local/api/v1/$1$2" },
|
{ "Type": "Microservice", "Path": "^/api/v1/([^/]+)(.*)", "IsRegex": true, "TranslatesTo": "http://$1.stella-ops.local/api/v1/$1$2" },
|
||||||
{ "Type": "Microservice", "Path": "^/api/v2/([^/]+)(.*)", "IsRegex": true, "TranslatesTo": "http://$1.stella-ops.local/api/v2/$1$2" },
|
{ "Type": "Microservice", "Path": "^/api/v2/([^/]+)(.*)", "IsRegex": true, "TranslatesTo": "http://$1.stella-ops.local/api/v2/$1$2" },
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using StellaOps.Auth.ServerIntegration.Tenancy;
|
|||||||
using StellaOps.Plugin.DependencyInjection;
|
using StellaOps.Plugin.DependencyInjection;
|
||||||
using StellaOps.Plugin.Hosting;
|
using StellaOps.Plugin.Hosting;
|
||||||
using StellaOps.Router.AspNet;
|
using StellaOps.Router.AspNet;
|
||||||
|
using StellaOps.Scheduler.Plugin;
|
||||||
using StellaOps.Scheduler.ImpactIndex;
|
using StellaOps.Scheduler.ImpactIndex;
|
||||||
using StellaOps.Scheduler.Models;
|
using StellaOps.Scheduler.Models;
|
||||||
using StellaOps.Scheduler.Persistence.Extensions;
|
using StellaOps.Scheduler.Persistence.Extensions;
|
||||||
@@ -29,12 +30,8 @@ using StellaOps.Scheduler.WebService.PolicyRuns;
|
|||||||
using StellaOps.Scheduler.WebService.PolicySimulations;
|
using StellaOps.Scheduler.WebService.PolicySimulations;
|
||||||
using StellaOps.Scheduler.WebService.Runs;
|
using StellaOps.Scheduler.WebService.Runs;
|
||||||
using StellaOps.Scheduler.WebService.Schedules;
|
using StellaOps.Scheduler.WebService.Schedules;
|
||||||
using StellaOps.Scheduler.WebService.Scripts;
|
|
||||||
using StellaOps.Scheduler.WebService.Exceptions;
|
using StellaOps.Scheduler.WebService.Exceptions;
|
||||||
using StellaOps.Scheduler.WebService.VulnerabilityResolverJobs;
|
using StellaOps.Scheduler.WebService.VulnerabilityResolverJobs;
|
||||||
using StellaOps.ReleaseOrchestrator.Scripts;
|
|
||||||
using StellaOps.ReleaseOrchestrator.Scripts.Persistence;
|
|
||||||
using StellaOps.ReleaseOrchestrator.Scripts.Search;
|
|
||||||
using StellaOps.Scheduler.Worker.Exceptions;
|
using StellaOps.Scheduler.Worker.Exceptions;
|
||||||
using StellaOps.Scheduler.Worker.Observability;
|
using StellaOps.Scheduler.Worker.Observability;
|
||||||
using StellaOps.Scheduler.Worker.Options;
|
using StellaOps.Scheduler.Worker.Options;
|
||||||
@@ -123,16 +120,6 @@ else
|
|||||||
builder.Services.AddSingleton<ISchedulerAuditService, InMemorySchedulerAuditService>();
|
builder.Services.AddSingleton<ISchedulerAuditService, InMemorySchedulerAuditService>();
|
||||||
builder.Services.AddSingleton<IPolicyRunService, InMemoryPolicyRunService>();
|
builder.Services.AddSingleton<IPolicyRunService, InMemoryPolicyRunService>();
|
||||||
}
|
}
|
||||||
// Scripts registry (shares the same Postgres options as Scheduler)
|
|
||||||
builder.Services.AddSingleton<ScriptsDataSource>();
|
|
||||||
builder.Services.AddSingleton<IScriptStore, PostgresScriptStore>();
|
|
||||||
builder.Services.AddSingleton<ISearchIndexer, InMemorySearchIndexer>();
|
|
||||||
builder.Services.AddSingleton<IScriptValidator, ScriptValidator>();
|
|
||||||
builder.Services.AddSingleton<ILanguageValidator, CSharpScriptValidator>();
|
|
||||||
builder.Services.AddSingleton<ILanguageValidator, PythonScriptValidator>();
|
|
||||||
builder.Services.AddSingleton<ILanguageValidator, TypeScriptScriptValidator>();
|
|
||||||
builder.Services.AddSingleton<IScriptRegistry, ScriptRegistry>();
|
|
||||||
|
|
||||||
// Workflow engine HTTP client (starts workflow instances for system schedules)
|
// Workflow engine HTTP client (starts workflow instances for system schedules)
|
||||||
builder.Services.AddHttpClient<StellaOps.Scheduler.WebService.Workflow.WorkflowTriggerClient>((sp, client) =>
|
builder.Services.AddHttpClient<StellaOps.Scheduler.WebService.Workflow.WorkflowTriggerClient>((sp, client) =>
|
||||||
{
|
{
|
||||||
@@ -188,6 +175,28 @@ var pluginHostOptions = SchedulerPluginHostFactory.Build(schedulerOptions.Plugin
|
|||||||
builder.Services.AddSingleton(pluginHostOptions);
|
builder.Services.AddSingleton(pluginHostOptions);
|
||||||
builder.Services.RegisterPluginRoutines(builder.Configuration, pluginHostOptions);
|
builder.Services.RegisterPluginRoutines(builder.Configuration, pluginHostOptions);
|
||||||
|
|
||||||
|
// Scheduler plugin registry: discover and register ISchedulerJobPlugin implementations
|
||||||
|
var pluginRegistry = new SchedulerPluginRegistry();
|
||||||
|
|
||||||
|
// Register built-in scan plugin (default for all existing schedules)
|
||||||
|
var scanPlugin = new ScanJobPlugin();
|
||||||
|
pluginRegistry.Register(scanPlugin);
|
||||||
|
|
||||||
|
// Discover ISchedulerJobPlugin implementations from assembly-loaded plugins
|
||||||
|
var loadResult = PluginHost.LoadPlugins(pluginHostOptions);
|
||||||
|
foreach (var pluginAssembly in loadResult.Plugins)
|
||||||
|
{
|
||||||
|
var jobPlugins = StellaOps.Plugin.PluginLoader.LoadPlugins<ISchedulerJobPlugin>(
|
||||||
|
new[] { pluginAssembly.Assembly });
|
||||||
|
foreach (var jobPlugin in jobPlugins)
|
||||||
|
{
|
||||||
|
pluginRegistry.Register(jobPlugin);
|
||||||
|
jobPlugin.ConfigureServices(builder.Services, builder.Configuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<ISchedulerPluginRegistry>(pluginRegistry);
|
||||||
|
|
||||||
if (authorityOptions.Enabled)
|
if (authorityOptions.Enabled)
|
||||||
{
|
{
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
@@ -319,7 +328,13 @@ app.MapFailureSignatureEndpoints();
|
|||||||
app.MapPolicyRunEndpoints();
|
app.MapPolicyRunEndpoints();
|
||||||
app.MapPolicySimulationEndpoints();
|
app.MapPolicySimulationEndpoints();
|
||||||
app.MapSchedulerEventWebhookEndpoints();
|
app.MapSchedulerEventWebhookEndpoints();
|
||||||
app.MapScriptsEndpoints();
|
|
||||||
|
// Map plugin-provided endpoints (e.g., Doctor trend endpoints)
|
||||||
|
foreach (var (jobKind, _) in pluginRegistry.ListRegistered())
|
||||||
|
{
|
||||||
|
var plugin = pluginRegistry.Resolve(jobKind);
|
||||||
|
plugin?.MapEndpoints(app);
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh Router endpoint cache
|
// Refresh Router endpoint cache
|
||||||
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Models/StellaOps.Scheduler.Models.csproj" />
|
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Models/StellaOps.Scheduler.Models.csproj" />
|
||||||
|
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Plugin.Abstractions/StellaOps.Scheduler.Plugin.Abstractions.csproj" />
|
||||||
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.ImpactIndex/StellaOps.Scheduler.ImpactIndex.csproj" />
|
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.ImpactIndex/StellaOps.Scheduler.ImpactIndex.csproj" />
|
||||||
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Queue/StellaOps.Scheduler.Queue.csproj" />
|
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Queue/StellaOps.Scheduler.Queue.csproj" />
|
||||||
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Persistence/StellaOps.Scheduler.Persistence.csproj" />
|
<ProjectReference Include="../StellaOps.Scheduler.__Libraries/StellaOps.Scheduler.Persistence/StellaOps.Scheduler.Persistence.csproj" />
|
||||||
@@ -23,7 +24,6 @@
|
|||||||
<ProjectReference Include="../../Router/__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
|
<ProjectReference Include="../../Router/__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj" />
|
||||||
<ProjectReference Include="../../Router/__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
|
<ProjectReference Include="../../Router/__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
|
||||||
<ProjectReference Include="../../__Libraries/StellaOps.Localization/StellaOps.Localization.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.Localization/StellaOps.Localization.csproj" />
|
||||||
<ProjectReference Include="../../ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Scripts/StellaOps.ReleaseOrchestrator.Scripts.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Translations\*.json" />
|
<EmbeddedResource Include="Translations\*.json" />
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||||
using StellaOps.ReleaseOrchestrator.Scripts;
|
using StellaOps.ReleaseOrchestrator.Scripts;
|
||||||
using StellaOps.Scheduler.WebService.Auth;
|
|
||||||
using StellaOps.Scheduler.WebService.Security;
|
|
||||||
|
|
||||||
namespace StellaOps.Scheduler.WebService.Scripts;
|
namespace StellaOps.ReleaseOrchestrator.WebApi.Endpoints;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Minimal API endpoints for the Scripts registry (/api/v2/scripts).
|
/// Minimal API endpoints for the Scripts registry (/api/v2/scripts).
|
||||||
|
/// Moved from Scheduler to Release-Orchestrator so each service owns exactly one schema.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class ScriptsEndpoints
|
internal static class ScriptsEndpoints
|
||||||
{
|
{
|
||||||
@@ -26,7 +25,7 @@ internal static class ScriptsEndpoints
|
|||||||
public static IEndpointRouteBuilder MapScriptsEndpoints(this IEndpointRouteBuilder routes)
|
public static IEndpointRouteBuilder MapScriptsEndpoints(this IEndpointRouteBuilder routes)
|
||||||
{
|
{
|
||||||
var group = routes.MapGroup("/api/v2/scripts")
|
var group = routes.MapGroup("/api/v2/scripts")
|
||||||
.RequireAuthorization(SchedulerPolicies.Read)
|
.RequireAuthorization(ReleaseOrchestratorPolicies.Read)
|
||||||
.RequireTenant();
|
.RequireTenant();
|
||||||
|
|
||||||
group.MapGet("/", ListScriptsAsync)
|
group.MapGet("/", ListScriptsAsync)
|
||||||
@@ -40,17 +39,17 @@ internal static class ScriptsEndpoints
|
|||||||
group.MapPost("/", CreateScriptAsync)
|
group.MapPost("/", CreateScriptAsync)
|
||||||
.WithName("CreateScript")
|
.WithName("CreateScript")
|
||||||
.WithDescription("Create a new script.")
|
.WithDescription("Create a new script.")
|
||||||
.RequireAuthorization(SchedulerPolicies.Operate);
|
.RequireAuthorization(ReleaseOrchestratorPolicies.Operate);
|
||||||
|
|
||||||
group.MapPatch("/{scriptId}", UpdateScriptAsync)
|
group.MapPatch("/{scriptId}", UpdateScriptAsync)
|
||||||
.WithName("UpdateScript")
|
.WithName("UpdateScript")
|
||||||
.WithDescription("Update an existing script.")
|
.WithDescription("Update an existing script.")
|
||||||
.RequireAuthorization(SchedulerPolicies.Operate);
|
.RequireAuthorization(ReleaseOrchestratorPolicies.Operate);
|
||||||
|
|
||||||
group.MapDelete("/{scriptId}", DeleteScriptAsync)
|
group.MapDelete("/{scriptId}", DeleteScriptAsync)
|
||||||
.WithName("DeleteScript")
|
.WithName("DeleteScript")
|
||||||
.WithDescription("Delete a script.")
|
.WithDescription("Delete a script.")
|
||||||
.RequireAuthorization(SchedulerPolicies.Operate);
|
.RequireAuthorization(ReleaseOrchestratorPolicies.Operate);
|
||||||
|
|
||||||
group.MapPost("/validate", ValidateScriptAsync)
|
group.MapPost("/validate", ValidateScriptAsync)
|
||||||
.WithName("ValidateScript")
|
.WithName("ValidateScript")
|
||||||
@@ -71,7 +70,7 @@ internal static class ScriptsEndpoints
|
|||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── List ────────────────────────────────────────────────────────────────
|
// -- List ----------------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> ListScriptsAsync(
|
private static async Task<IResult> ListScriptsAsync(
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
@@ -100,7 +99,7 @@ internal static class ScriptsEndpoints
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Get ─────────────────────────────────────────────────────────────────
|
// -- Get -----------------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> GetScriptAsync(
|
private static async Task<IResult> GetScriptAsync(
|
||||||
string scriptId,
|
string scriptId,
|
||||||
@@ -112,12 +111,12 @@ internal static class ScriptsEndpoints
|
|||||||
return Results.Json(ToDto(script), s_json);
|
return Results.Json(ToDto(script), s_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Create ──────────────────────────────────────────────────────────────
|
// -- Create --------------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> CreateScriptAsync(
|
private static async Task<IResult> CreateScriptAsync(
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
[FromServices] IScriptRegistry registry,
|
[FromServices] IScriptRegistry registry,
|
||||||
[FromServices] ITenantContextAccessor tenantAccessor,
|
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -125,7 +124,7 @@ internal static class ScriptsEndpoints
|
|||||||
var body = await JsonSerializer.DeserializeAsync<CreateScriptDto>(httpContext.Request.Body, s_json, ct).ConfigureAwait(false);
|
var body = await JsonSerializer.DeserializeAsync<CreateScriptDto>(httpContext.Request.Body, s_json, ct).ConfigureAwait(false);
|
||||||
if (body is null) return Results.BadRequest(new { error = "Invalid request body." });
|
if (body is null) return Results.BadRequest(new { error = "Invalid request body." });
|
||||||
|
|
||||||
var tenant = tenantAccessor.GetTenant(httpContext);
|
var tenantId = tenantAccessor.TenantId;
|
||||||
var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
|
var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
|
||||||
|
|
||||||
var request = new CreateScriptRequest
|
var request = new CreateScriptRequest
|
||||||
@@ -140,7 +139,6 @@ internal static class ScriptsEndpoints
|
|||||||
|
|
||||||
var script = await registry.CreateScriptAsync(request, userId, ct: ct).ConfigureAwait(false);
|
var script = await registry.CreateScriptAsync(request, userId, ct: ct).ConfigureAwait(false);
|
||||||
|
|
||||||
// Update variables on the created script via store if provided
|
|
||||||
return Results.Json(ToDto(script), s_json, statusCode: StatusCodes.Status201Created);
|
return Results.Json(ToDto(script), s_json, statusCode: StatusCodes.Status201Created);
|
||||||
}
|
}
|
||||||
catch (ScriptValidationException ex)
|
catch (ScriptValidationException ex)
|
||||||
@@ -153,13 +151,13 @@ internal static class ScriptsEndpoints
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Update ──────────────────────────────────────────────────────────────
|
// -- Update --------------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> UpdateScriptAsync(
|
private static async Task<IResult> UpdateScriptAsync(
|
||||||
string scriptId,
|
string scriptId,
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
[FromServices] IScriptRegistry registry,
|
[FromServices] IScriptRegistry registry,
|
||||||
[FromServices] ITenantContextAccessor tenantAccessor,
|
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -167,7 +165,7 @@ internal static class ScriptsEndpoints
|
|||||||
var body = await JsonSerializer.DeserializeAsync<UpdateScriptDto>(httpContext.Request.Body, s_json, ct).ConfigureAwait(false);
|
var body = await JsonSerializer.DeserializeAsync<UpdateScriptDto>(httpContext.Request.Body, s_json, ct).ConfigureAwait(false);
|
||||||
if (body is null) return Results.BadRequest(new { error = "Invalid request body." });
|
if (body is null) return Results.BadRequest(new { error = "Invalid request body." });
|
||||||
|
|
||||||
var tenant = tenantAccessor.GetTenant(httpContext);
|
var tenantId = tenantAccessor.TenantId;
|
||||||
var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
|
var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
|
||||||
|
|
||||||
var request = new UpdateScriptRequest
|
var request = new UpdateScriptRequest
|
||||||
@@ -197,7 +195,7 @@ internal static class ScriptsEndpoints
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Delete ──────────────────────────────────────────────────────────────
|
// -- Delete --------------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> DeleteScriptAsync(
|
private static async Task<IResult> DeleteScriptAsync(
|
||||||
string scriptId,
|
string scriptId,
|
||||||
@@ -210,7 +208,7 @@ internal static class ScriptsEndpoints
|
|||||||
: Results.NotFound(new { error = $"Script '{scriptId}' not found." });
|
: Results.NotFound(new { error = $"Script '{scriptId}' not found." });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Validate ────────────────────────────────────────────────────────────
|
// -- Validate ------------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> ValidateScriptAsync(
|
private static async Task<IResult> ValidateScriptAsync(
|
||||||
HttpContext httpContext,
|
HttpContext httpContext,
|
||||||
@@ -253,7 +251,7 @@ internal static class ScriptsEndpoints
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Versions ────────────────────────────────────────────────────────────
|
// -- Versions ------------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> GetVersionsAsync(
|
private static async Task<IResult> GetVersionsAsync(
|
||||||
string scriptId,
|
string scriptId,
|
||||||
@@ -293,7 +291,7 @@ internal static class ScriptsEndpoints
|
|||||||
return Results.Json(dto, s_json);
|
return Results.Json(dto, s_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Compatibility ───────────────────────────────────────────────────────
|
// -- Compatibility -------------------------------------------------------
|
||||||
|
|
||||||
private static async Task<IResult> CheckCompatibilityAsync(
|
private static async Task<IResult> CheckCompatibilityAsync(
|
||||||
string scriptId,
|
string scriptId,
|
||||||
@@ -309,7 +307,7 @@ internal static class ScriptsEndpoints
|
|||||||
return Results.Json(response, s_json);
|
return Results.Json(response, s_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── DTO mapping ─────────────────────────────────────────────────────────
|
// -- DTO mapping ---------------------------------------------------------
|
||||||
|
|
||||||
private static object ToDto(Script s) => new
|
private static object ToDto(Script s) => new
|
||||||
{
|
{
|
||||||
@@ -338,7 +336,7 @@ internal static class ScriptsEndpoints
|
|||||||
updatedAt = s.UpdatedAt,
|
updatedAt = s.UpdatedAt,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── DTO types ───────────────────────────────────────────────────────────
|
// -- DTO types -----------------------------------------------------------
|
||||||
|
|
||||||
private sealed record CreateScriptDto
|
private sealed record CreateScriptDto
|
||||||
{
|
{
|
||||||
@@ -377,7 +375,7 @@ internal static class ScriptsEndpoints
|
|||||||
public bool IsSecret { get; init; }
|
public bool IsSecret { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Enum parsing ────────────────────────────────────────────────────────
|
// -- Enum parsing --------------------------------------------------------
|
||||||
|
|
||||||
private static ScriptLanguage? ParseLanguage(string? value) => value?.ToLowerInvariant() switch
|
private static ScriptLanguage? ParseLanguage(string? value) => value?.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
using StellaOps.Router.AspNet;
|
using StellaOps.Router.AspNet;
|
||||||
using StellaOps.Auth.ServerIntegration;
|
using StellaOps.Auth.ServerIntegration;
|
||||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||||
|
using StellaOps.Infrastructure.Postgres.Options;
|
||||||
using StellaOps.JobEngine.Infrastructure;
|
using StellaOps.JobEngine.Infrastructure;
|
||||||
|
using StellaOps.ReleaseOrchestrator.Scripts;
|
||||||
|
using StellaOps.ReleaseOrchestrator.Scripts.Persistence;
|
||||||
|
using StellaOps.ReleaseOrchestrator.Scripts.Search;
|
||||||
using StellaOps.ReleaseOrchestrator.WebApi;
|
using StellaOps.ReleaseOrchestrator.WebApi;
|
||||||
using StellaOps.ReleaseOrchestrator.WebApi.Endpoints;
|
using StellaOps.ReleaseOrchestrator.WebApi.Endpoints;
|
||||||
using StellaOps.ReleaseOrchestrator.WebApi.Services;
|
using StellaOps.ReleaseOrchestrator.WebApi.Services;
|
||||||
@@ -40,6 +44,32 @@ builder.Services.AddSingleton<InMemoryDeploymentCompatibilityStore>();
|
|||||||
builder.Services.AddSingleton<IDeploymentCompatibilityStore>(sp =>
|
builder.Services.AddSingleton<IDeploymentCompatibilityStore>(sp =>
|
||||||
sp.GetRequiredService<InMemoryDeploymentCompatibilityStore>());
|
sp.GetRequiredService<InMemoryDeploymentCompatibilityStore>());
|
||||||
|
|
||||||
|
// Scripts registry (owns the 'scripts' schema — moved from scheduler to fix dual-schema violation)
|
||||||
|
var scriptsSection = builder.Configuration.GetSection("Scripts:Postgres");
|
||||||
|
if (scriptsSection.Exists())
|
||||||
|
{
|
||||||
|
builder.Services.Configure<PostgresOptions>(scriptsSection);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback: reuse the default connection string with scripts schema
|
||||||
|
builder.Services.Configure<PostgresOptions>(opt =>
|
||||||
|
{
|
||||||
|
opt.ConnectionString = builder.Configuration.GetConnectionString("Default")
|
||||||
|
?? builder.Configuration["ConnectionStrings__Default"]
|
||||||
|
?? "Host=localhost;Database=stellaops_platform;Username=stellaops;Password=stellaops";
|
||||||
|
opt.SchemaName = "scripts";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
builder.Services.AddSingleton<ScriptsDataSource>();
|
||||||
|
builder.Services.AddSingleton<IScriptStore, PostgresScriptStore>();
|
||||||
|
builder.Services.AddSingleton<ISearchIndexer, InMemorySearchIndexer>();
|
||||||
|
builder.Services.AddSingleton<IScriptValidator, ScriptValidator>();
|
||||||
|
builder.Services.AddSingleton<ILanguageValidator, CSharpScriptValidator>();
|
||||||
|
builder.Services.AddSingleton<ILanguageValidator, PythonScriptValidator>();
|
||||||
|
builder.Services.AddSingleton<ILanguageValidator, TypeScriptScriptValidator>();
|
||||||
|
builder.Services.AddSingleton<IScriptRegistry, ScriptRegistry>();
|
||||||
|
|
||||||
// Router integration
|
// Router integration
|
||||||
var routerEnabled = builder.Services.AddRouterMicroservice(
|
var routerEnabled = builder.Services.AddRouterMicroservice(
|
||||||
builder.Configuration,
|
builder.Configuration,
|
||||||
@@ -68,6 +98,7 @@ app.MapReleaseControlV2Endpoints();
|
|||||||
app.MapEvidenceEndpoints();
|
app.MapEvidenceEndpoints();
|
||||||
app.MapAuditEndpoints();
|
app.MapAuditEndpoints();
|
||||||
app.MapFirstSignalEndpoints();
|
app.MapFirstSignalEndpoints();
|
||||||
|
app.MapScriptsEndpoints();
|
||||||
|
|
||||||
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<ProjectReference Include="..\..\..\JobEngine\StellaOps.JobEngine\StellaOps.JobEngine.Core\StellaOps.JobEngine.Core.csproj" />
|
<ProjectReference Include="..\..\..\JobEngine\StellaOps.JobEngine\StellaOps.JobEngine.Core\StellaOps.JobEngine.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\..\JobEngine\StellaOps.JobEngine\StellaOps.JobEngine.Infrastructure\StellaOps.JobEngine.Infrastructure.csproj" />
|
<ProjectReference Include="..\..\..\JobEngine\StellaOps.JobEngine\StellaOps.JobEngine.Infrastructure\StellaOps.JobEngine.Infrastructure.csproj" />
|
||||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Localization\StellaOps.Localization.csproj" />
|
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Localization\StellaOps.Localization.csproj" />
|
||||||
|
<ProjectReference Include="..\..\__Libraries\StellaOps.ReleaseOrchestrator.Scripts\StellaOps.ReleaseOrchestrator.Scripts.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="StellaOpsReleaseVersion">
|
<PropertyGroup Label="StellaOpsReleaseVersion">
|
||||||
|
|||||||
Reference in New Issue
Block a user