Skip to content

Commit

Permalink
Merge pull request #195 from Avanade/feature/updateFrameworkVersion
Browse files Browse the repository at this point in the history
Feature/update framework version
  • Loading branch information
lucianareginalino authored Nov 29, 2023
2 parents bf80924 + b923e4b commit 08b06be
Show file tree
Hide file tree
Showing 14 changed files with 600 additions and 2 deletions.
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

0 comments on commit 08b06be

Please sign in to comment.