Restructure solution layout by module
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
root
2025-10-28 15:10:40 +02:00
parent 4e3e575db5
commit 68da90a11a
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,132 @@
using System.Globalization;
namespace StellaOps.Concelier.Connector.Vndr.Msrc.Configuration;
public sealed class MsrcOptions
{
public const string HttpClientName = "concelier.source.vndr.msrc";
public const string TokenClientName = "concelier.source.vndr.msrc.token";
public Uri BaseUri { get; set; } = new("https://api.msrc.microsoft.com/sug/v2.0/", UriKind.Absolute);
public string Locale { get; set; } = "en-US";
public string ApiVersion { get; set; } = "2024-08-01";
/// <summary>
/// Azure AD tenant identifier used for client credential flow.
/// </summary>
public string TenantId { get; set; } = string.Empty;
/// <summary>
/// Azure AD application (client) identifier.
/// </summary>
public string ClientId { get; set; } = string.Empty;
/// <summary>
/// Azure AD client secret used for token acquisition.
/// </summary>
public string ClientSecret { get; set; } = string.Empty;
/// <summary>
/// Scope requested during client-credential token acquisition.
/// </summary>
public string Scope { get; set; } = "api://api.msrc.microsoft.com/.default";
/// <summary>
/// Maximum advisories to fetch per cycle.
/// </summary>
public int MaxAdvisoriesPerFetch { get; set; } = 200;
/// <summary>
/// Page size used when iterating the MSRC API.
/// </summary>
public int PageSize { get; set; } = 100;
/// <summary>
/// Overlap window added when resuming from the last modified cursor.
/// </summary>
public TimeSpan CursorOverlap { get; set; } = TimeSpan.FromMinutes(10);
/// <summary>
/// When enabled the connector downloads the CVRF artefact referenced by each advisory.
/// </summary>
public bool DownloadCvrf { get; set; } = false;
public TimeSpan RequestDelay { get; set; } = TimeSpan.FromMilliseconds(250);
public TimeSpan FailureBackoff { get; set; } = TimeSpan.FromMinutes(5);
/// <summary>
/// Optional lower bound for the initial sync if the cursor is empty.
/// </summary>
public DateTimeOffset? InitialLastModified { get; set; } = DateTimeOffset.UtcNow.AddDays(-30);
public void Validate()
{
if (BaseUri is null || !BaseUri.IsAbsoluteUri)
{
throw new InvalidOperationException("MSRC base URI must be absolute.");
}
if (string.IsNullOrWhiteSpace(Locale))
{
throw new InvalidOperationException("Locale must be provided.");
}
if (!string.IsNullOrWhiteSpace(Locale) && !CultureInfo.GetCultures(CultureTypes.AllCultures).Any(c => string.Equals(c.Name, Locale, StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException($"Locale '{Locale}' is not recognised.");
}
if (string.IsNullOrWhiteSpace(ApiVersion))
{
throw new InvalidOperationException("API version must be provided.");
}
if (!Guid.TryParse(TenantId, out _))
{
throw new InvalidOperationException("TenantId must be a valid GUID.");
}
if (string.IsNullOrWhiteSpace(ClientId))
{
throw new InvalidOperationException("ClientId must be provided.");
}
if (string.IsNullOrWhiteSpace(ClientSecret))
{
throw new InvalidOperationException("ClientSecret must be provided.");
}
if (string.IsNullOrWhiteSpace(Scope))
{
throw new InvalidOperationException("Scope must be provided.");
}
if (MaxAdvisoriesPerFetch <= 0)
{
throw new InvalidOperationException($"{nameof(MaxAdvisoriesPerFetch)} must be greater than zero.");
}
if (PageSize <= 0 || PageSize > 500)
{
throw new InvalidOperationException($"{nameof(PageSize)} must be between 1 and 500.");
}
if (CursorOverlap < TimeSpan.Zero || CursorOverlap > TimeSpan.FromHours(6))
{
throw new InvalidOperationException($"{nameof(CursorOverlap)} must be within 0-6 hours.");
}
if (RequestDelay < TimeSpan.Zero)
{
throw new InvalidOperationException($"{nameof(RequestDelay)} cannot be negative.");
}
if (FailureBackoff <= TimeSpan.Zero)
{
throw new InvalidOperationException($"{nameof(FailureBackoff)} must be positive.");
}
}
}