- Cleaned up code formatting and organization across multiple files for improved readability. - Introduced `OsScanAnalyzerDispatcher` to handle OS analyzer execution and plugin loading. - Updated `ScanJobContext` to include an `Analysis` property for storing scan results. - Enhanced `ScanJobProcessor` to utilize the new `OsScanAnalyzerDispatcher`. - Improved logging and error handling in `ScanProgressReporter` for better traceability. - Updated project dependencies and added references to new analyzer plugins. - Revised task documentation to reflect current status and dependencies.
154 lines
5.4 KiB
C#
154 lines
5.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Scanner.Analyzers.OS;
|
|
using StellaOps.Scanner.Analyzers.OS.Abstractions;
|
|
using StellaOps.Scanner.Analyzers.OS.Plugin;
|
|
using StellaOps.Scanner.Core.Contracts;
|
|
using StellaOps.Scanner.Worker.Options;
|
|
|
|
namespace StellaOps.Scanner.Worker.Processing;
|
|
|
|
internal sealed class OsScanAnalyzerDispatcher : IScanAnalyzerDispatcher
|
|
{
|
|
private readonly IServiceScopeFactory _scopeFactory;
|
|
private readonly OsAnalyzerPluginCatalog _catalog;
|
|
private readonly ScannerWorkerOptions _options;
|
|
private readonly ILogger<OsScanAnalyzerDispatcher> _logger;
|
|
private IReadOnlyList<string> _pluginDirectories = Array.Empty<string>();
|
|
|
|
public OsScanAnalyzerDispatcher(
|
|
IServiceScopeFactory scopeFactory,
|
|
OsAnalyzerPluginCatalog catalog,
|
|
IOptions<ScannerWorkerOptions> options,
|
|
ILogger<OsScanAnalyzerDispatcher> logger)
|
|
{
|
|
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
|
_catalog = catalog ?? throw new ArgumentNullException(nameof(catalog));
|
|
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
|
|
LoadPlugins();
|
|
}
|
|
|
|
public async ValueTask ExecuteAsync(ScanJobContext context, CancellationToken cancellationToken)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(context);
|
|
|
|
using var scope = _scopeFactory.CreateScope();
|
|
var services = scope.ServiceProvider;
|
|
var analyzers = _catalog.CreateAnalyzers(services);
|
|
if (analyzers.Count == 0)
|
|
{
|
|
_logger.LogWarning("No OS analyzers available; skipping analyzer stage for job {JobId}.", context.JobId);
|
|
return;
|
|
}
|
|
|
|
var metadata = new Dictionary<string, string>(context.Lease.Metadata, StringComparer.Ordinal);
|
|
var rootfsPath = ResolvePath(metadata, _options.Analyzers.RootFilesystemMetadataKey);
|
|
if (rootfsPath is null)
|
|
{
|
|
_logger.LogWarning(
|
|
"Metadata key '{MetadataKey}' missing for job {JobId}; unable to locate root filesystem. OS analyzers skipped.",
|
|
_options.Analyzers.RootFilesystemMetadataKey,
|
|
context.JobId);
|
|
return;
|
|
}
|
|
|
|
var workspacePath = ResolvePath(metadata, _options.Analyzers.WorkspaceMetadataKey);
|
|
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
|
|
|
var results = new List<OSPackageAnalyzerResult>(analyzers.Count);
|
|
|
|
foreach (var analyzer in analyzers)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
var analyzerLogger = loggerFactory.CreateLogger(analyzer.GetType());
|
|
var analyzerContext = new OSPackageAnalyzerContext(rootfsPath, workspacePath, context.TimeProvider, analyzerLogger, metadata);
|
|
|
|
try
|
|
{
|
|
var result = await analyzer.AnalyzeAsync(analyzerContext, cancellationToken).ConfigureAwait(false);
|
|
results.Add(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Analyzer {AnalyzerId} failed for job {JobId}.", analyzer.AnalyzerId, context.JobId);
|
|
}
|
|
}
|
|
|
|
if (results.Count > 0)
|
|
{
|
|
var dictionary = results.ToDictionary(result => result.AnalyzerId, StringComparer.OrdinalIgnoreCase);
|
|
context.Analysis.Set(ScanAnalysisKeys.OsPackageAnalyzers, dictionary);
|
|
}
|
|
}
|
|
|
|
private void LoadPlugins()
|
|
{
|
|
var directories = new List<string>();
|
|
foreach (var configured in _options.Analyzers.PluginDirectories)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(configured))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var path = configured;
|
|
if (!Path.IsPathRooted(path))
|
|
{
|
|
path = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, path));
|
|
}
|
|
|
|
directories.Add(path);
|
|
}
|
|
|
|
if (directories.Count == 0)
|
|
{
|
|
directories.Add(Path.Combine(AppContext.BaseDirectory, "plugins", "scanner", "analyzers", "os"));
|
|
}
|
|
|
|
_pluginDirectories = new ReadOnlyCollection<string>(directories);
|
|
|
|
for (var i = 0; i < _pluginDirectories.Count; i++)
|
|
{
|
|
var directory = _pluginDirectories[i];
|
|
var seal = i == _pluginDirectories.Count - 1;
|
|
|
|
try
|
|
{
|
|
_catalog.LoadFromDirectory(directory, seal);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to load analyzer plug-ins from {Directory}.", directory);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string? ResolvePath(IReadOnlyDictionary<string, string> metadata, string key)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!metadata.TryGetValue(key, out var value) || string.IsNullOrWhiteSpace(value))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var trimmed = value.Trim();
|
|
return Path.IsPathRooted(trimmed)
|
|
? trimmed
|
|
: Path.GetFullPath(trimmed);
|
|
}
|
|
}
|