using StellaOps.AirGap.Bundle.Models; using StellaOps.AirGap.Bundle.Serialization; using StellaOps.AirGap.Bundle.Validation; namespace StellaOps.AirGap.Bundle.Services; public sealed class BundleLoader : IBundleLoader { private readonly IBundleValidator _validator; private readonly IFeedRegistry _feedRegistry; private readonly IPolicyRegistry _policyRegistry; private readonly ICryptoProviderRegistry _cryptoRegistry; public BundleLoader( IBundleValidator validator, IFeedRegistry feedRegistry, IPolicyRegistry policyRegistry, ICryptoProviderRegistry cryptoRegistry) { _validator = validator; _feedRegistry = feedRegistry; _policyRegistry = policyRegistry; _cryptoRegistry = cryptoRegistry; } public async Task LoadAsync(string bundlePath, CancellationToken ct = default) { var manifestPath = Path.Combine(bundlePath, "manifest.json"); if (!File.Exists(manifestPath)) { throw new FileNotFoundException("Bundle manifest not found", manifestPath); } var manifestJson = await File.ReadAllTextAsync(manifestPath, ct).ConfigureAwait(false); var manifest = BundleManifestSerializer.Deserialize(manifestJson); var validationResult = await _validator.ValidateAsync(manifest, bundlePath, ct).ConfigureAwait(false); if (!validationResult.IsValid) { var details = string.Join("; ", validationResult.Errors.Select(e => e.Message)); throw new InvalidOperationException($"Bundle validation failed: {details}"); } foreach (var feed in manifest.Feeds) { _feedRegistry.Register(feed, Path.Combine(bundlePath, feed.RelativePath)); } foreach (var policy in manifest.Policies) { _policyRegistry.Register(policy, Path.Combine(bundlePath, policy.RelativePath)); } foreach (var crypto in manifest.CryptoMaterials) { _cryptoRegistry.Register(crypto, Path.Combine(bundlePath, crypto.RelativePath)); } } } public interface IBundleLoader { Task LoadAsync(string bundlePath, CancellationToken ct = default); } public interface IFeedRegistry { void Register(FeedComponent component, string absolutePath); } public interface IPolicyRegistry { void Register(PolicyComponent component, string absolutePath); } public interface ICryptoProviderRegistry { void Register(CryptoComponent component, string absolutePath); }