using System.Collections.Immutable; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StellaOps.Policy.Engine.Evaluation; using StellaOps.Policy.Engine.Options; using StellaOps.Policy.Licensing; namespace StellaOps.Policy.Engine.Services; internal sealed class LicenseComplianceService { private readonly ILicenseComplianceEvaluator _evaluator; private readonly ILicensePolicyLoader _policyLoader; private readonly LicenseComplianceOptions _options; private readonly ILogger _logger; private readonly Lazy _policy; public LicenseComplianceService( ILicenseComplianceEvaluator evaluator, ILicensePolicyLoader policyLoader, IOptions options, ILogger logger) { _evaluator = evaluator ?? throw new ArgumentNullException(nameof(evaluator)); _policyLoader = policyLoader ?? throw new ArgumentNullException(nameof(policyLoader)); _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _policy = new Lazy(ResolvePolicy); } public async Task EvaluateAsync( PolicyEvaluationSbom sbom, CancellationToken ct) { if (!_options.Enabled) { return null; } var components = sbom.Components .Select(MapComponent) .ToList(); try { var report = await _evaluator.EvaluateAsync(components, _policy.Value, ct) .ConfigureAwait(false); return report; } catch (Exception ex) { _logger.LogWarning(ex, "License compliance evaluation failed; proceeding without report."); return null; } } private LicensePolicy ResolvePolicy() { if (_options.Policy is not null) { return _options.Policy; } if (!string.IsNullOrWhiteSpace(_options.PolicyPath)) { return _policyLoader.Load(_options.PolicyPath); } return LicensePolicyDefaults.Default; } private static LicenseComponent MapComponent(PolicyEvaluationComponent component) { var expression = GetMetadata(component, "license_expression") ?? GetMetadata(component, "licenseexpression") ?? GetMetadata(component, "spdx_license_expression") ?? GetMetadata(component, "license") ?? GetMetadata(component, "licenses"); var licenses = ImmutableArray.Empty; if (!string.IsNullOrWhiteSpace(expression) && expression.Contains(',')) { licenses = expression .Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(value => value.Trim()) .Where(value => !string.IsNullOrWhiteSpace(value)) .ToImmutableArray(); expression = null; } return new LicenseComponent { Name = component.Name, Version = component.Version, Purl = component.Purl, LicenseExpression = expression, Licenses = licenses, Metadata = component.Metadata }; } private static string? GetMetadata(PolicyEvaluationComponent component, string key) { return component.Metadata.TryGetValue(key, out var value) ? value : null; } }