Skip to content

Commit

Permalink
Merge pull request #353 from Lombiq/issue/OCORE-136
Browse files Browse the repository at this point in the history
Cache folder configuration for Azure and AWS Image Caches (Lombiq Technologies: OCORE-136)
  • Loading branch information
JimBobSquarePants committed Mar 6, 2024
2 parents 6096704 + ff7fdc4 commit a3f306c
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 6 deletions.
14 changes: 11 additions & 3 deletions src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class AWSS3StorageCache : IImageCache
{
private readonly IAmazonS3 amazonS3Client;
private readonly string bucketName;
private readonly string cacheFolder;

/// <summary>
/// Initializes a new instance of the <see cref="AWSS3StorageCache"/> class.
Expand All @@ -28,17 +29,21 @@ public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> 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('/') + '/';
}

/// <inheritdoc/>
public async Task<IImageCacheResolver?> 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
{
Expand All @@ -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
Expand Down Expand Up @@ -118,6 +123,9 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
return null;
}

private string GetKeyWithFolder(string key)
=> this.cacheFolder + key;

/// <summary>
/// <see href="https://github.com/aspnet/AspNetIdentity/blob/b7826741279450c58b230ece98bd04b4815beabf/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs"/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions
/// <inheritdoc/>
public string BucketName { get; set; } = null!;

/// <summary>
/// Gets or sets the cache folder's name that'll store cache files under the configured bucket.
/// </summary>
public string? CacheFolder { get; set; }

/// <inheritdoc/>
public string? AccessKey { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Web.Caching.Azure;
public class AzureBlobStorageCache : IImageCache
{
private readonly BlobContainerClient container;
private readonly string cacheFolder;

/// <summary>
/// Initializes a new instance of the <see cref="AzureBlobStorageCache"/> class.
Expand All @@ -27,12 +28,15 @@ public AzureBlobStorageCache(IOptions<AzureBlobStorageCacheOptions> 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('/') + '/';
}

/// <inheritdoc/>
public async Task<IImageCacheResolver?> GetAsync(string key)
{
BlobClient blob = this.container.GetBlobClient(key);
BlobClient blob = this.GetBlob(key);

if (!await blob.ExistsAsync())
{
Expand All @@ -45,7 +49,7 @@ public AzureBlobStorageCache(IOptions<AzureBlobStorageCacheOptions> cacheOptions
/// <inheritdoc/>
public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
{
BlobClient blob = this.container.GetBlobClient(key);
BlobClient blob = this.GetBlob(key);

BlobHttpHeaders headers = new()
{
Expand Down Expand Up @@ -79,4 +83,7 @@ public static Response<BlobContainerInfo> CreateIfNotExists(
AzureBlobStorageCacheOptions options,
PublicAccessType accessType)
=> new BlobContainerClient(options.ConnectionString, options.ContainerName).CreateIfNotExists(accessType);

private BlobClient GetBlob(string key)
=> this.container.GetBlobClient(this.cacheFolder + key);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ public class AzureBlobStorageCacheOptions

/// <summary>
/// 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.
/// <see href="https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names"/>
/// </summary>
public string ContainerName { get; set; } = null!;

/// <summary>
/// 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.
/// <see href="https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#directory-names"/>
/// </summary>
public string? CacheFolder { get; set; }
}
Original file line number Diff line number Diff line change
@@ -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<AWSS3StorageCacheCacheFolderTestServerFixture>
{
public AWSS3StorageCacheCacheFolderServerTests(AWSS3StorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper)
: base(fixture, outputHelper, TestConstants.AWSTestImage)
{
}
}
Original file line number Diff line number Diff line change
@@ -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<AzureBlobStorageCacheCacheFolderTestServerFixture>
{
public AzureBlobStorageCacheCacheFolderServerTests(AzureBlobStorageCacheCacheFolderTestServerFixture fixture, ITestOutputHelper outputHelper)
: base(fixture, outputHelper, TestConstants.AzureTestImage)
{
}
}
Original file line number Diff line number Diff line change
@@ -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<AWSS3StorageImageProviderOptions>(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<AWSS3StorageCacheOptions>(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<AWSS3StorageCache>();
}
Original file line number Diff line number Diff line change
@@ -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<AzureBlobStorageImageProviderOptions>(o =>
o.BlobContainers.Add(
new AzureBlobContainerClientOptions
{
ConnectionString = TestConstants.AzureConnectionString,
ContainerName = TestConstants.AzureContainerName
}))
.AddProvider(AzureBlobStorageImageProviderFactory.Create)
.Configure<AzureBlobStorageCacheOptions>(o =>
{
o.ConnectionString = TestConstants.AzureConnectionString;
o.ContainerName = TestConstants.AzureCacheContainerName;
o.CacheFolder = TestConstants.AzureCacheFolder;
AzureBlobStorageCache.CreateIfNotExists(o, PublicAccessType.None);
})
.SetCache<AzureBlobStorageCache>();
}
2 changes: 2 additions & 0 deletions tests/ImageSharp.Web.Tests/TestUtilities/TestConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ 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";
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;
Expand Down

0 comments on commit a3f306c

Please sign in to comment.