up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,107 +1,107 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Notify.Models;
|
||||
|
||||
namespace StellaOps.Notifier.Worker.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// Channel adapter for Slack webhook delivery.
|
||||
/// </summary>
|
||||
public sealed class SlackChannelAdapter : INotifyChannelAdapter
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<SlackChannelAdapter> _logger;
|
||||
|
||||
public SlackChannelAdapter(HttpClient httpClient, ILogger<SlackChannelAdapter> logger)
|
||||
{
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public NotifyChannelType ChannelType => NotifyChannelType.Slack;
|
||||
|
||||
public async Task<ChannelDispatchResult> SendAsync(
|
||||
NotifyChannel channel,
|
||||
NotifyDeliveryRendered rendered,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
ArgumentNullException.ThrowIfNull(rendered);
|
||||
|
||||
var endpoint = channel.Config?.Endpoint;
|
||||
if (string.IsNullOrWhiteSpace(endpoint))
|
||||
{
|
||||
return ChannelDispatchResult.Fail("Slack webhook URL not configured", shouldRetry: false);
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri))
|
||||
{
|
||||
return ChannelDispatchResult.Fail($"Invalid Slack webhook URL: {endpoint}", shouldRetry: false);
|
||||
}
|
||||
|
||||
// Build Slack message payload
|
||||
var slackPayload = new
|
||||
{
|
||||
channel = channel.Config?.Target,
|
||||
text = rendered.Title,
|
||||
blocks = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
type = "section",
|
||||
text = new
|
||||
{
|
||||
type = "mrkdwn",
|
||||
text = rendered.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, uri);
|
||||
request.Content = JsonContent.Create(slackPayload, options: new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
var statusCode = (int)response.StatusCode;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Slack delivery to channel {Target} succeeded.",
|
||||
channel.Config?.Target ?? "(default)");
|
||||
return ChannelDispatchResult.Ok(statusCode);
|
||||
}
|
||||
|
||||
var shouldRetry = statusCode >= 500 || statusCode == 429;
|
||||
_logger.LogWarning(
|
||||
"Slack delivery failed with status {StatusCode}. Retry: {ShouldRetry}.",
|
||||
statusCode,
|
||||
shouldRetry);
|
||||
|
||||
return ChannelDispatchResult.Fail(
|
||||
$"HTTP {statusCode}",
|
||||
shouldRetry: shouldRetry,
|
||||
httpStatusCode: statusCode);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Slack delivery failed with network error.");
|
||||
return ChannelDispatchResult.Fail(ex.Message, shouldRetry: true);
|
||||
}
|
||||
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Slack delivery timed out.");
|
||||
return ChannelDispatchResult.Fail("Request timeout", shouldRetry: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Notify.Models;
|
||||
|
||||
namespace StellaOps.Notifier.Worker.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// Channel adapter for Slack webhook delivery.
|
||||
/// </summary>
|
||||
public sealed class SlackChannelAdapter : INotifyChannelAdapter
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<SlackChannelAdapter> _logger;
|
||||
|
||||
public SlackChannelAdapter(HttpClient httpClient, ILogger<SlackChannelAdapter> logger)
|
||||
{
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public NotifyChannelType ChannelType => NotifyChannelType.Slack;
|
||||
|
||||
public async Task<ChannelDispatchResult> SendAsync(
|
||||
NotifyChannel channel,
|
||||
NotifyDeliveryRendered rendered,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(channel);
|
||||
ArgumentNullException.ThrowIfNull(rendered);
|
||||
|
||||
var endpoint = channel.Config?.Endpoint;
|
||||
if (string.IsNullOrWhiteSpace(endpoint))
|
||||
{
|
||||
return ChannelDispatchResult.Fail("Slack webhook URL not configured", shouldRetry: false);
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri))
|
||||
{
|
||||
return ChannelDispatchResult.Fail($"Invalid Slack webhook URL: {endpoint}", shouldRetry: false);
|
||||
}
|
||||
|
||||
// Build Slack message payload
|
||||
var slackPayload = new
|
||||
{
|
||||
channel = channel.Config?.Target,
|
||||
text = rendered.Title,
|
||||
blocks = new object[]
|
||||
{
|
||||
new
|
||||
{
|
||||
type = "section",
|
||||
text = new
|
||||
{
|
||||
type = "mrkdwn",
|
||||
text = rendered.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, uri);
|
||||
request.Content = JsonContent.Create(slackPayload, options: new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
var statusCode = (int)response.StatusCode;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Slack delivery to channel {Target} succeeded.",
|
||||
channel.Config?.Target ?? "(default)");
|
||||
return ChannelDispatchResult.Ok(statusCode);
|
||||
}
|
||||
|
||||
var shouldRetry = statusCode >= 500 || statusCode == 429;
|
||||
_logger.LogWarning(
|
||||
"Slack delivery failed with status {StatusCode}. Retry: {ShouldRetry}.",
|
||||
statusCode,
|
||||
shouldRetry);
|
||||
|
||||
return ChannelDispatchResult.Fail(
|
||||
$"HTTP {statusCode}",
|
||||
shouldRetry: shouldRetry,
|
||||
httpStatusCode: statusCode);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Slack delivery failed with network error.");
|
||||
return ChannelDispatchResult.Fail(ex.Message, shouldRetry: true);
|
||||
}
|
||||
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Slack delivery timed out.");
|
||||
return ChannelDispatchResult.Fail("Request timeout", shouldRetry: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user