Refactor code structure for improved readability and maintainability
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-06 21:48:12 +02:00
parent f6c22854a4
commit dd0067ea0b
105 changed files with 12662 additions and 427 deletions

View File

@@ -0,0 +1,79 @@
using MongoDB.Bson.Serialization.Attributes;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Storage.Catalog;
[BsonIgnoreExtraElements]
public sealed class BunPackageInventoryDocument
{
[BsonId]
public string ScanId { get; set; } = string.Empty;
[BsonElement("imageDigest")]
[BsonIgnoreIfNull]
public string? ImageDigest { get; set; }
= null;
[BsonElement("generatedAtUtc")]
public DateTime GeneratedAtUtc { get; set; }
= DateTime.UtcNow;
[BsonElement("packages")]
public List<BunPackageDocument> Packages { get; set; }
= new();
}
[BsonIgnoreExtraElements]
public sealed class BunPackageDocument
{
[BsonElement("id")]
public string Id { get; set; } = string.Empty;
[BsonElement("name")]
public string Name { get; set; } = string.Empty;
[BsonElement("version")]
[BsonIgnoreIfNull]
public string? Version { get; set; }
= null;
[BsonElement("source")]
[BsonIgnoreIfNull]
public string? Source { get; set; }
= null;
[BsonElement("resolved")]
[BsonIgnoreIfNull]
public string? Resolved { get; set; }
= null;
[BsonElement("integrity")]
[BsonIgnoreIfNull]
public string? Integrity { get; set; }
= null;
[BsonElement("isDev")]
[BsonIgnoreIfNull]
public bool? IsDev { get; set; }
= null;
[BsonElement("isDirect")]
[BsonIgnoreIfNull]
public bool? IsDirect { get; set; }
= null;
[BsonElement("isPatched")]
[BsonIgnoreIfNull]
public bool? IsPatched { get; set; }
= null;
[BsonElement("provenance")]
[BsonIgnoreIfNull]
public BunPackageProvenance? Provenance { get; set; }
= null;
[BsonElement("metadata")]
[BsonIgnoreIfNull]
public Dictionary<string, string?>? Metadata { get; set; }
= null;
}

View File

