From 98a43eb47e87071fbdea3c7475cf255056e38ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 00:48:53 +0100 Subject: [PATCH 1/8] Adding CacheFolder config to AzureBlobStorageCache --- .../Caching/AzureBlobStorageCache.cs | 11 +++++++++-- .../Caching/AzureBlobStorageCacheOptions.cs | 9 ++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs index 053a947a..9a3bcce2 100644 --- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs +++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Web.Caching.Azure; public class AzureBlobStorageCache : IImageCache { private readonly BlobContainerClient container; + private readonly string cacheFolder; /// /// Initializes a new instance of the class. @@ -27,12 +28,15 @@ public AzureBlobStorageCache(IOptions cacheOptions AzureBlobStorageCacheOptions options = cacheOptions.Value; this.container = new BlobContainerClient(options.ConnectionString, options.ContainerName); + this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder) + ? string.Empty + : options.CacheFolder.Trim().Trim('/') + '/'; } /// public async Task GetAsync(string key) { - BlobClient blob = this.container.GetBlobClient(key); + BlobClient blob = this.container.GetBlobClient(this.GetBlobName(key)); if (!await blob.ExistsAsync()) { @@ -45,7 +49,7 @@ public AzureBlobStorageCache(IOptions cacheOptions /// public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) { - BlobClient blob = this.container.GetBlobClient(key); + BlobClient blob = this.container.GetBlobClient(this.GetBlobName(key)); BlobHttpHeaders headers = new() { @@ -79,4 +83,7 @@ public static Response CreateIfNotExists( AzureBlobStorageCacheOptions options, PublicAccessType accessType) => new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType); + + private string GetBlobName(string key) + => this.cacheFolder + key; } diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs index f44bba41..72845174 100644 --- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs +++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs @@ -16,8 +16,15 @@ public class AzureBlobStorageCacheOptions /// /// Gets or sets the Azure Blob Storage container name. - /// Must conform to Azure Blob Storage container naming guidlines. + /// Must conform to Azure Blob Storage container naming guidelines. /// /// public string ContainerName { get; set; } = null!; + + /// + /// Gets or sets the cache folder name that'll store cache files under the configured container. + /// Must conform to Azure Blob Storage directory naming guidelines. + /// + /// + public string CacheFolder { get; set; } = null!; } From 5ff144c17e6c618c2efa4c84b98d24299ac7ed02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 00:58:36 +0100 Subject: [PATCH 2/8] Grammar --- .../Caching/AzureBlobStorageCacheOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs index 72845174..92c9c20e 100644 --- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs +++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs @@ -22,7 +22,7 @@ public class AzureBlobStorageCacheOptions public string ContainerName { get; set; } = null!; /// - /// Gets or sets the cache folder name that'll store cache files under the configured container. + /// Gets or sets the cache folder's name that'll store cache files under the configured container. /// Must conform to Azure Blob Storage directory naming guidelines. /// /// From 40b5b8c9e5a6b6a821dc73f733a9462d0b5f464f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 01:02:59 +0100 Subject: [PATCH 3/8] Adding CacheFolder config to AWSS3StorageCache --- .../Caching/AWSS3StorageCache.cs | 14 +++++++++++--- .../Caching/AWSS3StorageCacheOptions.cs | 5 +++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs index 79e12a04..0bccc027 100644 --- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs +++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs @@ -17,6 +17,7 @@ public class AWSS3StorageCache : IImageCache { private readonly IAmazonS3 amazonS3Client; private readonly string bucketName; + private readonly string cacheFolder; /// /// Initializes a new instance of the class. @@ -28,17 +29,21 @@ public AWSS3StorageCache(IOptions cacheOptions) AWSS3StorageCacheOptions options = cacheOptions.Value; this.bucketName = options.BucketName; this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options); + this.cacheFolder = string.IsNullOrEmpty(options.CacheFolder) + ? string.Empty + : options.CacheFolder.Trim().Trim('/') + '/'; } /// public async Task GetAsync(string key) { - GetObjectMetadataRequest request = new() { BucketName = this.bucketName, Key = key }; + string keyWithFolder = this.GetKeyWithFolder(key); + GetObjectMetadataRequest request = new() { BucketName = this.bucketName, Key = keyWithFolder }; try { // HEAD request throws a 404 if not found. MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata; - return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, key, metadata); + return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, keyWithFolder, metadata); } catch { @@ -52,7 +57,7 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) PutObjectRequest request = new() { BucketName = this.bucketName, - Key = key, + Key = this.GetKeyWithFolder(key), ContentType = metadata.ContentType, InputStream = stream, AutoCloseStream = false @@ -165,4 +170,7 @@ public static TResult RunSync(Func> task) }).Unwrap().GetAwaiter().GetResult(); } } + + private string GetKeyWithFolder(string key) + => this.cacheFolder + key; } diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs index 672d16fd..bf25a884 100644 --- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs +++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs @@ -14,6 +14,11 @@ public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions /// public string BucketName { get; set; } = null!; + /// + /// Gets or sets the cache folder's name that'll store cache files under the configured bucket. + /// + public string CacheFolder { get; set; } = null!; + /// public string? AccessKey { get; set; } From 9db0e6b161d496ea0ce3ab06af9e062e0bebe63d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Wed, 17 Jan 2024 01:11:36 +0100 Subject: [PATCH 4/8] Code styling --- .../Caching/AWSS3StorageCache.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs index 0bccc027..676dbeca 100644 --- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs +++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs @@ -123,6 +123,9 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) return null; } + private string GetKeyWithFolder(string key) + => this.cacheFolder + key; + /// /// /// @@ -170,7 +173,4 @@ public static TResult RunSync(Func> task) }).Unwrap().GetAwaiter().GetResult(); } } - - private string GetKeyWithFolder(string key) - => this.cacheFolder + key; } From 408610b8c5f4917b207f33a9060c7bb20cb98161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 25 Jan 2024 00:15:36 +0100 Subject: [PATCH 5/8] DRY --- .../Caching/AzureBlobStorageCache.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs index 9a3bcce2..05e8190c 100644 --- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs +++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCache.cs @@ -36,7 +36,7 @@ public AzureBlobStorageCache(IOptions cacheOptions /// public async Task GetAsync(string key) { - BlobClient blob = this.container.GetBlobClient(this.GetBlobName(key)); + BlobClient blob = this.GetBlob(key); if (!await blob.ExistsAsync()) { @@ -49,7 +49,7 @@ public AzureBlobStorageCache(IOptions cacheOptions /// public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata) { - BlobClient blob = this.container.GetBlobClient(this.GetBlobName(key)); + BlobClient blob = this.GetBlob(key); BlobHttpHeaders headers = new() { @@ -84,6 +84,6 @@ public static Response CreateIfNotExists( PublicAccessType accessType) => new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType); - private string GetBlobName(string key) - => this.cacheFolder + key; + private BlobClient GetBlob(string key) + => this.container.GetBlobClient(this.cacheFolder + key); } From dbcae3ada76902069826c7ec390fc6d7fa60ccc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 25 Jan 2024 00:21:04 +0100 Subject: [PATCH 6/8] Adding Azure CacheFolder tests --- ...eBlobStorageCacheCacheFolderServerTests.cs | 15 +++++++++ ...torageCacheCacheFolderTestServerFixture.cs | 33 +++++++++++++++++++ .../TestUtilities/TestConstants.cs | 1 + 3 files changed, 49 insertions(+) create mode 100644 tests/ImageSharp.Web.Tests/Processing/AzureBlobStorageCacheCacheFolderServerTests.cs create mode 100644 tests/ImageSharp.Web.Tests/TestUtilities/AzureBlobStorageCacheCacheFolderTestServerFixture.cs diff --git a/tests/ImageSharp.Web.Tests/Processing/AzureBlobStorageCacheCacheFolderServerTests.cs b/tests/ImageSharp.Web.Tests/Processing/AzureBlobStorageCacheCacheFolderServerTests.cs new file mode 100644 index 00000000..15b089cc --- /dev/null +++ b/tests/ImageSharp.Web.Tests/Processing/AzureBlobStorageCacheCacheFolderServerTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Web.Tests.TestUtilities; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Web.Tests.Processing; + +public class AzureBlobStorageCacheCacheFolderServerTests : ServerTestBase +{ + public AzureBlobStorageCacheCacheFolderServerTests(AzureBlobStorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper) + : base(fixture, outputHelper, TestConstants.AzureTestImage) + { + } +} diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/AzureBlobStorageCacheCacheFolderTestServerFixture.cs b/tests/ImageSharp.Web.Tests/TestUtilities/AzureBlobStorageCacheCacheFolderTestServerFixture.cs new file mode 100644 index 00000000..9b678c66 --- /dev/null +++ b/tests/ImageSharp.Web.Tests/TestUtilities/AzureBlobStorageCacheCacheFolderTestServerFixture.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Azure.Storage.Blobs.Models; +using Microsoft.Extensions.DependencyInjection; +using SixLabors.ImageSharp.Web.Caching.Azure; +using SixLabors.ImageSharp.Web.DependencyInjection; +using SixLabors.ImageSharp.Web.Providers.Azure; + +namespace SixLabors.ImageSharp.Web.Tests.TestUtilities; + +public class AzureBlobStorageCacheCacheFolderTestServerFixture : TestServerFixture +{ + protected override void ConfigureCustomServices(IServiceCollection services, IImageSharpBuilder builder) + => builder + .Configure(o => + o.BlobContainers.Add( + new AzureBlobContainerClientOptions + { + ConnectionString = TestConstants.AzureConnectionString, + ContainerName = TestConstants.AzureContainerName + })) + .AddProvider(AzureBlobStorageImageProviderFactory.Create) + .Configure(o => + { + o.ConnectionString = TestConstants.AzureConnectionString; + o.ContainerName = TestConstants.AzureCacheContainerName; + o.CacheFolder = TestConstants.AzureCacheFolder; + + AzureBlobStorageCache.CreateIfNotExists(o, PublicAccessType.None); + }) + .SetCache(); +} diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs index 26c64845..28572d26 100644 --- a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs +++ b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs @@ -8,6 +8,7 @@ public static class TestConstants public const string AzureConnectionString = "UseDevelopmentStorage=true"; public const string AzureContainerName = "azure"; public const string AzureCacheContainerName = "is-cache"; + public const string AzureCacheFolder = "cache/folder"; public const string AWSEndpoint = "http://localhost:4568/"; public const string AWSRegion = "eu-west-2"; public const string AWSBucketName = "aws"; From e41bc2a5b743b8b40806b26d85bcd1c38cd9f756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 25 Jan 2024 00:27:04 +0100 Subject: [PATCH 7/8] Adding AWS tests --- ...AWSS3StorageCacheCacheFolderServerTests.cs | 15 +++++++ ...torageCacheCacheFolderTestServerFixture.cs | 41 +++++++++++++++++++ .../TestUtilities/TestConstants.cs | 1 + 3 files changed, 57 insertions(+) create mode 100644 tests/ImageSharp.Web.Tests/Processing/AWSS3StorageCacheCacheFolderServerTests.cs create mode 100644 tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageCacheCacheFolderTestServerFixture.cs diff --git a/tests/ImageSharp.Web.Tests/Processing/AWSS3StorageCacheCacheFolderServerTests.cs b/tests/ImageSharp.Web.Tests/Processing/AWSS3StorageCacheCacheFolderServerTests.cs new file mode 100644 index 00000000..0123e46b --- /dev/null +++ b/tests/ImageSharp.Web.Tests/Processing/AWSS3StorageCacheCacheFolderServerTests.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Web.Tests.TestUtilities; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Web.Tests.Processing; + +public class AWSS3StorageCacheCacheFolderServerTests : ServerTestBase +{ + public AWSS3StorageCacheCacheFolderServerTests(AWSS3StorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper) + : base(fixture, outputHelper, TestConstants.AWSTestImage) + { + } +} diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageCacheCacheFolderTestServerFixture.cs b/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageCacheCacheFolderTestServerFixture.cs new file mode 100644 index 00000000..d452ef6b --- /dev/null +++ b/tests/ImageSharp.Web.Tests/TestUtilities/AWSS3StorageCacheCacheFolderTestServerFixture.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using Amazon.S3; +using Microsoft.Extensions.DependencyInjection; +using SixLabors.ImageSharp.Web.Caching.AWS; +using SixLabors.ImageSharp.Web.DependencyInjection; +using SixLabors.ImageSharp.Web.Providers.AWS; + +namespace SixLabors.ImageSharp.Web.Tests.TestUtilities; + +public class AWSS3StorageCacheCacheFolderTestServerFixture : TestServerFixture +{ + protected override void ConfigureCustomServices(IServiceCollection services, IImageSharpBuilder builder) + => builder + .Configure(o => + o.S3Buckets.Add( + new AWSS3BucketClientOptions + { + Endpoint = TestConstants.AWSEndpoint, + BucketName = TestConstants.AWSBucketName, + AccessKey = TestConstants.AWSAccessKey, + AccessSecret = TestConstants.AWSAccessSecret, + Region = TestConstants.AWSRegion, + Timeout = TestConstants.AWSTimeout, + })) + .AddProvider(AWSS3StorageImageProviderFactory.Create) + .Configure(o => + { + o.Endpoint = TestConstants.AWSEndpoint; + o.BucketName = TestConstants.AWSCacheBucketName; + o.AccessKey = TestConstants.AWSAccessKey; + o.AccessSecret = TestConstants.AWSAccessSecret; + o.Region = TestConstants.AWSRegion; + o.Timeout = TestConstants.AWSTimeout; + o.CacheFolder = TestConstants.AWSCacheFolder; + + AWSS3StorageCache.CreateIfNotExists(o, S3CannedACL.Private); + }) + .SetCache(); +} diff --git a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs index 28572d26..9d9e2554 100644 --- a/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs +++ b/tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs @@ -15,6 +15,7 @@ public static class TestConstants public const string AWSCacheBucketName = "aws-cache"; public const string AWSAccessKey = ""; public const string AWSAccessSecret = ""; + public const string AWSCacheFolder = "cache/folder"; public const string ImagePath = "SubFolder/sîxläbörs.îmägéshärp.wéb.png"; public const string PhysicalTestImage = "http://localhost/" + ImagePath; public const string AzureTestImage = "http://localhost/" + AzureContainerName + "/" + ImagePath; From ff7fdc49982e1749e4c3ff70cc0d502fd4d4c3a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Leh=C3=B3czky?= Date: Thu, 25 Jan 2024 00:30:06 +0100 Subject: [PATCH 8/8] The CacheFolder properties should actually be nullabe --- .../Caching/AWSS3StorageCacheOptions.cs | 2 +- .../Caching/AzureBlobStorageCacheOptions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs index bf25a884..f10f1a47 100644 --- a/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs +++ b/src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs @@ -17,7 +17,7 @@ public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions /// /// Gets or sets the cache folder's name that'll store cache files under the configured bucket. /// - public string CacheFolder { get; set; } = null!; + public string? CacheFolder { get; set; } /// public string? AccessKey { get; set; } diff --git a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs index 92c9c20e..9db937a9 100644 --- a/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs +++ b/src/ImageSharp.Web.Providers.Azure/Caching/AzureBlobStorageCacheOptions.cs @@ -26,5 +26,5 @@ public class AzureBlobStorageCacheOptions /// Must conform to Azure Blob Storage directory naming guidelines. /// /// - public string CacheFolder { get; set; } = null!; + public string? CacheFolder { get; set; } }