Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/update framework version #195

Merged
merged 4 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/liquid-ci-cd-adapter-dataverse.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# CI & CD workflow
name: CI/CD - Liquid.Cache component for Liquid Application Framework
name: CI/CD - Liquid.Adapter.Dataverse component for Liquid Application Framework

on:
push:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/liquid-ci-cd-adapter-storage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# CI & CD workflow
name: CI/CD - Liquid.Adapter.AzureStorage component for Liquid Application Framework

on:
push:
branches: [ main ]
paths:
- 'src/Liquid.Adapter.AzureStorage/**'

pull_request:
branches: [ main, releases/** ]
types: [opened, synchronize, reopened]
paths:
- 'src/Liquid.Adapter.AzureStorage/**'

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

jobs:
call-reusable-build-workflow:
uses: Avanade/Liquid-Application-Framework/.github/workflows/base-liquid-ci-and-cd.yml@main
with:
component_name: Liquid.Adapter.AzureStorage
secrets:
sonar_token: ${{ secrets.SONAR_TOKEN_STORAGE }}
nuget_token: ${{ secrets.PUBLISH_TO_NUGET_ORG }}
24 changes: 23 additions & 1 deletion Liquid.Adapters.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ VisualStudioVersion = 17.6.34202.202
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.Dataverse", "src\Liquid.Adapter.Dataverse\Liquid.Adapter.Dataverse.csproj", "{02191AB8-D13C-4CCC-8455-08813FB0C9C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.Adapter.Dataverse.Tests", "test\Liquid.Adapter.Dataverse.Tests\Liquid.Adapter.Dataverse.Tests.csproj", "{90D60966-6004-4705-907D-780503EBC141}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.Dataverse.Tests", "test\Liquid.Adapter.Dataverse.Tests\Liquid.Adapter.Dataverse.Tests.csproj", "{90D60966-6004-4705-907D-780503EBC141}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Dataverse", "Dataverse", "{0E973865-5B87-43F2-B513-CD1DA96A2A3A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureStorage", "AzureStorage", "{81CB75D9-FC31-4533-9A2D-C9277DD4A33E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Liquid.Adapter.AzureStorage", "src\Liquid.Adapter.AzureStorage\Liquid.Adapter.AzureStorage.csproj", "{E21AF05A-738E-4DA2-AEEE-9900D7534F7C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Liquid.Adapter.AzureStorage.Tests", "test\Liquid.Adapter.AzureStorage.Tests\Liquid.Adapter.AzureStorage.Tests.csproj", "{A2A7E164-98DF-4953-9679-B35E109E8990}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -21,10 +29,24 @@ Global
{90D60966-6004-4705-907D-780503EBC141}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90D60966-6004-4705-907D-780503EBC141}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90D60966-6004-4705-907D-780503EBC141}.Release|Any CPU.Build.0 = Release|Any CPU
{E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E21AF05A-738E-4DA2-AEEE-9900D7534F7C}.Release|Any CPU.Build.0 = Release|Any CPU
{A2A7E164-98DF-4953-9679-B35E109E8990}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2A7E164-98DF-4953-9679-B35E109E8990}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2A7E164-98DF-4953-9679-B35E109E8990}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2A7E164-98DF-4953-9679-B35E109E8990}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{02191AB8-D13C-4CCC-8455-08813FB0C9C3} = {0E973865-5B87-43F2-B513-CD1DA96A2A3A}
{90D60966-6004-4705-907D-780503EBC141} = {0E973865-5B87-43F2-B513-CD1DA96A2A3A}
{E21AF05A-738E-4DA2-AEEE-9900D7534F7C} = {81CB75D9-FC31-4533-9A2D-C9277DD4A33E}
{A2A7E164-98DF-4953-9679-B35E109E8990} = {81CB75D9-FC31-4533-9A2D-C9277DD4A33E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8105640B-39D2-4FFE-8BBD-D8E37F2A070D}
EndGlobalSection
Expand Down
56 changes: 56 additions & 0 deletions src/Liquid.Adapter.AzureStorage/BlobClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Azure.Core;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Options;

namespace Liquid.Adapter.AzureStorage
{
///<inheritdoc/>
public class BlobClientFactory : IBlobClientFactory
{
private readonly StorageSettings _options;
private IList<BlobContainerClient> _clients = new List<BlobContainerClient>();

Check warning on line 11 in src/Liquid.Adapter.AzureStorage/BlobClientFactory.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Make '_clients' 'readonly'.

Check warning on line 11 in src/Liquid.Adapter.AzureStorage/BlobClientFactory.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Make '_clients' 'readonly'.

///<inheritdoc/>
public IList<BlobContainerClient> Clients => _clients;

/// <summary>
/// Inicialize a new instance of <see cref="BlobClientFactory"/>
/// </summary>
/// <param name="options">Configurations set.</param>
/// <exception cref="ArgumentNullException"></exception>
public BlobClientFactory(IOptions<StorageSettings>? options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}

///<inheritdoc/>
public List<BlobContainerClient> SetContainerClients()
{
if(_options.Containers.Count == 0)
throw new ArgumentNullException(nameof(_options));

Check warning on line 30 in src/Liquid.Adapter.AzureStorage/BlobClientFactory.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

The parameter name '_options' is not declared in the argument list.

Check warning on line 30 in src/Liquid.Adapter.AzureStorage/BlobClientFactory.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

The parameter name '_options' is not declared in the argument list.

var clients = new List<BlobContainerClient>();

foreach(var container in _options.Containers)
{
var client = new BlobContainerClient(container.ConnectionString,container.ContainerName);

clients.Add(client);
}

return clients;
}

///<inheritdoc/>
public BlobContainerClient GetContainerClient(string containerName)
{
var client = _clients.FirstOrDefault(x => x.Name == containerName);

if (client == null) {
throw new ArgumentException(nameof(containerName));
}

return client;
}
}
}
166 changes: 166 additions & 0 deletions src/Liquid.Adapter.AzureStorage/BlobStorageAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using Azure.Storage.Sas;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace Liquid.Adapter.AzureStorage
{
///<inheritdoc/>
[ExcludeFromCodeCoverage]
public class BlobStorageAdapter : ILiquidBlobStorageAdapter
{
private readonly IBlobClientFactory _factory;

/// <summary>
/// Initialize a new instance of <see cref="BlobStorageAdapter"/>
/// </summary>
/// <param name="factory"></param>
/// <exception cref="ArgumentNullException"></exception>
public BlobStorageAdapter(IBlobClientFactory factory)
{
_factory = factory ?? throw new ArgumentNullException(nameof(factory));

_factory.SetContainerClients();
}

///<inheritdoc/>
public async Task DeleteByTags(IDictionary<string, string> tags, string containerName)
{
var client = _factory.GetContainerClient(containerName);

var stringFilter = string.Empty;

foreach (var tag in tags)
{
stringFilter += @$"""{tag.Key}"" = '{tag.Value}' AND ";

Check warning on line 36 in src/Liquid.Adapter.AzureStorage/BlobStorageAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Use a StringBuilder instead.

Check warning on line 36 in src/Liquid.Adapter.AzureStorage/BlobStorageAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Use a StringBuilder instead.
}

stringFilter = stringFilter.Substring(0, stringFilter.Length - 4);

await foreach (TaggedBlobItem blobItem in client.FindBlobsByTagsAsync(stringFilter))
{
var blockBlob = client.GetBlockBlobClient(blobItem.BlobName);

await blockBlob.DeleteAsync();
};

Check warning on line 46 in src/Liquid.Adapter.AzureStorage/BlobStorageAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Remove this empty statement.

Check warning on line 46 in src/Liquid.Adapter.AzureStorage/BlobStorageAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Remove this empty statement.
}

///<inheritdoc/>
public async Task<List<LiquidBlob>> GetAllBlobs(string containerName)
{
var client = _factory.GetContainerClient(containerName);

var results = new List<LiquidBlob>();

await foreach (var blobItem in client.GetBlobsAsync())
{
var blockBlob = client.GetBlockBlobClient(blobItem.Name);
var blob = await blockBlob.DownloadContentAsync();

var item = new LiquidBlob
{
Blob = Encoding.UTF8.GetString(blob.Value.Content.ToArray()),
Name = blobItem.Name
};
results.Add(item);
}

return results;
}

///<inheritdoc/>
public async Task Delete(string id, string containerName)
{
var client = _factory.GetContainerClient(containerName);

var blobClient = client.GetBlobClient(id);

await blobClient.DeleteAsync();
}

///<inheritdoc/>
public async Task<List<LiquidBlob>> ReadBlobsByTags(IDictionary<string, string> tags, string containerName)
{
var client = _factory.GetContainerClient(containerName);

var stringFilter = string.Empty;
foreach (var tag in tags)
{
stringFilter += @$"""{tag.Key}"" = '{tag.Value}' AND ";

Check warning on line 90 in src/Liquid.Adapter.AzureStorage/BlobStorageAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Use a StringBuilder instead.

Check warning on line 90 in src/Liquid.Adapter.AzureStorage/BlobStorageAdapter.cs

View workflow job for this annotation

GitHub Actions / call-reusable-build-workflow / build

Use a StringBuilder instead.
}
stringFilter = stringFilter.Substring(0, stringFilter.Length - 4);

var results = new List<LiquidBlob>();
await foreach (TaggedBlobItem blobItem in client.FindBlobsByTagsAsync(stringFilter))
{
var blockBlob = client.GetBlockBlobClient(blobItem.BlobName);
var blob = await blockBlob.DownloadContentAsync();
var item = new LiquidBlob
{
Blob = Encoding.UTF8.GetString(blob.Value.Content.ToArray()),
Tags = blockBlob.GetTags().Value.Tags,
Name = blobItem.BlobName
};
results.Add(item);
}
return results;
}

///<inheritdoc/>
public async Task UploadBlob(string data, string name, string containerName, IDictionary<string, string>? tags = null)
{
var client = _factory.GetContainerClient(containerName);

var blockBlob = client.GetBlockBlobClient(name);

var options = new BlobUploadOptions()
{
Tags = tags
};
await blockBlob.UploadAsync(new MemoryStream(Encoding.UTF8.GetBytes(data)), options);
}

///<inheritdoc/>
public async Task<LiquidBlob> ReadBlobsByName(string blobName, string containerName)
{
var client = _factory.GetContainerClient(containerName);

var blockBlob = client.GetBlockBlobClient(blobName);
var blob = await blockBlob.DownloadContentAsync();
var item = new LiquidBlob
{
Blob = Encoding.UTF8.GetString(blob.Value.Content.ToArray()),
Tags = blockBlob.GetTags().Value.Tags,
Name = blobName
};

return item;
}

///<inheritdoc/>
public string? GetBlobSasUri(string blobName, string containerName, DateTimeOffset expiresOn, BlobContainerSasPermissions permissions)
{
var blobClient = _factory.GetContainerClient(containerName);

if (!blobClient.CanGenerateSasUri)
{
return null;
}

var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = blobClient.Name,
BlobName = blobName,
Resource = "b"
};

sasBuilder.ExpiresOn = expiresOn;
sasBuilder.SetPermissions(permissions);

var sasURI = blobClient.GenerateSasUri(sasBuilder);

return sasURI.AbsolutePath;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;

namespace Liquid.Adapter.AzureStorage.Extensions
{
/// <summary>
/// Extension methods of <see cref="IServiceCollection"/>
/// for register Liquid Azure Storage services.
/// </summary>
[ExcludeFromCodeCoverage]
public static class IServiceCollectionExtensions
{
/// <summary>
/// Registers <see cref="BlobStorageAdapter"/> service, it's dependency
/// <see cref="BlobClientFactory"/>, and also set configuration
/// option <see cref="StorageSettings"/>.
/// </summary>
/// <param name="services">service collection instance.</param>
/// <param name="configSection">configuration section of storage settings.</param>
public static IServiceCollection AddLiquidAzureStorageAdapter(this IServiceCollection services, string configSection)
{
services.AddOptions<StorageSettings>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection(configSection).Bind(settings);
});

services.AddTransient<IBlobClientFactory, BlobClientFactory>();

services.AddSingleton<ILiquidBlobStorageAdapter, BlobStorageAdapter>();

return services;
}
}
}
30 changes: 30 additions & 0 deletions src/Liquid.Adapter.AzureStorage/IBlobClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Azure.Storage.Blobs;

namespace Liquid.Adapter.AzureStorage
{
/// <summary>
/// <see cref="BlobContainerClient"/> instances factory.
/// </summary>
public interface IBlobClientFactory
{
/// <summary>
/// List of instances of <see cref="BlobContainerClient"/>.
/// </summary>
IList<BlobContainerClient> Clients { get; }

/// <summary>
/// Initialize an instance of <see cref="BlobContainerClient"/>
/// for each container on the <see cref="StorageSettings"/> and
/// add to <see cref="Clients"/>.
/// </summary>
List<BlobContainerClient> SetContainerClients();

/// <summary>
/// Get an instance of <see cref="BlobContainerClient"/>
/// by name.
/// </summary>
/// <param name="containerName"></param>
/// <returns></returns>
BlobContainerClient GetContainerClient(string containerName);
}
}
Loading
Loading