@@ -1,16 +1,16 @@
using System;
using System.Net.Http;
using Amazon;
using Amazon.S3;
using Amazon.Runtime;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Scanner.Core.Contracts;
using StellaOps.Scanner.EntryTrace;
using System;
using System.Net.Http;
using Amazon;
using Amazon.S3;
using Amazon.Runtime;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Scanner.Core.Contracts;
using StellaOps.Scanner.EntryTrace;
using StellaOps.Scanner.Storage.Migrations;
using StellaOps.Scanner.Storage.Mongo;
using StellaOps.Scanner.Storage.ObjectStore;
@@ -62,65 +62,67 @@ public static class ServiceCollectionExtensions
services.TryAddSingleton<MongoBootstrapper>();
services.TryAddSingleton<ArtifactRepository>();
services.TryAddSingleton<ImageRepository>();
services.TryAddSingleton<LayerRepository>();
services.TryAddSingleton<LinkRepository>();
services.TryAddSingleton<JobRepository>();
services.TryAddSingleton<LifecycleRuleRepository>();
services.TryAddSingleton<RuntimeEventRepository>();
services.TryAddSingleton<EntryTraceRepository>();
services.TryAddSingleton<RubyPackageInventoryRepository>();
services.AddSingleton<IEntryTraceResultStore, EntryTraceResultStore>();
services.AddSingleton<IRubyPackageInventoryStore, RubyPackageInventoryStore>();
services.AddHttpClient(RustFsArtifactObjectStore.HttpClientName)
.ConfigureHttpClient((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<ScannerStorageOptions>>().Value.ObjectStore;
if (!options.IsRustFsDriver())
{
return;
}
if (!Uri.TryCreate(options.RustFs.BaseUrl, UriKind.Absolute, out var baseUri))
{
throw new InvalidOperationException("RustFS baseUrl must be a valid absolute URI.");
}
client.BaseAddress = baseUri;
client.Timeout = options.RustFs.Timeout;
foreach (var header in options.Headers)
{
client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
}
if (!string.IsNullOrWhiteSpace(options.RustFs.ApiKeyHeader)
&& !string.IsNullOrWhiteSpace(options.RustFs.ApiKey))
{
client.DefaultRequestHeaders.TryAddWithoutValidation(options.RustFs.ApiKeyHeader, options.RustFs.ApiKey);
}
})
.ConfigurePrimaryHttpMessageHandler(sp =>
{
var options = sp.GetRequiredService<IOptions<ScannerStorageOptions>>().Value.ObjectStore;
if (!options.IsRustFsDriver())
{
return new HttpClientHandler();
}
var handler = new HttpClientHandler();
if (options.RustFs.AllowInsecureTls)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
return handler;
});
services.TryAddSingleton(CreateAmazonS3Client);
services.TryAddSingleton<IArtifactObjectStore>(CreateArtifactObjectStore);
services.TryAddSingleton<ArtifactStorageService>();
}
services.TryAddSingleton<LayerRepository>();
services.TryAddSingleton<LinkRepository>();
services.TryAddSingleton<JobRepository>();
services.TryAddSingleton<LifecycleRuleRepository>();
services.TryAddSingleton<RuntimeEventRepository>();
services.TryAddSingleton<EntryTraceRepository>();
services.TryAddSingleton<RubyPackageInventoryRepository>();
services.TryAddSingleton<BunPackageInventoryRepository>();
services.AddSingleton<IEntryTraceResultStore, EntryTraceResultStore>();
services.AddSingleton<IRubyPackageInventoryStore, RubyPackageInventoryStore>();
services.AddSingleton<IBunPackageInventoryStore, BunPackageInventoryStore>();
services.AddHttpClient(RustFsArtifactObjectStore.HttpClientName)
.ConfigureHttpClient((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<ScannerStorageOptions>>().Value.ObjectStore;
if (!options.IsRustFsDriver())
{
return;
}
if (!Uri.TryCreate(options.RustFs.BaseUrl, UriKind.Absolute, out var baseUri))
{
throw new InvalidOperationException("RustFS baseUrl must be a valid absolute URI.");
}
client.BaseAddress = baseUri;
client.Timeout = options.RustFs.Timeout;
foreach (var header in options.Headers)
{
client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
}
if (!string.IsNullOrWhiteSpace(options.RustFs.ApiKeyHeader)
&& !string.IsNullOrWhiteSpace(options.RustFs.ApiKey))
{
client.DefaultRequestHeaders.TryAddWithoutValidation(options.RustFs.ApiKeyHeader, options.RustFs.ApiKey);
}
})
.ConfigurePrimaryHttpMessageHandler(sp =>
{
var options = sp.GetRequiredService<IOptions<ScannerStorageOptions>>().Value.ObjectStore;
if (!options.IsRustFsDriver())
{
return new HttpClientHandler();
}
var handler = new HttpClientHandler();
if (options.RustFs.AllowInsecureTls)
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
return handler;
});
services.TryAddSingleton(CreateAmazonS3Client);
services.TryAddSingleton<IArtifactObjectStore>(CreateArtifactObjectStore);
services.TryAddSingleton<ArtifactStorageService>();
}
private static IMongoClient CreateMongoClient(IServiceProvider provider)
{
@@ -149,47 +151,47 @@ public static class ServiceCollectionExtensions
return client.GetDatabase(databaseName);
}
private static IAmazonS3 CreateAmazonS3Client(IServiceProvider provider)
{
var options = provider.GetRequiredService<IOptions<ScannerStorageOptions>>().Value.ObjectStore;
var config = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.GetBySystemName(options.Region),
ForcePathStyle = options.ForcePathStyle,
};
if (!string.IsNullOrWhiteSpace(options.ServiceUrl))
{
config.ServiceURL = options.ServiceUrl;
}
if (!string.IsNullOrWhiteSpace(options.AccessKeyId) && !string.IsNullOrWhiteSpace(options.SecretAccessKey))
{
AWSCredentials credentials = string.IsNullOrWhiteSpace(options.SessionToken)
? new BasicAWSCredentials(options.AccessKeyId, options.SecretAccessKey)
: new SessionAWSCredentials(options.AccessKeyId, options.SecretAccessKey, options.SessionToken);
return new AmazonS3Client(credentials, config);
}
return new AmazonS3Client(config);
}
private static IArtifactObjectStore CreateArtifactObjectStore(IServiceProvider provider)
{
var options = provider.GetRequiredService<IOptions<ScannerStorageOptions>>();
var objectStore = options.Value.ObjectStore;
if (objectStore.IsRustFsDriver())
{
return new RustFsArtifactObjectStore(
provider.GetRequiredService<IHttpClientFactory>(),
options,
provider.GetRequiredService<ILogger<RustFsArtifactObjectStore>>());
}
return new S3ArtifactObjectStore(
provider.GetRequiredService<IAmazonS3>(),
options,
provider.GetRequiredService<ILogger<S3ArtifactObjectStore>>());
}
}
private static IAmazonS3 CreateAmazonS3Client(IServiceProvider provider)
{
var options = provider.GetRequiredService<IOptions<ScannerStorageOptions>>().Value.ObjectStore;
var config = new AmazonS3Config
{
RegionEndpoint = RegionEndpoint.GetBySystemName(options.Region),
ForcePathStyle = options.ForcePathStyle,
};
if (!string.IsNullOrWhiteSpace(options.ServiceUrl))
{
config.ServiceURL = options.ServiceUrl;
}
if (!string.IsNullOrWhiteSpace(options.AccessKeyId) && !string.IsNullOrWhiteSpace(options.SecretAccessKey))
{
AWSCredentials credentials = string.IsNullOrWhiteSpace(options.SessionToken)
? new BasicAWSCredentials(options.AccessKeyId, options.SecretAccessKey)
: new SessionAWSCredentials(options.AccessKeyId, options.SecretAccessKey, options.SessionToken);
return new AmazonS3Client(credentials, config);
}
return new AmazonS3Client(config);
}
private static IArtifactObjectStore CreateArtifactObjectStore(IServiceProvider provider)
{
var options = provider.GetRequiredService<IOptions<ScannerStorageOptions>>();
var objectStore = options.Value.ObjectStore;
if (objectStore.IsRustFsDriver())
{
return new RustFsArtifactObjectStore(
provider.GetRequiredService<IHttpClientFactory>(),
options,
provider.GetRequiredService<ILogger<RustFsArtifactObjectStore>>());
}
return new S3ArtifactObjectStore(
provider.GetRequiredService<IAmazonS3>(),
options,
provider.GetRequiredService<ILogger<S3ArtifactObjectStore>>());
}
}

