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
|
||||
|
||||
# --- Slot 10: Excititor ----------------------------------------------------
|
||||
excititor:
|
||||
excititor-web:
|
||||
<<: *resources-medium
|
||||
image: stellaops/excititor:dev
|
||||
container_name: stellaops-excititor
|
||||
image: stellaops/excititor-web:dev
|
||||
container_name: stellaops-excititor-web
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ASPNETCORE_URLS: "http://+:8080"
|
||||
@@ -962,6 +962,9 @@ services:
|
||||
Authority__ResourceServer__BypassNetworks__2: "::1/128"
|
||||
Authority__ResourceServer__BypassNetworks__3: "0.0.0.0/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__Messaging__ConsumerGroup: "release-orchestrator"
|
||||
volumes:
|
||||
@@ -1709,10 +1712,10 @@ services:
|
||||
labels: *release-labels
|
||||
|
||||
# --- Slot 40: ExportCenter -------------------------------------------------
|
||||
export:
|
||||
export-web:
|
||||
<<: *resources-light
|
||||
image: stellaops/export:dev
|
||||
container_name: stellaops-export
|
||||
image: stellaops/export-web:dev
|
||||
container_name: stellaops-export-web
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ASPNETCORE_URLS: "http://+:8080"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
CREATE SCHEMA IF NOT EXISTS scanner;
|
||||
CREATE SCHEMA IF NOT EXISTS vex;
|
||||
CREATE SCHEMA IF NOT EXISTS scheduler;
|
||||
CREATE SCHEMA IF NOT EXISTS scripts;
|
||||
CREATE SCHEMA IF NOT EXISTS policy;
|
||||
CREATE SCHEMA IF NOT EXISTS notify;
|
||||
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/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/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/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.Hosting;
|
||||
using StellaOps.Router.AspNet;
|
||||
using StellaOps.Scheduler.Plugin;
|
||||
using StellaOps.Scheduler.ImpactIndex;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Persistence.Extensions;
|
||||
@@ -29,12 +30,8 @@ using StellaOps.Scheduler.WebService.PolicyRuns;
|
||||
using StellaOps.Scheduler.WebService.PolicySimulations;
|
||||
using StellaOps.Scheduler.WebService.Runs;
|
||||
using StellaOps.Scheduler.WebService.Schedules;
|
||||
using StellaOps.Scheduler.WebService.Scripts;
|
||||
using StellaOps.Scheduler.WebService.Exceptions;
|
||||
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.Observability;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
@@ -123,16 +120,6 @@ else
|
||||
builder.Services.AddSingleton<ISchedulerAuditService, InMemorySchedulerAuditService>();
|
||||
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)
|
||||
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.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)
|
||||
{
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
@@ -319,7 +328,13 @@ app.MapFailureSignatureEndpoints();
|
||||
app.MapPolicyRunEndpoints();
|
||||
app.MapPolicySimulationEndpoints();
|
||||
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
|
||||
app.TryRefreshStellaRouterEndpoints(routerEnabled);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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.Queue/StellaOps.Scheduler.Queue.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.Router.AspNet/StellaOps.Router.AspNet.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Localization/StellaOps.Localization.csproj" />
|
||||
<ProjectReference Include="../../ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Scripts/StellaOps.ReleaseOrchestrator.Scripts.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Translations\*.json" />
|
||||
|
||||
@@ -6,13 +6,12 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
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>
|
||||
/// Minimal API endpoints for the Scripts registry (/api/v2/scripts).
|
||||
/// Moved from Scheduler to Release-Orchestrator so each service owns exactly one schema.
|
||||
/// </summary>
|
||||
internal static class ScriptsEndpoints
|
||||
{
|
||||
@@ -26,7 +25,7 @@ internal static class ScriptsEndpoints
|
||||
public static IEndpointRouteBuilder MapScriptsEndpoints(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
var group = routes.MapGroup("/api/v2/scripts")
|
||||
.RequireAuthorization(SchedulerPolicies.Read)
|
||||
.RequireAuthorization(ReleaseOrchestratorPolicies.Read)
|
||||
.RequireTenant();
|
||||
|
||||
group.MapGet("/", ListScriptsAsync)
|
||||
@@ -40,17 +39,17 @@ internal static class ScriptsEndpoints
|
||||
group.MapPost("/", CreateScriptAsync)
|
||||
.WithName("CreateScript")
|
||||
.WithDescription("Create a new script.")
|
||||
.RequireAuthorization(SchedulerPolicies.Operate);
|
||||
.RequireAuthorization(ReleaseOrchestratorPolicies.Operate);
|
||||
|
||||
group.MapPatch("/{scriptId}", UpdateScriptAsync)
|
||||
.WithName("UpdateScript")
|
||||
.WithDescription("Update an existing script.")
|
||||
.RequireAuthorization(SchedulerPolicies.Operate);
|
||||
.RequireAuthorization(ReleaseOrchestratorPolicies.Operate);
|
||||
|
||||
group.MapDelete("/{scriptId}", DeleteScriptAsync)
|
||||
.WithName("DeleteScript")
|
||||
.WithDescription("Delete a script.")
|
||||
.RequireAuthorization(SchedulerPolicies.Operate);
|
||||
.RequireAuthorization(ReleaseOrchestratorPolicies.Operate);
|
||||
|
||||
group.MapPost("/validate", ValidateScriptAsync)
|
||||
.WithName("ValidateScript")
|
||||
@@ -71,7 +70,7 @@ internal static class ScriptsEndpoints
|
||||
return routes;
|
||||
}
|
||||
|
||||
// ── List ────────────────────────────────────────────────────────────────
|
||||
// -- List ----------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> ListScriptsAsync(
|
||||
HttpContext httpContext,
|
||||
@@ -100,7 +99,7 @@ internal static class ScriptsEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
// ── Get ─────────────────────────────────────────────────────────────────
|
||||
// -- Get -----------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> GetScriptAsync(
|
||||
string scriptId,
|
||||
@@ -112,12 +111,12 @@ internal static class ScriptsEndpoints
|
||||
return Results.Json(ToDto(script), s_json);
|
||||
}
|
||||
|
||||
// ── Create ──────────────────────────────────────────────────────────────
|
||||
// -- Create --------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> CreateScriptAsync(
|
||||
HttpContext httpContext,
|
||||
[FromServices] IScriptRegistry registry,
|
||||
[FromServices] ITenantContextAccessor tenantAccessor,
|
||||
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -125,7 +124,7 @@ internal static class ScriptsEndpoints
|
||||
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." });
|
||||
|
||||
var tenant = tenantAccessor.GetTenant(httpContext);
|
||||
var tenantId = tenantAccessor.TenantId;
|
||||
var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
|
||||
|
||||
var request = new CreateScriptRequest
|
||||
@@ -140,7 +139,6 @@ internal static class ScriptsEndpoints
|
||||
|
||||
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);
|
||||
}
|
||||
catch (ScriptValidationException ex)
|
||||
@@ -153,13 +151,13 @@ internal static class ScriptsEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
// ── Update ──────────────────────────────────────────────────────────────
|
||||
// -- Update --------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> UpdateScriptAsync(
|
||||
string scriptId,
|
||||
HttpContext httpContext,
|
||||
[FromServices] IScriptRegistry registry,
|
||||
[FromServices] ITenantContextAccessor tenantAccessor,
|
||||
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -167,7 +165,7 @@ internal static class ScriptsEndpoints
|
||||
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." });
|
||||
|
||||
var tenant = tenantAccessor.GetTenant(httpContext);
|
||||
var tenantId = tenantAccessor.TenantId;
|
||||
var userId = httpContext.User.FindFirst("sub")?.Value ?? "anonymous";
|
||||
|
||||
var request = new UpdateScriptRequest
|
||||
@@ -197,7 +195,7 @@ internal static class ScriptsEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete ──────────────────────────────────────────────────────────────
|
||||
// -- Delete --------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> DeleteScriptAsync(
|
||||
string scriptId,
|
||||
@@ -210,7 +208,7 @@ internal static class ScriptsEndpoints
|
||||
: Results.NotFound(new { error = $"Script '{scriptId}' not found." });
|
||||
}
|
||||
|
||||
// ── Validate ────────────────────────────────────────────────────────────
|
||||
// -- Validate ------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> ValidateScriptAsync(
|
||||
HttpContext httpContext,
|
||||
@@ -253,7 +251,7 @@ internal static class ScriptsEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
// ── Versions ────────────────────────────────────────────────────────────
|
||||
// -- Versions ------------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> GetVersionsAsync(
|
||||
string scriptId,
|
||||
@@ -293,7 +291,7 @@ internal static class ScriptsEndpoints
|
||||
return Results.Json(dto, s_json);
|
||||
}
|
||||
|
||||
// ── Compatibility ───────────────────────────────────────────────────────
|
||||
// -- Compatibility -------------------------------------------------------
|
||||
|
||||
private static async Task<IResult> CheckCompatibilityAsync(
|
||||
string scriptId,
|
||||
@@ -309,7 +307,7 @@ internal static class ScriptsEndpoints
|
||||
return Results.Json(response, s_json);
|
||||
}
|
||||
|
||||
// ── DTO mapping ─────────────────────────────────────────────────────────
|
||||
// -- DTO mapping ---------------------------------------------------------
|
||||
|
||||
private static object ToDto(Script s) => new
|
||||
{
|
||||
@@ -338,7 +336,7 @@ internal static class ScriptsEndpoints
|
||||
updatedAt = s.UpdatedAt,
|
||||
};
|
||||
|
||||
// ── DTO types ───────────────────────────────────────────────────────────
|
||||
// -- DTO types -----------------------------------------------------------
|
||||
|
||||
private sealed record CreateScriptDto
|
||||
{
|
||||
@@ -377,7 +375,7 @@ internal static class ScriptsEndpoints
|
||||
public bool IsSecret { get; init; }
|
||||
}
|
||||
|
||||
// ── Enum parsing ────────────────────────────────────────────────────────
|
||||
// -- Enum parsing --------------------------------------------------------
|
||||
|
||||
private static ScriptLanguage? ParseLanguage(string? value) => value?.ToLowerInvariant() switch
|
||||
{
|
||||
@@ -1,7 +1,11 @@
|
||||
using StellaOps.Router.AspNet;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
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.Endpoints;
|
||||
using StellaOps.ReleaseOrchestrator.WebApi.Services;
|
||||
@@ -40,6 +44,32 @@ builder.Services.AddSingleton<InMemoryDeploymentCompatibilityStore>();
|
||||
builder.Services.AddSingleton<IDeploymentCompatibilityStore>(sp =>
|
||||
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
|
||||
var routerEnabled = builder.Services.AddRouterMicroservice(
|
||||
builder.Configuration,
|
||||
@@ -68,6 +98,7 @@ app.MapReleaseControlV2Endpoints();
|
||||
app.MapEvidenceEndpoints();
|
||||
app.MapAuditEndpoints();
|
||||
app.MapFirstSignalEndpoints();
|
||||
app.MapScriptsEndpoints();
|
||||
|
||||
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.Infrastructure\StellaOps.JobEngine.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Localization\StellaOps.Localization.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.ReleaseOrchestrator.Scripts\StellaOps.ReleaseOrchestrator.Scripts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Label="StellaOpsReleaseVersion">
|
||||
|
||||
Reference in New Issue
Block a user