101 lines
3.4 KiB
C#
101 lines
3.4 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Artifact.Core;
|
|
namespace StellaOps.Artifact.Infrastructure;
|
|
|
|
public sealed partial class S3UnifiedArtifactStore
|
|
{
|
|
private async Task<ArtifactStoreResult?> TryGetExistingAsync(
|
|
ArtifactStoreRequest request,
|
|
string fullKey,
|
|
CancellationToken ct)
|
|
{
|
|
if (request.Overwrite || _options.AllowOverwrite)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var exists = await _client.ObjectExistsAsync(_options.BucketName, fullKey, ct).ConfigureAwait(false);
|
|
if (!exists)
|
|
{
|
|
return null;
|
|
}
|
|
_logger.LogInformation("Artifact already exists at {Key}, skipping", fullKey);
|
|
var entry = await _indexRepository.GetAsync(
|
|
request.BomRef,
|
|
request.SerialNumber,
|
|
request.ArtifactId,
|
|
ct).ConfigureAwait(false);
|
|
|
|
return entry == null
|
|
? null
|
|
: ArtifactStoreResult.Succeeded(fullKey, entry.Sha256, entry.SizeBytes, wasCreated: false);
|
|
}
|
|
private async Task<(byte[] ContentBytes, string Sha256)> ReadContentAsync(
|
|
ArtifactStoreRequest request,
|
|
CancellationToken ct)
|
|
{
|
|
using var memoryStream = new MemoryStream();
|
|
await request.Content.CopyToAsync(memoryStream, ct).ConfigureAwait(false);
|
|
var contentBytes = memoryStream.ToArray();
|
|
var sha256 = ComputeSha256(contentBytes);
|
|
return (contentBytes, sha256);
|
|
}
|
|
private async Task<string> WriteContentAsync(
|
|
ArtifactStoreRequest request,
|
|
string fullKey,
|
|
byte[] contentBytes,
|
|
string sha256,
|
|
CancellationToken ct)
|
|
{
|
|
if (_options.EnableDeduplication)
|
|
{
|
|
var existingBySha = await _indexRepository.FindBySha256Async(sha256, ct).ConfigureAwait(false);
|
|
if (existingBySha.Count > 0)
|
|
{
|
|
var existingKey = existingBySha[0].StorageKey;
|
|
_logger.LogInformation(
|
|
"Deduplicating artifact {ArtifactId} - content matches {ExistingKey}",
|
|
request.ArtifactId,
|
|
existingKey);
|
|
return existingKey;
|
|
}
|
|
}
|
|
await UploadAsync(fullKey, request, contentBytes, ct).ConfigureAwait(false);
|
|
return fullKey;
|
|
}
|
|
private async Task UploadAsync(string key, ArtifactStoreRequest request, byte[] contentBytes, CancellationToken ct)
|
|
{
|
|
using var uploadStream = new MemoryStream(contentBytes);
|
|
var metadata = BuildS3Metadata(request);
|
|
await _client.PutObjectAsync(
|
|
_options.BucketName,
|
|
key,
|
|
uploadStream,
|
|
request.ContentType,
|
|
metadata,
|
|
ct).ConfigureAwait(false);
|
|
}
|
|
private ArtifactIndexEntry BuildIndexEntry(
|
|
ArtifactStoreRequest request,
|
|
string storageKey,
|
|
string sha256,
|
|
long sizeBytes)
|
|
{
|
|
var now = _timeProvider.GetUtcNow();
|
|
return new ArtifactIndexEntry
|
|
{
|
|
Id = _guidProvider.NewGuid(),
|
|
TenantId = request.TenantId,
|
|
BomRef = request.BomRef,
|
|
SerialNumber = request.SerialNumber,
|
|
ArtifactId = request.ArtifactId,
|
|
StorageKey = storageKey,
|
|
Type = request.Type,
|
|
ContentType = request.ContentType,
|
|
Sha256 = sha256,
|
|
SizeBytes = sizeBytes,
|
|
CreatedAt = now
|
|
};
|
|
}
|
|
}
|