Add Authority Advisory AI and API Lifecycle Configuration
- Introduced AuthorityAdvisoryAiOptions and related classes for managing advisory AI configurations, including remote inference options and tenant-specific settings. - Added AuthorityApiLifecycleOptions to control API lifecycle settings, including legacy OAuth endpoint configurations. - Implemented validation and normalization methods for both advisory AI and API lifecycle options to ensure proper configuration. - Created AuthorityNotificationsOptions and its related classes for managing notification settings, including ack tokens, webhooks, and escalation options. - Developed IssuerDirectoryClient and related models for interacting with the issuer directory service, including caching mechanisms and HTTP client configurations. - Added support for dependency injection through ServiceCollectionExtensions for the Issuer Directory Client. - Updated project file to include necessary package references for the new Issuer Directory Client library.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Reports validation outcomes for observability purposes.
|
||||
/// </summary>
|
||||
public interface ISurfaceValidationReporter
|
||||
{
|
||||
void Report(SurfaceValidationContext context, SurfaceValidationResult result);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Contract implemented by components that validate surface prerequisites.
|
||||
/// </summary>
|
||||
public interface ISurfaceValidator
|
||||
{
|
||||
ValueTask<SurfaceValidationResult> ValidateAsync(
|
||||
SurfaceValidationContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Executes registered surface validators and aggregates their results.
|
||||
/// </summary>
|
||||
public interface ISurfaceValidatorRunner
|
||||
{
|
||||
ValueTask<SurfaceValidationResult> RunAllAsync(
|
||||
SurfaceValidationContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
ValueTask EnsureAsync(
|
||||
SurfaceValidationContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
internal sealed class LoggingSurfaceValidationReporter : ISurfaceValidationReporter
|
||||
{
|
||||
private readonly ILogger<LoggingSurfaceValidationReporter> _logger;
|
||||
|
||||
public LoggingSurfaceValidationReporter(ILogger<LoggingSurfaceValidationReporter> logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public void Report(SurfaceValidationContext context, SurfaceValidationResult result)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (result is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_logger.LogInformation("Surface validation succeeded for component {Component}.", context.ComponentName);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var issue in result.Issues)
|
||||
{
|
||||
var logLevel = issue.Severity switch
|
||||
{
|
||||
SurfaceValidationSeverity.Info => LogLevel.Information,
|
||||
SurfaceValidationSeverity.Warning => LogLevel.Warning,
|
||||
_ => LogLevel.Error
|
||||
};
|
||||
|
||||
_logger.Log(logLevel,
|
||||
"Surface validation issue for component {Component}: {Code} - {Message}. Hint: {Hint}",
|
||||
context.ComponentName,
|
||||
issue.Code,
|
||||
issue.Message,
|
||||
issue.Hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scanner.Surface.Validation.Validators;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddSurfaceValidation(
|
||||
this IServiceCollection services,
|
||||
Action<SurfaceValidationBuilder>? configure = null)
|
||||
{
|
||||
if (services is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.TryAddSingleton<ISurfaceValidationReporter, LoggingSurfaceValidationReporter>();
|
||||
services.TryAddSingleton<ISurfaceValidatorRunner, SurfaceValidatorRunner>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ISurfaceValidator, SurfaceEndpointValidator>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ISurfaceValidator, SurfaceCacheValidator>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ISurfaceValidator, SurfaceSecretsValidator>());
|
||||
services.TryAddSingleton<IConfigureOptions<SurfaceValidationOptions>, SurfaceValidationOptionsConfigurator>();
|
||||
|
||||
if (configure is not null)
|
||||
{
|
||||
var builder = new SurfaceValidationBuilder(services);
|
||||
configure(builder);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private sealed class SurfaceValidationOptionsConfigurator : IConfigureOptions<SurfaceValidationOptions>
|
||||
{
|
||||
public void Configure(SurfaceValidationOptions options)
|
||||
{
|
||||
options ??= new SurfaceValidationOptions();
|
||||
options.ThrowOnFailure = true;
|
||||
options.ContinueOnError = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<EnableDefaultItems>false</EnableDefaultItems>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="**\*.cs" Exclude="obj\**;bin\**" />
|
||||
<EmbeddedResource Include="**\*.json" Exclude="obj\**;bin\**" />
|
||||
<None Include="**\*" Exclude="**\*.cs;**\*.json;bin\**;obj\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../StellaOps.Scanner.Surface.Env/StellaOps.Scanner.Surface.Env.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
public sealed class SurfaceValidationBuilder
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
|
||||
internal SurfaceValidationBuilder(IServiceCollection services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public SurfaceValidationBuilder AddValidator<TValidator>()
|
||||
where TValidator : class, ISurfaceValidator
|
||||
{
|
||||
_services.AddSingleton<ISurfaceValidator, TValidator>();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SurfaceValidationBuilder AddValidator(Func<IServiceProvider, ISurfaceValidator> factory)
|
||||
{
|
||||
if (factory is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
_services.AddSingleton(provider => factory(provider));
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using StellaOps.Scanner.Surface.Env;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Context supplied to validation checks to describe the surface configuration.
|
||||
/// </summary>
|
||||
public sealed record SurfaceValidationContext(
|
||||
IServiceProvider Services,
|
||||
string ComponentName,
|
||||
SurfaceEnvironmentSettings Environment,
|
||||
IReadOnlyDictionary<string, object?> Properties)
|
||||
{
|
||||
public static SurfaceValidationContext Create(
|
||||
IServiceProvider services,
|
||||
string componentName,
|
||||
SurfaceEnvironmentSettings environment,
|
||||
IReadOnlyDictionary<string, object?>? properties = null)
|
||||
{
|
||||
if (services is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(componentName))
|
||||
{
|
||||
throw new ArgumentException("Component name cannot be null or whitespace.", nameof(componentName));
|
||||
}
|
||||
|
||||
if (environment is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(environment));
|
||||
}
|
||||
|
||||
return new SurfaceValidationContext(
|
||||
services,
|
||||
componentName,
|
||||
environment,
|
||||
properties ?? new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
public sealed class SurfaceValidationException : Exception
|
||||
{
|
||||
public SurfaceValidationException(string message, IEnumerable<SurfaceValidationIssue> issues)
|
||||
: base(message)
|
||||
{
|
||||
Issues = issues.ToImmutableArray();
|
||||
}
|
||||
|
||||
public ImmutableArray<SurfaceValidationIssue> Issues { get; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single validation finding produced by a surface validator.
|
||||
/// </summary>
|
||||
public sealed record SurfaceValidationIssue(
|
||||
string Code,
|
||||
string Message,
|
||||
SurfaceValidationSeverity Severity,
|
||||
string? Hint = null)
|
||||
{
|
||||
public static SurfaceValidationIssue Info(string code, string message, string? hint = null)
|
||||
=> new(code, message, SurfaceValidationSeverity.Info, hint);
|
||||
|
||||
public static SurfaceValidationIssue Warning(string code, string message, string? hint = null)
|
||||
=> new(code, message, SurfaceValidationSeverity.Warning, hint);
|
||||
|
||||
public static SurfaceValidationIssue Error(string code, string message, string? hint = null)
|
||||
=> new(code, message, SurfaceValidationSeverity.Error, hint);
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Hint))]
|
||||
public bool HasHint => !string.IsNullOrWhiteSpace(Hint);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
public static class SurfaceValidationIssueCodes
|
||||
{
|
||||
public const string SurfaceEndpointMissing = "SURFACE_ENV_MISSING_ENDPOINT";
|
||||
public const string SurfaceEndpointInvalid = "SURFACE_ENV_ENDPOINT_INVALID";
|
||||
public const string CacheDirectoryUnwritable = "SURFACE_ENV_CACHE_DIR_UNWRITABLE";
|
||||
public const string CacheQuotaInvalid = "SURFACE_ENV_CACHE_QUOTA_INVALID";
|
||||
public const string SecretsProviderUnknown = "SURFACE_SECRET_PROVIDER_UNKNOWN";
|
||||
public const string SecretsConfigurationMissing = "SURFACE_SECRET_CONFIGURATION_MISSING";
|
||||
public const string TenantMissing = "SURFACE_ENV_TENANT_MISSING";
|
||||
public const string BucketMissing = "SURFACE_FS_BUCKET_MISSING";
|
||||
public const string FeatureUnknown = "SURFACE_FEATURE_UNKNOWN";
|
||||
public const string ValidatorException = "SURFACE_VALIDATOR_EXCEPTION";
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Controls behaviour of the surface validation runner.
|
||||
/// </summary>
|
||||
public sealed class SurfaceValidationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the runner should continue invoking validators after an error is recorded.
|
||||
/// </summary>
|
||||
public bool ContinueOnError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the runner should throw a <see cref="SurfaceValidationException"/> when validation fails.
|
||||
/// Defaults to <c>true</c> to align with fail-fast expectations.
|
||||
/// </summary>
|
||||
public bool ThrowOnFailure { get; set; } = true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Aggregate outcome emitted after running all registered validators.
|
||||
/// </summary>
|
||||
public sealed record SurfaceValidationResult
|
||||
{
|
||||
private SurfaceValidationResult(bool isSuccess, ImmutableArray<SurfaceValidationIssue> issues)
|
||||
{
|
||||
IsSuccess = isSuccess;
|
||||
Issues = issues;
|
||||
}
|
||||
|
||||
public bool IsSuccess { get; }
|
||||
|
||||
public ImmutableArray<SurfaceValidationIssue> Issues { get; }
|
||||
|
||||
public static SurfaceValidationResult Success()
|
||||
=> new(true, ImmutableArray<SurfaceValidationIssue>.Empty);
|
||||
|
||||
public static SurfaceValidationResult FromIssues(IEnumerable<SurfaceValidationIssue> issues)
|
||||
{
|
||||
var immutable = issues.ToImmutableArray();
|
||||
var success = immutable.All(issue => issue.Severity != SurfaceValidationSeverity.Error);
|
||||
return new SurfaceValidationResult(success, immutable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Severity classification for surface validation issues.
|
||||
/// </summary>
|
||||
public enum SurfaceValidationSeverity
|
||||
{
|
||||
Info = 0,
|
||||
Warning = 1,
|
||||
Error = 2,
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
internal sealed class SurfaceValidatorRunner : ISurfaceValidatorRunner
|
||||
{
|
||||
private readonly IReadOnlyList<ISurfaceValidator> _validators;
|
||||
private readonly ILogger<SurfaceValidatorRunner> _logger;
|
||||
private readonly ISurfaceValidationReporter _reporter;
|
||||
private readonly SurfaceValidationOptions _options;
|
||||
|
||||
public SurfaceValidatorRunner(
|
||||
IEnumerable<ISurfaceValidator> validators,
|
||||
ILogger<SurfaceValidatorRunner> logger,
|
||||
ISurfaceValidationReporter reporter,
|
||||
IOptions<SurfaceValidationOptions> options)
|
||||
{
|
||||
_validators = validators?.ToArray() ?? Array.Empty<ISurfaceValidator>();
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_reporter = reporter ?? throw new ArgumentNullException(nameof(reporter));
|
||||
_options = options?.Value ?? new SurfaceValidationOptions();
|
||||
}
|
||||
|
||||
public async ValueTask<SurfaceValidationResult> RunAllAsync(
|
||||
SurfaceValidationContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (_validators.Count == 0)
|
||||
{
|
||||
var success = SurfaceValidationResult.Success();
|
||||
_reporter.Report(context, success);
|
||||
return success;
|
||||
}
|
||||
|
||||
var issues = new List<SurfaceValidationIssue>();
|
||||
foreach (var validator in _validators)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await validator.ValidateAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
issues.AddRange(result.Issues);
|
||||
|
||||
if (!_options.ContinueOnError && result.Issues.Any(issue => issue.Severity == SurfaceValidationSeverity.Error))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Surface validator {Validator} threw an exception.", validator.GetType().FullName);
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.ValidatorException,
|
||||
$"Validator '{validator.GetType().FullName}' threw an exception: {ex.Message}",
|
||||
"Inspect logs for stack trace."));
|
||||
|
||||
if (!_options.ContinueOnError)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resultAggregate = issues.Count == 0
|
||||
? SurfaceValidationResult.Success()
|
||||
: SurfaceValidationResult.FromIssues(issues);
|
||||
|
||||
_reporter.Report(context, resultAggregate);
|
||||
return resultAggregate;
|
||||
}
|
||||
|
||||
public async ValueTask EnsureAsync(
|
||||
SurfaceValidationContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await RunAllAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.IsSuccess && _options.ThrowOnFailure)
|
||||
{
|
||||
throw new SurfaceValidationException(
|
||||
$"Surface validation failed for component '{context.ComponentName}'.",
|
||||
result.Issues);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||
|----|--------|----------|------------|-------------|---------------|
|
||||
| SURFACE-VAL-01 | TODO | Scanner Guild, Security Guild | SURFACE-FS-01, SURFACE-ENV-01 | Define validation framework (design doc `surface-validation.md`) covering SOLID extension points and queryable checks for env/cache/secrets. | Spec merged; architecture sign-off from Scanner + Security; checklist of baseline validators established. |
|
||||
| SURFACE-VAL-01 | DOING (2025-11-01) | Scanner Guild, Security Guild | SURFACE-FS-01, SURFACE-ENV-01 | Define validation framework (design doc `surface-validation.md`) covering SOLID extension points and queryable checks for env/cache/secrets. | Spec merged; architecture sign-off from Scanner + Security; checklist of baseline validators established. |
|
||||
| SURFACE-VAL-02 | TODO | Scanner Guild | SURFACE-VAL-01, SURFACE-ENV-02, SURFACE-FS-02 | Implement base validation library (interfaces, check registry, default validators for env/cached manifests, secret refs) with unit tests. | Library published; validation registry supports DI; tests cover success/failure; XML docs added. |
|
||||
| SURFACE-VAL-03 | TODO | Scanner Guild, Analyzer Guild | SURFACE-VAL-02 | Integrate validation pipeline into Scanner analyzers (Lang, EntryTrace, etc.) to ensure consistent checks before processing. | Analyzers call validation hooks; integration tests updated; performance baseline measured. |
|
||||
| SURFACE-VAL-04 | TODO | Scanner Guild, Zastava Guild | SURFACE-VAL-02 | Expose validation helpers to Zastava and other runtime consumers (Observer/Webhook) for preflight checks. | Zastava uses shared validators; admission tests include validation failure scenarios. |
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation.Validators;
|
||||
|
||||
internal sealed class SurfaceCacheValidator : ISurfaceValidator
|
||||
{
|
||||
public ValueTask<SurfaceValidationResult> ValidateAsync(
|
||||
SurfaceValidationContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var issues = new List<SurfaceValidationIssue>();
|
||||
var directory = context.Environment.CacheRoot;
|
||||
try
|
||||
{
|
||||
if (!directory.Exists)
|
||||
{
|
||||
directory.Create();
|
||||
}
|
||||
|
||||
var testFile = Path.Combine(directory.FullName, ".validation");
|
||||
using (File.Open(testFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
|
||||
{
|
||||
}
|
||||
|
||||
File.Delete(testFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.CacheDirectoryUnwritable,
|
||||
$"Surface cache directory '{directory.FullName}' is not writable: {ex.Message}",
|
||||
"Ensure the cache directory exists and is writable by the process user."));
|
||||
}
|
||||
|
||||
if (context.Environment.CacheQuotaMegabytes <= 0)
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.CacheQuotaInvalid,
|
||||
"Surface cache quota must be greater than zero.",
|
||||
"Set SCANNER_SURFACE_CACHE_QUOTA_MB to a positive value."));
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(issues.Count == 0
|
||||
? SurfaceValidationResult.Success()
|
||||
: SurfaceValidationResult.FromIssues(issues));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using StellaOps.Scanner.Surface.Env;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation.Validators;
|
||||
|
||||
internal sealed class SurfaceEndpointValidator : ISurfaceValidator
|
||||
{
|
||||
public ValueTask<SurfaceValidationResult> ValidateAsync(SurfaceValidationContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var issues = new List<SurfaceValidationIssue>();
|
||||
if (context.Environment.SurfaceFsEndpoint is null || string.Equals(context.Environment.SurfaceFsEndpoint.Host, "surface.invalid", StringComparison.Ordinal))
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.SurfaceEndpointMissing,
|
||||
"Surface FS endpoint is missing or invalid.",
|
||||
"Set SCANNER_SURFACE_FS_ENDPOINT to the RustFS/S3 endpoint."));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(context.Environment.SurfaceFsBucket))
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.BucketMissing,
|
||||
"Surface FS bucket must be provided.",
|
||||
"Set SCANNER_SURFACE_FS_BUCKET"));
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(issues.Count == 0
|
||||
? SurfaceValidationResult.Success()
|
||||
: SurfaceValidationResult.FromIssues(issues));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Scanner.Surface.Env;
|
||||
using StellaOps.Scanner.Surface.Validation;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Validation.Validators;
|
||||
|
||||
internal sealed class SurfaceSecretsValidator : ISurfaceValidator
|
||||
{
|
||||
private static readonly HashSet<string> KnownProviders = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"kubernetes",
|
||||
"file",
|
||||
"inline"
|
||||
};
|
||||
|
||||
public ValueTask<SurfaceValidationResult> ValidateAsync(
|
||||
SurfaceValidationContext context,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var issues = new List<SurfaceValidationIssue>();
|
||||
var secrets = context.Environment.Secrets;
|
||||
|
||||
if (!KnownProviders.Contains(secrets.Provider))
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.SecretsProviderUnknown,
|
||||
$"Surface secrets provider '{secrets.Provider}' is not recognised.",
|
||||
"Set SCANNER_SURFACE_SECRETS_PROVIDER to 'kubernetes', 'file', or another supported provider."));
|
||||
}
|
||||
|
||||
if (string.Equals(secrets.Provider, "kubernetes", StringComparison.OrdinalIgnoreCase) &&
|
||||
string.IsNullOrWhiteSpace(secrets.Namespace))
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.SecretsConfigurationMissing,
|
||||
"Kubernetes secrets provider requires a namespace.",
|
||||
"Set SCANNER_SURFACE_SECRETS_NAMESPACE to the target namespace."));
|
||||
}
|
||||
|
||||
if (string.Equals(secrets.Provider, "file", StringComparison.OrdinalIgnoreCase) &&
|
||||
string.IsNullOrWhiteSpace(secrets.Root))
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.SecretsConfigurationMissing,
|
||||
"File secrets provider requires a root directory.",
|
||||
"Set SCANNER_SURFACE_SECRETS_ROOT to a directory path."));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(secrets.Tenant))
|
||||
{
|
||||
issues.Add(SurfaceValidationIssue.Error(
|
||||
SurfaceValidationIssueCodes.TenantMissing,
|
||||
"Surface secrets tenant cannot be empty.",
|
||||
"Set SCANNER_SURFACE_SECRETS_TENANT or ensure the tenant resolver provides a value."));
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(issues.Count == 0
|
||||
? SurfaceValidationResult.Success()
|
||||
: SurfaceValidationResult.FromIssues(issues));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user