using Amazon; using Amazon.Runtime; using Amazon.S3; using DodoSSH.Client.Domain; using DodoSSH.Client.Ssh; namespace DodoSSH.Client.ObjectStore; /// Opens a bucket as a place with files in it. /// /// An interface so the file screen can be tested without a bucket, exactly as ISftpSessionFactory is /// what lets it be tested without a host. /// public interface IObjectStoreFactory { /// Builds a client for one bucket. /// The bucket and its credentials, decrypted. /// /// Synchronous and cheap: nothing is contacted here. S3 is request-per-operation, so there is no /// connect step to fail — the first thing that can fail is the first listing, which is where the /// credentials and the endpoint are actually tested. /// IRemoteFileStore Open(ObjectStoreSecret store); } /// Opens buckets with the AWS SDK. public sealed class S3ObjectStoreFactory : IObjectStoreFactory { /// public IRemoteFileStore Open(ObjectStoreSecret store) { ArgumentNullException.ThrowIfNull(store); if (!store.TryValidate(out var reason)) { throw new ArgumentException(reason, nameof(store)); } var config = new AmazonS3Config { // On for nearly every self-hosted service and off for AWS. It is a stored setting rather than // something inferred from the endpoint, because inferring it wrongly produces a DNS failure that // says nothing about buckets — see ObjectStoreSecret.UsePathStyle. ForcePathStyle = store.UsePathStyle, }; if (store.Endpoint is { } endpoint) { config.ServiceURL = endpoint; // Still set when there is one, because SigV4 signs the region into every request and several // S3-compatible services check it. The ones that do not, ignore it. if (store.Region is { } named) { config.AuthenticationRegion = named; } } else { // No endpoint means Amazon, and then the region is what resolves the host. Validation has // already refused the case where neither is set. config.RegionEndpoint = RegionEndpoint.GetBySystemName(store.Region!); } var credentials = new BasicAWSCredentials(store.AccessKeyId, store.SecretAccessKey); return new S3FileStore(new AmazonS3Client(credentials, config), store.Bucket); } }