feat: Add Go module and workspace test fixtures
- Created expected JSON files for Go modules and workspaces. - Added go.mod and go.sum files for example projects. - Implemented private module structure with expected JSON output. - Introduced vendored dependencies with corresponding expected JSON. - Developed PostgresGraphJobStore for managing graph jobs. - Established SQL migration scripts for graph jobs schema. - Implemented GraphJobRepository for CRUD operations on graph jobs. - Created IGraphJobRepository interface for repository abstraction. - Added unit tests for GraphJobRepository to ensure functionality.
This commit is contained in:
@@ -1,36 +1,30 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.Surface.Models;
|
||||
|
||||
namespace StellaOps.Scanner.Surface.Discovery;
|
||||
|
||||
/// <summary>
|
||||
/// Registry for surface entry collectors.
|
||||
/// Manages collector registration and orchestrates collection.
|
||||
/// Registry for surface entry and entry point collectors.
|
||||
/// </summary>
|
||||
public interface ISurfaceEntryRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a collector.
|
||||
/// </summary>
|
||||
void Register(ISurfaceEntryCollector collector);
|
||||
/// <summary>Registers a surface entry collector.</summary>
|
||||
void RegisterCollector(ISurfaceEntryCollector collector);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all registered collectors.
|
||||
/// </summary>
|
||||
/// <summary>Registers an entry point collector.</summary>
|
||||
void RegisterEntryPointCollector(IEntryPointCollector collector);
|
||||
|
||||
/// <summary>Gets all registered surface entry collectors.</summary>
|
||||
IReadOnlyList<ISurfaceEntryCollector> GetCollectors();
|
||||
|
||||
/// <summary>
|
||||
/// Gets collectors that can analyze the given context.
|
||||
/// </summary>
|
||||
IReadOnlyList<ISurfaceEntryCollector> GetApplicableCollectors(SurfaceCollectionContext context);
|
||||
/// <summary>Gets all registered entry point collectors.</summary>
|
||||
IReadOnlyList<IEntryPointCollector> GetEntryPointCollectors();
|
||||
|
||||
/// <summary>
|
||||
/// Collects entries using all applicable collectors.
|
||||
/// </summary>
|
||||
IAsyncEnumerable<SurfaceEntry> CollectAllAsync(
|
||||
SurfaceCollectionContext context,
|
||||
CancellationToken cancellationToken = default);
|
||||
/// <summary>Gets collectors that support the specified surface type.</summary>
|
||||
IReadOnlyList<ISurfaceEntryCollector> GetCollectorsForType(SurfaceType type);
|
||||
|
||||
/// <summary>Gets entry point collectors that support the specified language.</summary>
|
||||
IReadOnlyList<IEntryPointCollector> GetEntryPointCollectorsForLanguage(string language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,6 +33,7 @@ public interface ISurfaceEntryRegistry
|
||||
public sealed class SurfaceEntryRegistry : ISurfaceEntryRegistry
|
||||
{
|
||||
private readonly List<ISurfaceEntryCollector> _collectors = [];
|
||||
private readonly List<IEntryPointCollector> _entryPointCollectors = [];
|
||||
private readonly ILogger<SurfaceEntryRegistry> _logger;
|
||||
private readonly object _lock = new();
|
||||
|
||||
@@ -47,141 +42,61 @@ public sealed class SurfaceEntryRegistry : ISurfaceEntryRegistry
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Register(ISurfaceEntryCollector collector)
|
||||
public void RegisterCollector(ISurfaceEntryCollector collector)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(collector);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
// Check for duplicate
|
||||
if (_collectors.Any(c => c.CollectorId == collector.CollectorId))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Collector {CollectorId} already registered, skipping duplicate",
|
||||
collector.CollectorId);
|
||||
_logger.LogWarning("Collector {CollectorId} already registered, skipping", collector.CollectorId);
|
||||
return;
|
||||
}
|
||||
|
||||
_collectors.Add(collector);
|
||||
_logger.LogDebug(
|
||||
"Registered surface collector {CollectorId} ({Name}) for languages: {Languages}",
|
||||
collector.CollectorId,
|
||||
collector.Name,
|
||||
string.Join(", ", collector.SupportedLanguages));
|
||||
_logger.LogDebug("Registered surface collector: {CollectorId}", collector.CollectorId);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterEntryPointCollector(IEntryPointCollector collector)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(collector);
|
||||
lock (_lock)
|
||||
{
|
||||
if (_entryPointCollectors.Any(c => c.CollectorId == collector.CollectorId))
|
||||
{
|
||||
_logger.LogWarning("Entry point collector {CollectorId} already registered, skipping", collector.CollectorId);
|
||||
return;
|
||||
}
|
||||
_entryPointCollectors.Add(collector);
|
||||
_logger.LogDebug("Registered entry point collector: {CollectorId}", collector.CollectorId);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISurfaceEntryCollector> GetCollectors()
|
||||
{
|
||||
lock (_lock) return [.. _collectors];
|
||||
}
|
||||
|
||||
public IReadOnlyList<IEntryPointCollector> GetEntryPointCollectors()
|
||||
{
|
||||
lock (_lock) return [.. _entryPointCollectors];
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISurfaceEntryCollector> GetCollectorsForType(SurfaceType type)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _collectors
|
||||
.OrderByDescending(c => c.Priority)
|
||||
.ToList();
|
||||
return [.. _collectors.Where(c => c.SupportedTypes.Contains(type))];
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ISurfaceEntryCollector> GetApplicableCollectors(SurfaceCollectionContext context)
|
||||
public IReadOnlyList<IEntryPointCollector> GetEntryPointCollectorsForLanguage(string language)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(language);
|
||||
lock (_lock)
|
||||
{
|
||||
var applicable = _collectors
|
||||
.Where(c => c.CanCollect(context))
|
||||
.OrderByDescending(c => c.Priority)
|
||||
.ToList();
|
||||
|
||||
// Filter by options if specified
|
||||
if (context.Options?.Collectors is { Count: > 0 } allowedCollectors)
|
||||
{
|
||||
applicable = applicable
|
||||
.Where(c => allowedCollectors.Contains(c.CollectorId))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return applicable;
|
||||
return [.. _entryPointCollectors.Where(c =>
|
||||
c.SupportedLanguages.Contains(language, StringComparer.OrdinalIgnoreCase))];
|
||||
}
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<SurfaceEntry> CollectAllAsync(
|
||||
SurfaceCollectionContext context,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var collectors = GetApplicableCollectors(context);
|
||||
|
||||
if (collectors.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("No applicable collectors for scan {ScanId}", context.ScanId);
|
||||
yield break;
|
||||
}
|
||||
|
||||
_logger.LogDebug(
|
||||
"Running {CollectorCount} collectors for scan {ScanId}",
|
||||
collectors.Count,
|
||||
context.ScanId);
|
||||
|
||||
var seenIds = new HashSet<string>();
|
||||
var entryCount = 0;
|
||||
var maxEntries = context.Options?.MaxEntries;
|
||||
|
||||
foreach (var collector in collectors)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
if (maxEntries.HasValue && entryCount >= maxEntries.Value)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Reached max entries limit ({MaxEntries}) for scan {ScanId}",
|
||||
maxEntries.Value,
|
||||
context.ScanId);
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.LogDebug(
|
||||
"Running collector {CollectorId} for scan {ScanId}",
|
||||
collector.CollectorId,
|
||||
context.ScanId);
|
||||
|
||||
await foreach (var entry in collector.CollectAsync(context, cancellationToken))
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
|
||||
// Apply confidence threshold
|
||||
if (context.Options?.ConfidenceThreshold is double threshold)
|
||||
{
|
||||
var confidenceValue = (int)entry.Confidence / 4.0;
|
||||
if (confidenceValue < threshold)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply type filters
|
||||
if (context.Options?.ExcludeTypes?.Contains(entry.Type) == true)
|
||||
continue;
|
||||
|
||||
if (context.Options?.IncludeTypes is { Count: > 0 } includeTypes &&
|
||||
!includeTypes.Contains(entry.Type))
|
||||
continue;
|
||||
|
||||
// Deduplicate by ID
|
||||
if (!seenIds.Add(entry.Id))
|
||||
continue;
|
||||
|
||||
entryCount++;
|
||||
yield return entry;
|
||||
|
||||
if (maxEntries.HasValue && entryCount >= maxEntries.Value)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug(
|
||||
"Collected {EntryCount} surface entries for scan {ScanId}",
|
||||
entryCount,
|
||||
context.ScanId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user