View File

@@ -16,14 +16,15 @@ public sealed class MongoCollectionProvider
}
public IMongoCollection<ArtifactDocument> Artifacts => GetCollection<ArtifactDocument>(ScannerStorageDefaults.Collections.Artifacts);
public IMongoCollection<ImageDocument> Images => GetCollection<ImageDocument>(ScannerStorageDefaults.Collections.Images);
public IMongoCollection<LayerDocument> Layers => GetCollection<LayerDocument>(ScannerStorageDefaults.Collections.Layers);
public IMongoCollection<LinkDocument> Links => GetCollection<LinkDocument>(ScannerStorageDefaults.Collections.Links);
public IMongoCollection<JobDocument> Jobs => GetCollection<JobDocument>(ScannerStorageDefaults.Collections.Jobs);
public IMongoCollection<LifecycleRuleDocument> LifecycleRules => GetCollection<LifecycleRuleDocument>(ScannerStorageDefaults.Collections.LifecycleRules);
public IMongoCollection<RuntimeEventDocument> RuntimeEvents => GetCollection<RuntimeEventDocument>(ScannerStorageDefaults.Collections.RuntimeEvents);
public IMongoCollection<EntryTraceDocument> EntryTrace => GetCollection<EntryTraceDocument>(ScannerStorageDefaults.Collections.EntryTrace);
public IMongoCollection<RubyPackageInventoryDocument> RubyPackages => GetCollection<RubyPackageInventoryDocument>(ScannerStorageDefaults.Collections.RubyPackages);
public IMongoCollection<ImageDocument> Images => GetCollection<ImageDocument>(ScannerStorageDefaults.Collections.Images);
public IMongoCollection<LayerDocument> Layers => GetCollection<LayerDocument>(ScannerStorageDefaults.Collections.Layers);
public IMongoCollection<LinkDocument> Links => GetCollection<LinkDocument>(ScannerStorageDefaults.Collections.Links);
public IMongoCollection<JobDocument> Jobs => GetCollection<JobDocument>(ScannerStorageDefaults.Collections.Jobs);
public IMongoCollection<LifecycleRuleDocument> LifecycleRules => GetCollection<LifecycleRuleDocument>(ScannerStorageDefaults.Collections.LifecycleRules);
public IMongoCollection<RuntimeEventDocument> RuntimeEvents => GetCollection<RuntimeEventDocument>(ScannerStorageDefaults.Collections.RuntimeEvents);
public IMongoCollection<EntryTraceDocument> EntryTrace => GetCollection<EntryTraceDocument>(ScannerStorageDefaults.Collections.EntryTrace);
public IMongoCollection<RubyPackageInventoryDocument> RubyPackages => GetCollection<RubyPackageInventoryDocument>(ScannerStorageDefaults.Collections.RubyPackages);
public IMongoCollection<BunPackageInventoryDocument> BunPackages => GetCollection<BunPackageInventoryDocument>(ScannerStorageDefaults.Collections.BunPackages);
private IMongoCollection<TDocument> GetCollection<TDocument>(string name)
{

View File

@@ -0,0 +1,33 @@
using MongoDB.Driver;
using StellaOps.Scanner.Storage.Catalog;
using StellaOps.Scanner.Storage.Mongo;
namespace StellaOps.Scanner.Storage.Repositories;
public sealed class BunPackageInventoryRepository
{
private readonly MongoCollectionProvider _collections;
public BunPackageInventoryRepository(MongoCollectionProvider collections)
{
_collections = collections ?? throw new ArgumentNullException(nameof(collections));
}
public async Task<BunPackageInventoryDocument?> GetAsync(string scanId, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(scanId);
return await _collections.BunPackages
.Find(x => x.ScanId == scanId)
.FirstOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
}
public async Task UpsertAsync(BunPackageInventoryDocument document, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(document);
var options = new ReplaceOptions { IsUpsert = true };
await _collections.BunPackages
.ReplaceOneAsync(x => x.ScanId == document.ScanId, document, options, cancellationToken)
.ConfigureAwait(false);
}
}

View File

@@ -1,8 +1,8 @@
namespace StellaOps.Scanner.Storage;
public static class ScannerStorageDefaults
{
public const string DefaultDatabaseName = "scanner";
namespace StellaOps.Scanner.Storage;
public static class ScannerStorageDefaults
{
public const string DefaultDatabaseName = "scanner";
public const string DefaultBucketName = "stellaops";
public const string DefaultRootPrefix = "scanner";
@@ -24,9 +24,10 @@ public static class ScannerStorageDefaults
public const string RuntimeEvents = "runtime.events";
public const string EntryTrace = "entrytrace";
public const string RubyPackages = "ruby.packages";
public const string BunPackages = "bun.packages";
public const string Migrations = "schema_migrations";
}
public static class ObjectPrefixes
{
public const string Layers = "layers";

View File

@@ -0,0 +1,90 @@
using System.Collections.Immutable;
using StellaOps.Scanner.Core.Contracts;
using StellaOps.Scanner.Storage.Catalog;
using StellaOps.Scanner.Storage.Repositories;
namespace StellaOps.Scanner.Storage.Services;
public sealed class BunPackageInventoryStore : IBunPackageInventoryStore
{
private readonly BunPackageInventoryRepository _repository;
public BunPackageInventoryStore(BunPackageInventoryRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}
public async Task StoreAsync(BunPackageInventory inventory, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(inventory);
var document = new BunPackageInventoryDocument
{
ScanId = inventory.ScanId,
ImageDigest = inventory.ImageDigest,
GeneratedAtUtc = inventory.GeneratedAtUtc.UtcDateTime,
Packages = inventory.Packages.Select(ToDocument).ToList()
};
await _repository.UpsertAsync(document, cancellationToken).ConfigureAwait(false);
}
public async Task<BunPackageInventory?> GetAsync(string scanId, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrWhiteSpace(scanId);
var document = await _repository.GetAsync(scanId, cancellationToken).ConfigureAwait(false);
if (document is null)
{
return null;
}
var generatedAt = DateTime.SpecifyKind(document.GeneratedAtUtc, DateTimeKind.Utc);
var packages = document.Packages?.Select(FromDocument).ToImmutableArray()
?? ImmutableArray<BunPackageArtifact>.Empty;
return new BunPackageInventory(
document.ScanId,
document.ImageDigest ?? string.Empty,
new DateTimeOffset(generatedAt),
packages);
}
private static BunPackageDocument ToDocument(BunPackageArtifact artifact)
{
var doc = new BunPackageDocument
{
Id = artifact.Id,
Name = artifact.Name,
Version = artifact.Version,
Source = artifact.Source,
Resolved = artifact.Resolved,
Integrity = artifact.Integrity,
IsDev = artifact.IsDev,
IsDirect = artifact.IsDirect,
IsPatched = artifact.IsPatched,
Provenance = artifact.Provenance,
Metadata = artifact.Metadata is null ? null : new Dictionary<string, string?>(artifact.Metadata, StringComparer.OrdinalIgnoreCase)
};
return doc;
}
private static BunPackageArtifact FromDocument(BunPackageDocument document)
{
IReadOnlyDictionary<string, string?>? metadata = document.Metadata;
return new BunPackageArtifact(
document.Id,
document.Name,
document.Version,
document.Source,
document.Resolved,
document.Integrity,
document.IsDev,
document.IsDirect,
document.IsPatched,
document.Provenance,
metadata);
}
}