up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-27 07:46:56 +02:00
parent d63af51f84
commit ea970ead2a
302 changed files with 43161 additions and 1534 deletions

View File

@@ -0,0 +1,105 @@
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 webhook (HTTP POST) delivery with retry support.
/// </summary>
public sealed class WebhookChannelAdapter : INotifyChannelAdapter
{
private readonly HttpClient _httpClient;
private readonly ILogger<WebhookChannelAdapter> _logger;
public WebhookChannelAdapter(HttpClient httpClient, ILogger<WebhookChannelAdapter> logger)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public NotifyChannelType ChannelType => NotifyChannelType.Webhook;
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("Webhook endpoint not configured", shouldRetry: false);
}
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri))
{
return ChannelDispatchResult.Fail($"Invalid webhook endpoint: {endpoint}", shouldRetry: false);
}
var payload = new
{
channel = channel.ChannelId,
target = rendered.Target,
title = rendered.Title,
body = rendered.Body,
summary = rendered.Summary,
format = rendered.Format.ToString().ToLowerInvariant(),
locale = rendered.Locale,
timestamp = DateTimeOffset.UtcNow
};
try
{
using var request = new HttpRequestMessage(HttpMethod.Post, uri);
request.Content = JsonContent.Create(payload, options: new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
// Add HMAC signature header if secret is available (placeholder for KMS integration)
request.Headers.Add("X-StellaOps-Notifier", "1.0");
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var statusCode = (int)response.StatusCode;
if (response.IsSuccessStatusCode)
{
_logger.LogInformation(
"Webhook delivery to {Endpoint} succeeded with status {StatusCode}.",
endpoint,
statusCode);
return ChannelDispatchResult.Ok(statusCode);
}
var shouldRetry = statusCode >= 500 || statusCode == 429;
_logger.LogWarning(
"Webhook delivery to {Endpoint} failed with status {StatusCode}. Retry: {ShouldRetry}.",
endpoint,
statusCode,
shouldRetry);
return ChannelDispatchResult.Fail(
$"HTTP {statusCode}",
statusCode,
shouldRetry);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Webhook delivery to {Endpoint} failed with network error.", endpoint);
return ChannelDispatchResult.Fail(ex.Message, shouldRetry: true);
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (TaskCanceledException ex)
{
_logger.LogWarning(ex, "Webhook delivery to {Endpoint} timed out.", endpoint);
return ChannelDispatchResult.Fail("Request timeout", shouldRetry: true);
}
}
}