feat: Add initial implementation of Vulnerability Resolver Jobs
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Created project for StellaOps.Scanner.Analyzers.Native.Tests with necessary dependencies. - Documented roles and guidelines in AGENTS.md for Scheduler module. - Implemented IResolverJobService interface and InMemoryResolverJobService for handling resolver jobs. - Added ResolverBacklogNotifier and ResolverBacklogService for monitoring job metrics. - Developed API endpoints for managing resolver jobs and retrieving metrics. - Defined models for resolver job requests and responses. - Integrated dependency injection for resolver job services. - Implemented ImpactIndexSnapshot for persisting impact index data. - Introduced SignalsScoringOptions for configurable scoring weights in reachability scoring. - Added unit tests for ReachabilityScoringService and RuntimeFactsIngestionService. - Created dotnet-filter.sh script to handle command-line arguments for dotnet. - Established nuget-prime project for managing package downloads.
This commit is contained in:
@@ -130,11 +130,11 @@ public sealed class RoaringImpactIndexTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveAllAsync_UsageOnlyFiltersEntrypointImages()
|
||||
{
|
||||
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
|
||||
var (entryStream, entryDigest) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
|
||||
var nonEntryDigestValue = "sha256:" + new string('1', 64);
|
||||
public async Task ResolveAllAsync_UsageOnlyFiltersEntrypointImages()
|
||||
{
|
||||
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
|
||||
var (entryStream, entryDigest) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
|
||||
var nonEntryDigestValue = "sha256:" + new string('1', 64);
|
||||
var (nonEntryStream, nonEntryDigest) = CreateBomIndex(component, ComponentUsage.Create(false), nonEntryDigestValue);
|
||||
|
||||
var index = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
|
||||
@@ -159,12 +159,88 @@ public sealed class RoaringImpactIndexTests
|
||||
|
||||
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
|
||||
|
||||
var usageOnly = await index.ResolveAllAsync(selector, usageOnly: true);
|
||||
usageOnly.Images.Should().ContainSingle(image => image.ImageDigest == entryDigest);
|
||||
|
||||
var allImages = await index.ResolveAllAsync(selector, usageOnly: false);
|
||||
allImages.Images.Should().HaveCount(2);
|
||||
}
|
||||
var usageOnly = await index.ResolveAllAsync(selector, usageOnly: true);
|
||||
usageOnly.Images.Should().ContainSingle(image => image.ImageDigest == entryDigest);
|
||||
|
||||
var allImages = await index.ResolveAllAsync(selector, usageOnly: false);
|
||||
allImages.Images.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RemoveAsync_RemovesImageAndComponents()
|
||||
{
|
||||
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
|
||||
var (stream1, digest1) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
|
||||
var (stream2, digest2) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
|
||||
|
||||
var index = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
|
||||
|
||||
await index.IngestAsync(new ImpactIndexIngestionRequest
|
||||
{
|
||||
TenantId = "tenant-alpha",
|
||||
ImageDigest = digest1,
|
||||
Registry = "docker.io",
|
||||
Repository = "library/service",
|
||||
BomIndexStream = stream1,
|
||||
});
|
||||
|
||||
await index.IngestAsync(new ImpactIndexIngestionRequest
|
||||
{
|
||||
TenantId = "tenant-alpha",
|
||||
ImageDigest = digest2,
|
||||
Registry = "docker.io",
|
||||
Repository = "library/service",
|
||||
BomIndexStream = stream2,
|
||||
});
|
||||
|
||||
await index.RemoveAsync(digest1);
|
||||
|
||||
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
|
||||
var impact = await index.ResolveByPurlsAsync(new[] { "pkg:npm/a@1.0.0" }, usageOnly: false, selector);
|
||||
|
||||
impact.Images.Should().ContainSingle(img => img.ImageDigest == digest2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateSnapshotAsync_CompactsIdsAndRestores()
|
||||
{
|
||||
var component = ComponentIdentity.Create("pkg:npm/a@1.0.0", "a", "1.0.0", "pkg:npm/a@1.0.0");
|
||||
var (stream1, digest1) = CreateBomIndex(component, ComponentUsage.Create(true, new[] { "/start.sh" }));
|
||||
var (stream2, digest2) = CreateBomIndex(component, ComponentUsage.Create(false));
|
||||
|
||||
var index = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
|
||||
|
||||
await index.IngestAsync(new ImpactIndexIngestionRequest
|
||||
{
|
||||
TenantId = "tenant-alpha",
|
||||
ImageDigest = digest1,
|
||||
Registry = "docker.io",
|
||||
Repository = "library/service",
|
||||
BomIndexStream = stream1,
|
||||
});
|
||||
|
||||
await index.IngestAsync(new ImpactIndexIngestionRequest
|
||||
{
|
||||
TenantId = "tenant-alpha",
|
||||
ImageDigest = digest2,
|
||||
Registry = "docker.io",
|
||||
Repository = "library/service",
|
||||
BomIndexStream = stream2,
|
||||
});
|
||||
|
||||
await index.RemoveAsync(digest1);
|
||||
|
||||
var snapshot = await index.CreateSnapshotAsync();
|
||||
|
||||
var restored = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
|
||||
await restored.RestoreSnapshotAsync(snapshot);
|
||||
|
||||
var selector = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
|
||||
var resolved = await restored.ResolveAllAsync(selector, usageOnly: false);
|
||||
|
||||
resolved.Images.Should().ContainSingle(img => img.ImageDigest == digest2);
|
||||
resolved.SnapshotId.Should().Be(snapshot.SnapshotId);
|
||||
}
|
||||
|
||||
private static (Stream Stream, string Digest) CreateBomIndex(ComponentIdentity identity, ComponentUsage usage, string? digest = null)
|
||||
{
|
||||
|
||||
@@ -213,5 +213,19 @@ public sealed class ImpactTargetingServiceTests
|
||||
public ValueTask<ImpactSet> ResolveAllAsync(Selector selector, bool usageOnly, CancellationToken cancellationToken = default)
|
||||
=> OnResolveAll?.Invoke(selector, usageOnly, cancellationToken)
|
||||
?? ValueTask.FromResult(CreateEmptyImpactSet(selector, usageOnly));
|
||||
|
||||
public ValueTask RemoveAsync(string imageDigest, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
|
||||
public ValueTask<ImpactIndexSnapshot> CreateSnapshotAsync(CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult(new ImpactIndexSnapshot(
|
||||
DateTimeOffset.UtcNow,
|
||||
"stub",
|
||||
ImmutableArray<ImpactImageRecord>.Empty,
|
||||
ImmutableDictionary<string, ImmutableArray<int>>.Empty,
|
||||
ImmutableDictionary<string, ImmutableArray<int>>.Empty));
|
||||
|
||||
public ValueTask RestoreSnapshotAsync(ImpactIndexSnapshot snapshot, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MongoDB.Driver;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Mongo.Repositories;
|
||||
using StellaOps.Scheduler.Worker.Options;
|
||||
using StellaOps.Scheduler.Worker.Policy;
|
||||
using StellaOps.Scheduler.Worker.Observability;
|
||||
|
||||
namespace StellaOps.Scheduler.Worker.Tests;
|
||||
|
||||
@@ -35,7 +37,7 @@ public sealed class PolicyRunDispatchBackgroundServiceTests
|
||||
var executionService = new PolicyRunExecutionService(
|
||||
repository,
|
||||
new StubPolicyRunClient(),
|
||||
Options.Create(options),
|
||||
Microsoft.Extensions.Options.Options.Create(options),
|
||||
timeProvider: null,
|
||||
new SchedulerWorkerMetrics(),
|
||||
new StubPolicyRunTargetingService(),
|
||||
@@ -45,7 +47,7 @@ public sealed class PolicyRunDispatchBackgroundServiceTests
|
||||
return new PolicyRunDispatchBackgroundService(
|
||||
repository,
|
||||
executionService,
|
||||
Options.Create(options),
|
||||
Microsoft.Extensions.Options.Options.Create(options),
|
||||
timeProvider: null,
|
||||
NullLogger<PolicyRunDispatchBackgroundService>.Instance);
|
||||
}
|
||||
@@ -61,7 +63,9 @@ public sealed class PolicyRunDispatchBackgroundServiceTests
|
||||
|
||||
private sealed class RecordingPolicyRunJobRepository : IPolicyRunJobRepository
|
||||
{
|
||||
public int LeaseAttempts { get; private set; }
|
||||
private int _leaseAttempts;
|
||||
|
||||
public int LeaseAttempts => Volatile.Read(ref _leaseAttempts);
|
||||
|
||||
public Task InsertAsync(PolicyRunJob job, IClientSessionHandle? session = null, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
@@ -80,7 +84,7 @@ public sealed class PolicyRunDispatchBackgroundServiceTests
|
||||
IClientSessionHandle? session = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
Interlocked.Increment(ref LeaseAttempts);
|
||||
Interlocked.Increment(ref _leaseAttempts);
|
||||
return Task.FromResult<PolicyRunJob?>(null);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user