using Amazon.S3; using Amazon.S3.Model; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace StellaOps.Scanner.Storage.ObjectStore; public sealed class S3ArtifactObjectStore : IArtifactObjectStore { private readonly IAmazonS3 _s3; private readonly ObjectStoreOptions _options; private readonly ILogger _logger; public S3ArtifactObjectStore(IAmazonS3 s3, IOptions options, ILogger logger) { _s3 = s3 ?? throw new ArgumentNullException(nameof(s3)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _options = (options ?? throw new ArgumentNullException(nameof(options))).Value.ObjectStore; } public async Task PutAsync(ArtifactObjectDescriptor descriptor, Stream content, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(descriptor); ArgumentNullException.ThrowIfNull(content); var request = new PutObjectRequest { BucketName = descriptor.Bucket, Key = descriptor.Key, InputStream = content, AutoCloseStream = false, }; if (descriptor.Immutable && _options.EnableObjectLock) { request.ObjectLockMode = ObjectLockMode.Compliance; if (descriptor.RetainFor is { } retention && retention > TimeSpan.Zero) { request.ObjectLockRetainUntilDate = DateTime.UtcNow + retention; } else if (_options.ComplianceRetention is { } defaultRetention && defaultRetention > TimeSpan.Zero) { request.ObjectLockRetainUntilDate = DateTime.UtcNow + defaultRetention; } } await _s3.PutObjectAsync(request, cancellationToken).ConfigureAwait(false); _logger.LogDebug("Uploaded scanner object {Bucket}/{Key}", descriptor.Bucket, descriptor.Key); } public async Task GetAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(descriptor); try { var response = await _s3.GetObjectAsync(descriptor.Bucket, descriptor.Key, cancellationToken).ConfigureAwait(false); var buffer = new MemoryStream(); await response.ResponseStream.CopyToAsync(buffer, cancellationToken).ConfigureAwait(false); buffer.Position = 0; return buffer; } catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { _logger.LogDebug("Scanner object {Bucket}/{Key} not found", descriptor.Bucket, descriptor.Key); return null; } } public async Task DeleteAsync(ArtifactObjectDescriptor descriptor, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(descriptor); await _s3.DeleteObjectAsync(descriptor.Bucket, descriptor.Key, cancellationToken).ConfigureAwait(false); _logger.LogDebug("Deleted scanner object {Bucket}/{Key}", descriptor.Bucket, descriptor.Key); } }