Implement ledger metrics for observability and add tests for Ruby packages endpoints
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Added `LedgerMetrics` class to record write latency and total events for ledger operations.
- Created comprehensive tests for Ruby packages endpoints, covering scenarios for missing inventory, successful retrieval, and identifier handling.
- Introduced `TestSurfaceSecretsScope` for managing environment variables during tests.
- Developed `ProvenanceMongoExtensions` for attaching DSSE provenance and trust information to event documents.
- Implemented `EventProvenanceWriter` and `EventWriter` classes for managing event provenance in MongoDB.
- Established MongoDB indexes for efficient querying of events based on provenance and trust.
- Added models and JSON parsing logic for DSSE provenance and trust information.
This commit is contained in:
master
2025-11-13 09:29:09 +02:00
parent 151f6b35cc
commit 61f963fd52
101 changed files with 5881 additions and 1776 deletions

View File

@@ -0,0 +1,82 @@
using System.IO;
using System.Text.Json;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Provenance.Mongo;
namespace StellaOps.Events.Mongo;
public sealed class EventProvenanceWriter
{
private readonly IMongoCollection<BsonDocument> _events;
public EventProvenanceWriter(IMongoDatabase database, string collectionName = "events")
{
if (database is null) throw new ArgumentNullException(nameof(database));
if (string.IsNullOrWhiteSpace(collectionName)) throw new ArgumentException("Collection name is required", nameof(collectionName));
_events = database.GetCollection<BsonDocument>(collectionName);
}
public Task AttachAsync(string eventId, DsseProvenance dsse, TrustInfo trust, CancellationToken cancellationToken = default)
{
var filter = BuildIdFilter(eventId);
return AttachAsync(filter, dsse, trust, cancellationToken);
}
public async Task AttachAsync(FilterDefinition<BsonDocument> filter, DsseProvenance dsse, TrustInfo trust, CancellationToken cancellationToken = default)
{
if (filter is null) throw new ArgumentNullException(nameof(filter));
if (dsse is null) throw new ArgumentNullException(nameof(dsse));
if (trust is null) throw new ArgumentNullException(nameof(trust));
var update = BuildUpdateDefinition(dsse, trust);
var result = await _events.UpdateOneAsync(filter, update, cancellationToken: cancellationToken).ConfigureAwait(false);
if (result.MatchedCount == 0)
{
throw new InvalidOperationException("Target event document not found.");
}
}
public async Task AttachFromJsonAsync(string eventId, string provenanceMetaJson, TrustInfo? trustOverride = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(provenanceMetaJson)) throw new ArgumentException("JSON payload is required.", nameof(provenanceMetaJson));
using var document = JsonDocument.Parse(provenanceMetaJson);
await AttachFromJsonElementAsync(eventId, document.RootElement, trustOverride, cancellationToken).ConfigureAwait(false);
}
public async Task AttachFromJsonAsync(string eventId, Stream provenanceMetaStream, TrustInfo? trustOverride = null, CancellationToken cancellationToken = default)
{
if (provenanceMetaStream is null) throw new ArgumentNullException(nameof(provenanceMetaStream));
var (dsse, trust) = await ProvenanceJsonParser.ParseAsync(provenanceMetaStream, trustOverride, cancellationToken).ConfigureAwait(false);
await AttachAsync(eventId, dsse, trust, cancellationToken).ConfigureAwait(false);
}
private Task AttachFromJsonElementAsync(string eventId, JsonElement root, TrustInfo? trustOverride, CancellationToken cancellationToken)
{
var (dsse, trust) = ProvenanceJsonParser.Parse(root, trustOverride);
return AttachAsync(eventId, dsse, trust, cancellationToken);
}
private static FilterDefinition<BsonDocument> BuildIdFilter(string eventId)
{
if (string.IsNullOrWhiteSpace(eventId)) throw new ArgumentException("Event identifier is required.", nameof(eventId));
return ObjectId.TryParse(eventId, out var objectId)
? Builders<BsonDocument>.Filter.Eq("_id", objectId)
: Builders<BsonDocument>.Filter.Eq("_id", eventId);
}
private static UpdateDefinition<BsonDocument> BuildUpdateDefinition(DsseProvenance dsse, TrustInfo trust)
{
var temp = new BsonDocument();
temp.AttachDsseProvenance(dsse, trust);
return Builders<BsonDocument>.Update
.Set("provenance", temp["provenance"])
.Set("trust", temp["trust"]);
}
}

View File

@@ -0,0 +1,25 @@
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Provenance.Mongo;
namespace StellaOps.Events.Mongo;
public sealed class EventWriter
{
private readonly IMongoCollection<BsonDocument> _events;
public EventWriter(IMongoDatabase db, string collectionName = "events")
{
_events = db.GetCollection<BsonDocument>(collectionName);
}
public async Task AppendEventAsync(
BsonDocument eventDoc,
DsseProvenance dsse,
TrustInfo trust,
CancellationToken ct = default)
{
eventDoc.AttachDsseProvenance(dsse, trust);
await _events.InsertOneAsync(eventDoc, cancellationToken: ct);
}
}

View File

@@ -0,0 +1,45 @@
using MongoDB.Bson;
using MongoDB.Driver;
namespace StellaOps.Events.Mongo;
public static class MongoIndexes
{
public static Task EnsureEventIndexesAsync(IMongoDatabase db, CancellationToken ct = default)
{
var events = db.GetCollection<BsonDocument>("events");
var models = new[]
{
new CreateIndexModel<BsonDocument>(
Builders<BsonDocument>.IndexKeys
.Ascending("subject.digest.sha256")
.Ascending("kind")
.Ascending("provenance.dsse.rekor.logIndex"),
new CreateIndexOptions
{
Name = "events_by_subject_kind_provenance"
}),
new CreateIndexModel<BsonDocument>(
Builders<BsonDocument>.IndexKeys
.Ascending("kind")
.Ascending("trust.verified")
.Ascending("provenance.dsse.rekor.logIndex"),
new CreateIndexOptions
{
Name = "events_unproven_by_kind"
}),
new CreateIndexModel<BsonDocument>(
Builders<BsonDocument>.IndexKeys
.Ascending("provenance.dsse.rekor.logIndex"),
new CreateIndexOptions
{
Name = "events_by_rekor_logindex"
})
};
return events.Indexes.CreateManyAsync(models, ct);
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../__Libraries/StellaOps.Provenance.Mongo/StellaOps.Provenance.Mongo.csproj" />
</ItemGroup>
</Project>