diff --git a/src/Imageflow.Server.Storage.AzureBlob/Caching/HealthCheck.cs b/src/Imageflow.Server.Storage.AzureBlob/Caching/HealthCheck.cs new file mode 100644 index 0000000..2314133 --- /dev/null +++ b/src/Imageflow.Server.Storage.AzureBlob/Caching/HealthCheck.cs @@ -0,0 +1,42 @@ +using Azure.Storage.Blobs; +using Imazen.Abstractions.BlobCache; +using Microsoft.Extensions.Logging; + +namespace Imageflow.Server.Storage.AzureBlob.Caching; + +internal record HealthCheck(IBlobCache Cache, NamedCacheConfiguration Config, ILogger Logger, Dictionary ServiceClients, + ContainerExistenceCache ContainerExists) +{ + + // this.InitialCacheCapabilities = new BlobCacheCapabilities + // { + // CanFetchMetadata = true, + // CanFetchData = true, + // CanConditionalFetch = false, + // CanPut = true, + // CanConditionalPut = false, + // CanDelete = false, + // CanSearchByTag = false, + // CanPurgeByTag = false, + // CanReceiveEvents = false, + // SupportsHealthCheck = false, + // SubscribesToRecentRequest = false, + // SubscribesToExternalHits = true, + // SubscribesToFreshResults = true, + // RequiresInlineExecution = false, + // FixedSize = false + // }; + // + + public ValueTask CacheHealthCheck(CancellationToken cancellationToken = default) + { + + } + + internal record BasicHealthDetails(bool CanFetchData, bool CanFetchMetadata, bool CanConditionalFetch, bool CanPut, bool CanConditionalPut, bool CanSearchByTag, bool CanPurgeByTag); + + internal ValueTask CheckGroup(BlobGroup group, CancellationToken cancellationToken = default) + { + + } +} \ No newline at end of file diff --git a/src/Imageflow.Server.Storage.AzureBlob/Caching/LifecycleRuleUpdater.cs b/src/Imageflow.Server.Storage.AzureBlob/Caching/LifecycleRuleUpdater.cs new file mode 100644 index 0000000..3b55f4c --- /dev/null +++ b/src/Imageflow.Server.Storage.AzureBlob/Caching/LifecycleRuleUpdater.cs @@ -0,0 +1,128 @@ +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using Imazen.Common.Concurrency; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Imazen.Abstractions.Logging; +using Imazen.Abstractions.Resulting; + +namespace Imageflow.Server.Storage.AzureBlob.Caching +{ + internal class LifecycleRuleUpdater + { + private readonly NamedCacheConfiguration config; + private readonly IReLogger logger; + private readonly BlobServiceClient defaultClient; + private readonly BasicAsyncLock updateLock = new BasicAsyncLock(); + private bool updateComplete = false; + + public LifecycleRuleUpdater(NamedCacheConfiguration config, BlobServiceClient defaultClient, IReLogger logger) + { + this.config = config; + this.logger = logger.WithSubcategory(nameof(LifecycleRuleUpdater)); + this.defaultClient = defaultClient; + } + + internal async Task UpdateIfIncompleteAsync() + { + if (updateComplete) return; + + using (await updateLock.LockAsync()) + { + if (updateComplete) return; + await CreateContainersAsync(); + await UpdateLifecycleRulesAsync(); + await CreateAndReadTestFilesAsync(false); + updateComplete = true; + } + } + + internal async Task CreateContainersAsync() + { + // Implementation similar to S3LifecycleUpdater's CreateBucketsAsync + // Use BlobServiceClient to create containers if they don't exist + } + + internal async Task UpdateLifecycleRulesAsync() +{ + var client = defaultClient; + var lifecycleManagementPolicy = new BlobLifecycleManagementPolicy(); + var rules = new List(); + + foreach (var groupConfig in config.BlobGroupConfigurations.Values) + { + if (groupConfig.UpdateLifecycleRules == false) continue; + + var containerName = groupConfig.Location.ContainerName; + var prefix = groupConfig.Location.BlobPrefix; + + if (groupConfig.Lifecycle.DaysBeforeExpiry.HasValue) + { + var rule = new BlobLifecycleRule + { + Name = $"Rule-{containerName}-{prefix}", + Enabled = true, + Definition = new BlobLifecycleRuleDefinition + { + Filters = new BlobLifecycleRuleFilter + { + PrefixMatch = new List { $"{containerName}/{prefix}" } + }, + Actions = new BlobLifecycleRuleActions + { + BaseBlob = new BlobLifecycleRuleActionBase + { + Delete = new BlobLifecycleRuleActionDelete + { + DaysAfterModificationGreaterThan = groupConfig.Lifecycle.DaysBeforeExpiry.Value + } + } + } + } + }; + + rules.Add(rule); + } + } + + lifecycleManagementPolicy.Rules = rules; + + try + { + await client.SetBlobLifecyclePolicyAsync(lifecycleManagementPolicy); + logger.LogInformation("Updated lifecycle rules for storage account"); + } + catch (Exception e) + { + logger.LogError(e, $"Error updating lifecycle rules for storage account: {e.Message}"); + } +} + + internal async Task CreateAndReadTestFilesAsync(bool forceAll) + { + // Implementation similar to S3LifecycleUpdater's CreateAndReadTestFilesAsync + // Use BlobContainerClient to perform operations on blobs + } + + private async Task TryAzureOperationAsync(string containerName, string blobName, string operationName, + Func operation) + { + try + { + await operation(); + return CodeResult.Ok(); + } + catch (Azure.RequestFailedException e) + { + var err = CodeResult.FromException(e, $"Azure {operationName} {containerName} {blobName}"); + logger.LogAsError(err); + return err; + } + } + + internal record class TestFilesResult(List Results, bool ReadsFailed, bool WritesFailed, bool ListFailed, bool DeleteFailed); + } +} \ No newline at end of file