up the blokcing tasks
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Risk Bundle CI / risk-bundle-build (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Risk Bundle CI / risk-bundle-offline-kit (push) Has been cancelled
Risk Bundle CI / publish-checksums (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Risk Bundle CI / risk-bundle-build (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Risk Bundle CI / risk-bundle-offline-kit (push) Has been cancelled
Risk Bundle CI / publish-checksums (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -67,4 +67,18 @@ internal interface INotifyClient
|
||||
Task<NotifySendResult> SendAsync(
|
||||
NotifySendRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Simulate rule evaluation.
|
||||
/// </summary>
|
||||
Task<NotifySimulationResult> SimulateAsync(
|
||||
NotifySimulationRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Acknowledge an incident or signed token.
|
||||
/// </summary>
|
||||
Task<NotifyAckResult> AckAsync(
|
||||
NotifyAckRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Cli.Services.Models;
|
||||
@@ -610,3 +611,83 @@ internal sealed class NotifySendResult
|
||||
[JsonPropertyName("idempotencyKey")]
|
||||
public string? IdempotencyKey { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class NotifySimulationRequest
|
||||
{
|
||||
[JsonPropertyName("tenantId")]
|
||||
public string? TenantId { get; init; }
|
||||
|
||||
[JsonPropertyName("events")]
|
||||
public JsonElement? Events { get; init; }
|
||||
|
||||
[JsonPropertyName("rules")]
|
||||
public JsonElement? Rules { get; init; }
|
||||
|
||||
[JsonPropertyName("enabledRulesOnly")]
|
||||
public bool? EnabledRulesOnly { get; init; }
|
||||
|
||||
[JsonPropertyName("historicalLookbackMinutes")]
|
||||
public int? HistoricalLookbackMinutes { get; init; }
|
||||
|
||||
[JsonPropertyName("maxEvents")]
|
||||
public int? MaxEvents { get; init; }
|
||||
|
||||
[JsonPropertyName("eventKindFilter")]
|
||||
public string? EventKindFilter { get; init; }
|
||||
|
||||
[JsonPropertyName("includeNonMatches")]
|
||||
public bool? IncludeNonMatches { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class NotifySimulationResult
|
||||
{
|
||||
[JsonPropertyName("simulationId")]
|
||||
public string? SimulationId { get; init; }
|
||||
|
||||
[JsonPropertyName("totalEvents")]
|
||||
public int? TotalEvents { get; init; }
|
||||
|
||||
[JsonPropertyName("totalRules")]
|
||||
public int? TotalRules { get; init; }
|
||||
|
||||
[JsonPropertyName("matchedEvents")]
|
||||
public int? MatchedEvents { get; init; }
|
||||
|
||||
[JsonPropertyName("totalActionsTriggered")]
|
||||
public int? TotalActionsTriggered { get; init; }
|
||||
|
||||
[JsonPropertyName("durationMs")]
|
||||
public double? DurationMs { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class NotifyAckRequest
|
||||
{
|
||||
[JsonPropertyName("tenantId")]
|
||||
public string? TenantId { get; init; }
|
||||
|
||||
[JsonPropertyName("incidentId")]
|
||||
public string? IncidentId { get; init; }
|
||||
|
||||
[JsonPropertyName("acknowledgedBy")]
|
||||
public string? AcknowledgedBy { get; init; }
|
||||
|
||||
[JsonPropertyName("comment")]
|
||||
public string? Comment { get; init; }
|
||||
|
||||
public string? Token { get; init; }
|
||||
}
|
||||
|
||||
internal sealed class NotifyAckResult
|
||||
{
|
||||
[JsonPropertyName("success")]
|
||||
public bool Success { get; init; }
|
||||
|
||||
[JsonPropertyName("incidentId")]
|
||||
public string? IncidentId { get; init; }
|
||||
|
||||
[JsonPropertyName("error")]
|
||||
public string? Error { get; init; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; init; }
|
||||
}
|
||||
|
||||
@@ -569,6 +569,131 @@ internal sealed class NotifyClient : INotifyClient
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<NotifySimulationResult> SimulateAsync(
|
||||
NotifySimulationRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
try
|
||||
{
|
||||
EnsureConfigured();
|
||||
|
||||
var json = JsonSerializer.Serialize(request, SerializerOptions);
|
||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
using var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v2/simulate")
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.TenantId))
|
||||
{
|
||||
httpRequest.Headers.TryAddWithoutValidation("X-Tenant-Id", request.TenantId);
|
||||
}
|
||||
|
||||
await AuthorizeRequestAsync(httpRequest, "notify.simulate", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var response = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
logger.LogError(
|
||||
"Failed to simulate notify rules (status {StatusCode}). Response: {Payload}",
|
||||
(int)response.StatusCode,
|
||||
string.IsNullOrWhiteSpace(payload) ? "<empty>" : payload);
|
||||
|
||||
return new NotifySimulationResult { SimulationId = null, TotalEvents = 0, TotalRules = 0 };
|
||||
}
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
var result = await JsonSerializer
|
||||
.DeserializeAsync<NotifySimulationResult>(stream, SerializerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return result ?? new NotifySimulationResult { SimulationId = null, TotalEvents = 0, TotalRules = 0 };
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
logger.LogError(ex, "HTTP error while simulating notify rules");
|
||||
return new NotifySimulationResult { SimulationId = null, TotalEvents = 0, TotalRules = 0 };
|
||||
}
|
||||
catch (TaskCanceledException ex) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogError(ex, "Request timed out while simulating notify rules");
|
||||
return new NotifySimulationResult { SimulationId = null, TotalEvents = 0, TotalRules = 0 };
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<NotifyAckResult> AckAsync(
|
||||
NotifyAckRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
try
|
||||
{
|
||||
EnsureConfigured();
|
||||
|
||||
var hasToken = !string.IsNullOrWhiteSpace(request.Token);
|
||||
using var httpRequest = hasToken
|
||||
? new HttpRequestMessage(HttpMethod.Get, $"/api/v2/ack?token={Uri.EscapeDataString(request.Token!)}")
|
||||
: new HttpRequestMessage(HttpMethod.Post, "/api/v2/ack")
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(new AckApiRequestBody
|
||||
{
|
||||
TenantId = request.TenantId,
|
||||
IncidentId = request.IncidentId,
|
||||
AcknowledgedBy = request.AcknowledgedBy,
|
||||
Comment = request.Comment
|
||||
}, SerializerOptions), Encoding.UTF8, "application/json")
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.TenantId))
|
||||
{
|
||||
httpRequest.Headers.TryAddWithoutValidation("X-Tenant-Id", request.TenantId);
|
||||
}
|
||||
|
||||
await AuthorizeRequestAsync(httpRequest, "notify.write", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using var response = await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
logger.LogError(
|
||||
"Failed to acknowledge notification (status {StatusCode}). Response: {Payload}",
|
||||
(int)response.StatusCode,
|
||||
string.IsNullOrWhiteSpace(payload) ? "<empty>" : payload);
|
||||
|
||||
return new NotifyAckResult { Success = false, IncidentId = request.IncidentId, Error = payload };
|
||||
}
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
var result = await JsonSerializer
|
||||
.DeserializeAsync<NotifyAckResult>(stream, SerializerOptions, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return result ?? new NotifyAckResult { Success = true, IncidentId = request.IncidentId };
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
logger.LogError(ex, "HTTP error while acknowledging notification");
|
||||
return new NotifyAckResult { Success = false, IncidentId = request.IncidentId, Error = ex.Message };
|
||||
}
|
||||
catch (TaskCanceledException ex) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogError(ex, "Request timed out while acknowledging notification");
|
||||
return new NotifyAckResult { Success = false, IncidentId = request.IncidentId, Error = "Request timed out" };
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record AckApiRequestBody
|
||||
{
|
||||
public string? TenantId { get; init; }
|
||||
public string? IncidentId { get; init; }
|
||||
public string? AcknowledgedBy { get; init; }
|
||||
public string? Comment { get; init; }
|
||||
}
|
||||
|
||||
private static string BuildChannelListUri(NotifyChannelListRequest request)
|
||||
{
|
||||
var queryParams = new List<string>();
|
||||
|
||||
Reference in New Issue
Block a user