Complete release compatibility and host inventory sprints
Signed-off-by: master <>
This commit is contained in:
@@ -24,12 +24,16 @@ public static class AssistantEndpoints
|
||||
|
||||
group.MapGet("/tips", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
[FromQuery] string route,
|
||||
[FromQuery] string? locale,
|
||||
[FromQuery] string? contexts,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var effectiveLocale = locale ?? "en-US";
|
||||
var contextList = string.IsNullOrWhiteSpace(contexts)
|
||||
@@ -46,11 +50,15 @@ public static class AssistantEndpoints
|
||||
|
||||
group.MapGet("/glossary", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
[FromQuery] string? locale,
|
||||
[FromQuery] string? terms,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var effectiveLocale = locale ?? "en-US";
|
||||
var termList = string.IsNullOrWhiteSpace(terms)
|
||||
@@ -67,9 +75,13 @@ public static class AssistantEndpoints
|
||||
|
||||
group.MapGet("/user-state", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var (userId, tenantId) = ResolveUserContext(httpContext);
|
||||
var state = await store.GetUserStateAsync(userId, tenantId, ct);
|
||||
return state is not null ? Results.Ok(state) : Results.Ok(new AssistantUserStateDto(
|
||||
@@ -80,10 +92,14 @@ public static class AssistantEndpoints
|
||||
|
||||
group.MapPut("/user-state", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
AssistantUserStateDto state,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var (userId, tenantId) = ResolveUserContext(httpContext);
|
||||
await store.UpsertUserStateAsync(userId, tenantId, state, ct);
|
||||
return Results.Ok();
|
||||
@@ -96,11 +112,15 @@ public static class AssistantEndpoints
|
||||
|
||||
group.MapGet("/tours", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
[FromQuery] string? locale,
|
||||
[FromQuery] string? tourKey,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var effectiveLocale = locale ?? "en-US";
|
||||
var result = await store.GetToursAsync(effectiveLocale, tenantId, tourKey, ct);
|
||||
@@ -117,10 +137,14 @@ public static class AssistantEndpoints
|
||||
|
||||
admin.MapPost("/tips", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
UpsertAssistantTipRequest request,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var actor = ResolveUserId(httpContext);
|
||||
var id = await store.UpsertTipAsync(tenantId, request, actor, ct);
|
||||
@@ -130,10 +154,15 @@ public static class AssistantEndpoints
|
||||
.WithSummary("Create or update a tip");
|
||||
|
||||
admin.MapDelete("/tips/{tipId}", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
string tipId,
|
||||
PostgresAssistantStore store,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
await store.DeactivateTipAsync(tipId, ct);
|
||||
return Results.Ok();
|
||||
})
|
||||
@@ -142,11 +171,15 @@ public static class AssistantEndpoints
|
||||
|
||||
admin.MapGet("/tips", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
[FromQuery] string? locale,
|
||||
[FromQuery] string? route,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var result = await store.ListAllTipsAsync(tenantId, locale ?? "en-US", route, ct);
|
||||
return Results.Ok(result);
|
||||
@@ -156,10 +189,14 @@ public static class AssistantEndpoints
|
||||
|
||||
admin.MapGet("/tours", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
[FromQuery] string? locale,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var result = await store.ListAllToursAsync(tenantId, locale ?? "en-US", ct);
|
||||
return Results.Ok(result);
|
||||
@@ -169,10 +206,14 @@ public static class AssistantEndpoints
|
||||
|
||||
admin.MapPost("/tours", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
UpsertTourRequest request,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var id = await store.UpsertTourAsync(tenantId, request, ct);
|
||||
return Results.Ok(new { tourId = id });
|
||||
@@ -183,10 +224,14 @@ public static class AssistantEndpoints
|
||||
admin.MapGet("/tours/{tourKey}", async Task<IResult>(
|
||||
string tourKey,
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
[FromQuery] string? locale,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var tour = await store.GetTourByKeyAsync(tenantId, tourKey, locale ?? "en-US", ct);
|
||||
return tour is not null ? Results.Ok(tour) : Results.NotFound();
|
||||
@@ -196,10 +241,14 @@ public static class AssistantEndpoints
|
||||
|
||||
admin.MapPost("/glossary", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
PostgresAssistantStore store,
|
||||
UpsertGlossaryTermRequest request,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
if (!TryResolveStore(httpContext, out var store))
|
||||
{
|
||||
return AssistantStoreUnavailable();
|
||||
}
|
||||
|
||||
var tenantId = ResolveTenantId(httpContext);
|
||||
var id = await store.UpsertGlossaryTermAsync(tenantId, request, ct);
|
||||
return Results.Ok(new { termId = id });
|
||||
@@ -215,6 +264,20 @@ public static class AssistantEndpoints
|
||||
?? ctx.User.FindFirst("stellaops:user_id")?.Value
|
||||
?? "anonymous";
|
||||
|
||||
private static bool TryResolveStore(HttpContext context, out PostgresAssistantStore store)
|
||||
{
|
||||
store = context.RequestServices.GetService<PostgresAssistantStore>()!;
|
||||
return store is not null;
|
||||
}
|
||||
|
||||
private static IResult AssistantStoreUnavailable()
|
||||
{
|
||||
return Results.Problem(
|
||||
detail: "Assistant persistence is unavailable because the Platform service is running without PostgreSQL.",
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable,
|
||||
title: "assistant_store_unavailable");
|
||||
}
|
||||
|
||||
private static string ResolveTenantId(HttpContext ctx)
|
||||
=> ctx.Request.Headers["X-Tenant-Id"].FirstOrDefault() ?? "_system";
|
||||
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.Platform.WebService.Constants;
|
||||
using StellaOps.ReleaseOrchestrator.Environment.FreezeWindow;
|
||||
using StellaOps.ReleaseOrchestrator.Environment.Models;
|
||||
using StellaOps.ReleaseOrchestrator.Environment.Services;
|
||||
using StellaOps.ReleaseOrchestrator.Environment.Target;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Endpoints;
|
||||
|
||||
public static class ReleaseOrchestratorEnvironmentEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapReleaseOrchestratorEnvironmentEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var environments = app.MapGroup("/api/v1/release-orchestrator/environments")
|
||||
.WithTags("Release Orchestrator Environments")
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlRead)
|
||||
.RequireTenant();
|
||||
|
||||
environments.MapGet(string.Empty, ListEnvironments)
|
||||
.WithName("ListReleaseOrchestratorEnvironments")
|
||||
.WithSummary("List release orchestrator environments");
|
||||
|
||||
environments.MapGet("/{id:guid}", GetEnvironment)
|
||||
.WithName("GetReleaseOrchestratorEnvironment")
|
||||
.WithSummary("Get a release orchestrator environment");
|
||||
|
||||
environments.MapPost(string.Empty, CreateEnvironment)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("CreateReleaseOrchestratorEnvironment")
|
||||
.WithSummary("Create a release orchestrator environment");
|
||||
|
||||
environments.MapPut("/{id:guid}", UpdateEnvironment)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("UpdateReleaseOrchestratorEnvironment")
|
||||
.WithSummary("Update a release orchestrator environment");
|
||||
|
||||
environments.MapDelete("/{id:guid}", DeleteEnvironment)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("DeleteReleaseOrchestratorEnvironment")
|
||||
.WithSummary("Delete a release orchestrator environment");
|
||||
|
||||
environments.MapPut("/{id:guid}/settings", UpdateEnvironmentSettings)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("UpdateReleaseOrchestratorEnvironmentSettings")
|
||||
.WithSummary("Update environment release settings");
|
||||
|
||||
environments.MapGet("/{id:guid}/targets", ListTargets)
|
||||
.WithName("ListReleaseOrchestratorEnvironmentTargets")
|
||||
.WithSummary("List environment targets");
|
||||
|
||||
environments.MapPost("/{id:guid}/targets", CreateTarget)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("CreateReleaseOrchestratorEnvironmentTarget")
|
||||
.WithSummary("Create an environment target");
|
||||
|
||||
environments.MapPut("/{id:guid}/targets/{targetId:guid}", UpdateTarget)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("UpdateReleaseOrchestratorEnvironmentTarget")
|
||||
.WithSummary("Update an environment target");
|
||||
|
||||
environments.MapDelete("/{id:guid}/targets/{targetId:guid}", DeleteTarget)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("DeleteReleaseOrchestratorEnvironmentTarget")
|
||||
.WithSummary("Delete an environment target");
|
||||
|
||||
environments.MapPost("/{id:guid}/targets/{targetId:guid}/health-check", CheckTargetHealth)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("HealthCheckReleaseOrchestratorEnvironmentTarget")
|
||||
.WithSummary("Run a target health check");
|
||||
|
||||
environments.MapGet("/{id:guid}/freeze-windows", ListFreezeWindows)
|
||||
.WithName("ListReleaseOrchestratorFreezeWindows")
|
||||
.WithSummary("List environment freeze windows");
|
||||
|
||||
environments.MapPost("/{id:guid}/freeze-windows", CreateFreezeWindow)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("CreateReleaseOrchestratorFreezeWindow")
|
||||
.WithSummary("Create an environment freeze window");
|
||||
|
||||
environments.MapPut("/{id:guid}/freeze-windows/{freezeWindowId:guid}", UpdateFreezeWindow)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("UpdateReleaseOrchestratorFreezeWindow")
|
||||
.WithSummary("Update an environment freeze window");
|
||||
|
||||
environments.MapDelete("/{id:guid}/freeze-windows/{freezeWindowId:guid}", DeleteFreezeWindow)
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlOperate)
|
||||
.WithName("DeleteReleaseOrchestratorFreezeWindow")
|
||||
.WithSummary("Delete an environment freeze window");
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static async Task<IResult> ListEnvironments(
|
||||
IEnvironmentService environmentService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var items = await environmentService.ListOrderedAsync(cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(items);
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetEnvironment(
|
||||
Guid id,
|
||||
IEnvironmentService environmentService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var environment = await environmentService.GetAsync(id, cancellationToken).ConfigureAwait(false);
|
||||
return environment is not null
|
||||
? Results.Ok(environment)
|
||||
: Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
private static async Task<IResult> CreateEnvironment(
|
||||
CreateEnvironmentRequest request,
|
||||
IEnvironmentService environmentService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var created = await environmentService.CreateAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Created($"/api/v1/release-orchestrator/environments/{created.Id:D}", created);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = "environment_validation_failed", details = ex.Errors });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> UpdateEnvironment(
|
||||
Guid id,
|
||||
UpdateEnvironmentRequest request,
|
||||
IEnvironmentService environmentService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updated = await environmentService.UpdateAsync(id, request, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(updated);
|
||||
}
|
||||
catch (EnvironmentNotFoundException)
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = "environment_validation_failed", details = ex.Errors });
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<IResult> UpdateEnvironmentSettings(
|
||||
Guid id,
|
||||
UpdateEnvironmentSettingsRequest request,
|
||||
IEnvironmentService environmentService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return UpdateEnvironment(
|
||||
id,
|
||||
new UpdateEnvironmentRequest(
|
||||
RequiredApprovals: request.RequiredApprovals,
|
||||
RequireSeparationOfDuties: request.RequireSeparationOfDuties,
|
||||
AutoPromoteFrom: request.AutoPromoteFrom,
|
||||
DeploymentTimeoutSeconds: request.DeploymentTimeoutSeconds),
|
||||
environmentService,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<IResult> DeleteEnvironment(
|
||||
Guid id,
|
||||
IEnvironmentService environmentService,
|
||||
ITargetRegistry targetRegistry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var targets = await targetRegistry.ListByEnvironmentAsync(id, cancellationToken).ConfigureAwait(false);
|
||||
if (targets.Count > 0)
|
||||
{
|
||||
return Results.Conflict(new { error = "environment_has_targets", id, targetCount = targets.Count });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await environmentService.DeleteAsync(id, cancellationToken).ConfigureAwait(false);
|
||||
return Results.NoContent();
|
||||
}
|
||||
catch (EnvironmentNotFoundException)
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.Conflict(new { error = "environment_delete_blocked", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> ListTargets(
|
||||
Guid id,
|
||||
IEnvironmentService environmentService,
|
||||
ITargetRegistry targetRegistry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
var targets = await targetRegistry.ListByEnvironmentAsync(id, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(targets);
|
||||
}
|
||||
|
||||
private static async Task<IResult> CreateTarget(
|
||||
Guid id,
|
||||
CreateTargetRequest request,
|
||||
IEnvironmentService environmentService,
|
||||
ITargetRegistry targetRegistry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var created = await targetRegistry.RegisterAsync(
|
||||
new RegisterTargetRequest(
|
||||
id,
|
||||
request.Name,
|
||||
request.DisplayName,
|
||||
request.Type,
|
||||
request.ConnectionConfig,
|
||||
request.AgentId),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Created(
|
||||
$"/api/v1/release-orchestrator/environments/{id:D}/targets/{created.Id:D}",
|
||||
created);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = "target_validation_failed", details = ex.Errors });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> UpdateTarget(
|
||||
Guid id,
|
||||
Guid targetId,
|
||||
UpdateTargetRequest request,
|
||||
IEnvironmentService environmentService,
|
||||
ITargetRegistry targetRegistry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
var target = await targetRegistry.GetAsync(targetId, cancellationToken).ConfigureAwait(false);
|
||||
if (target is null || target.EnvironmentId != id)
|
||||
{
|
||||
return Results.NotFound(new { error = "target_not_found", environmentId = id, targetId });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var updated = await targetRegistry.UpdateAsync(targetId, request, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(updated);
|
||||
}
|
||||
catch (ValidationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = "target_validation_failed", details = ex.Errors });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> DeleteTarget(
|
||||
Guid id,
|
||||
Guid targetId,
|
||||
IEnvironmentService environmentService,
|
||||
ITargetRegistry targetRegistry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
var target = await targetRegistry.GetAsync(targetId, cancellationToken).ConfigureAwait(false);
|
||||
if (target is null || target.EnvironmentId != id)
|
||||
{
|
||||
return Results.NotFound(new { error = "target_not_found", environmentId = id, targetId });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await targetRegistry.UnregisterAsync(targetId, cancellationToken).ConfigureAwait(false);
|
||||
return Results.NoContent();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.Conflict(new { error = "target_delete_blocked", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> CheckTargetHealth(
|
||||
Guid id,
|
||||
Guid targetId,
|
||||
IEnvironmentService environmentService,
|
||||
ITargetRegistry targetRegistry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
var target = await targetRegistry.GetAsync(targetId, cancellationToken).ConfigureAwait(false);
|
||||
if (target is null || target.EnvironmentId != id)
|
||||
{
|
||||
return Results.NotFound(new { error = "target_not_found", environmentId = id, targetId });
|
||||
}
|
||||
|
||||
var result = await targetRegistry.TestConnectionAsync(targetId, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(result);
|
||||
}
|
||||
|
||||
private static async Task<IResult> ListFreezeWindows(
|
||||
Guid id,
|
||||
IEnvironmentService environmentService,
|
||||
IFreezeWindowService freezeWindowService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
var windows = await freezeWindowService.ListByEnvironmentAsync(id, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(windows);
|
||||
}
|
||||
|
||||
private static async Task<IResult> CreateFreezeWindow(
|
||||
Guid id,
|
||||
CreateFreezeWindowBody request,
|
||||
IEnvironmentService environmentService,
|
||||
IFreezeWindowService freezeWindowService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var created = await freezeWindowService.CreateAsync(
|
||||
new CreateFreezeWindowRequest(
|
||||
id,
|
||||
request.Name,
|
||||
request.StartAt,
|
||||
request.EndAt,
|
||||
request.Reason,
|
||||
request.IsRecurring,
|
||||
request.RecurrenceRule),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Created(
|
||||
$"/api/v1/release-orchestrator/environments/{id:D}/freeze-windows/{created.Id:D}",
|
||||
created);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = "freeze_window_validation_failed", message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> UpdateFreezeWindow(
|
||||
Guid id,
|
||||
Guid freezeWindowId,
|
||||
UpdateFreezeWindowRequest request,
|
||||
IEnvironmentService environmentService,
|
||||
IFreezeWindowService freezeWindowService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
var window = await freezeWindowService.GetAsync(freezeWindowId, cancellationToken).ConfigureAwait(false);
|
||||
if (window is null || window.EnvironmentId != id)
|
||||
{
|
||||
return Results.NotFound(new { error = "freeze_window_not_found", environmentId = id, freezeWindowId });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var updated = await freezeWindowService.UpdateAsync(freezeWindowId, request, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(updated);
|
||||
}
|
||||
catch (FreezeWindowNotFoundException)
|
||||
{
|
||||
return Results.NotFound(new { error = "freeze_window_not_found", environmentId = id, freezeWindowId });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> DeleteFreezeWindow(
|
||||
Guid id,
|
||||
Guid freezeWindowId,
|
||||
IEnvironmentService environmentService,
|
||||
IFreezeWindowService freezeWindowService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await EnvironmentExistsAsync(id, environmentService, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return Results.NotFound(new { error = "environment_not_found", id });
|
||||
}
|
||||
|
||||
var window = await freezeWindowService.GetAsync(freezeWindowId, cancellationToken).ConfigureAwait(false);
|
||||
if (window is null || window.EnvironmentId != id)
|
||||
{
|
||||
return Results.NotFound(new { error = "freeze_window_not_found", environmentId = id, freezeWindowId });
|
||||
}
|
||||
|
||||
await freezeWindowService.DeleteAsync(freezeWindowId, cancellationToken).ConfigureAwait(false);
|
||||
return Results.NoContent();
|
||||
}
|
||||
|
||||
private static async Task<bool> EnvironmentExistsAsync(
|
||||
Guid environmentId,
|
||||
IEnvironmentService environmentService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await environmentService.GetAsync(environmentId, cancellationToken).ConfigureAwait(false) is not null;
|
||||
}
|
||||
|
||||
public sealed record CreateTargetRequest(
|
||||
string Name,
|
||||
string DisplayName,
|
||||
TargetType Type,
|
||||
TargetConnectionConfig ConnectionConfig,
|
||||
Guid? AgentId = null);
|
||||
|
||||
public sealed record UpdateEnvironmentSettingsRequest(
|
||||
int? RequiredApprovals = null,
|
||||
bool? RequireSeparationOfDuties = null,
|
||||
Guid? AutoPromoteFrom = null,
|
||||
int? DeploymentTimeoutSeconds = null);
|
||||
|
||||
public sealed record CreateFreezeWindowBody(
|
||||
string Name,
|
||||
DateTimeOffset StartAt,
|
||||
DateTimeOffset EndAt,
|
||||
string? Reason = null,
|
||||
bool IsRecurring = false,
|
||||
string? RecurrenceRule = null);
|
||||
}
|
||||
Reference in New Issue
Block a user