Implement ledger metrics for observability and add tests for Ruby packages endpoints
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
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:
82
src/StellaOps.Events.Mongo/EventProvenanceWriter.cs
Normal file
82
src/StellaOps.Events.Mongo/EventProvenanceWriter.cs
Normal 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"]);
|
||||
}
|
||||
}
|
||||
25
src/StellaOps.Events.Mongo/EventWriter.cs
Normal file
25
src/StellaOps.Events.Mongo/EventWriter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
45
src/StellaOps.Events.Mongo/MongoIndexes.cs
Normal file
45
src/StellaOps.Events.Mongo/MongoIndexes.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
17
src/StellaOps.Events.Mongo/StellaOps.Events.Mongo.csproj
Normal file
17
src/StellaOps.Events.Mongo/StellaOps.Events.Mongo.csproj
Normal 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>
|
||||
Reference in New Issue
Block a user