From 7bb87b63a7d30039faa014a9a099550face1fd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20G=C3=B3mez?= Date: Sun, 8 Dec 2024 21:31:52 -0500 Subject: [PATCH] Fixes issue with non-prod like Azurite urls Fixes #192 --- .gitignore | 3 +- .vscode/launch.json | 23 ++++++++++- .vscode/tasks.json | 9 +++++ azurite.sh | 18 +++++++++ build.sh | 3 +- docker-build.sh | 3 +- docker-compose/azurestorageexplorer.yaml | 3 +- docker-run.sh | 2 +- justfile | 40 ++++++++++++++++++++ publish.sh | 2 +- src/Dockerfile | 2 +- src/StorageLibrary/AzureContainer.cs | 8 ++-- src/StorageLibrary/AzureFile.cs | 4 +- src/StorageLibrary/AzureQueue.cs | 5 ++- src/StorageLibrary/AzureTable.cs | 4 +- src/StorageLibrary/Common/BlobItemWrapper.cs | 30 +++++++-------- src/StorageLibrary/Mocks/MockContainer.cs | 2 +- src/StorageLibrary/StorageFactory.cs | 35 ++++++++++++++--- src/StorageLibrary/StorageObject.cs | 9 ++++- src/web/Directory.Build.props | 2 +- src/web/Pages/Blobs.razor.cs | 7 ++-- src/web/Utils/Util.cs | 12 ++++-- test.sh | 5 ++- version.sh | 2 + 24 files changed, 178 insertions(+), 55 deletions(-) create mode 100755 azurite.sh create mode 100644 justfile diff --git a/.gitignore b/.gitignore index 5db5058..b69f001 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ bin/ obj/ .vs/ - +.DS_Store **/wwwroot/dist/ !**/**/wwwroot/dist/azurewebexplorer.css **/Properties/launchSettings.json @@ -12,5 +12,4 @@ obj/ __azurite*.json private*.data -.DS_Store azurestorageexplorer.sln diff --git a/.vscode/launch.json b/.vscode/launch.json index 28816c6..e0e3d00 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,7 +19,28 @@ }, "env": { "ASPNETCORE_ENVIRONMENT": "Development" - //"AZURE_STORAGE_CONNECTIONSTRING": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;QueueEndpoint=http://localhost:10001/devstoreaccount1;TableEndpoint=http://localhost:10002/devstoreaccount1;" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": "Blazor WEB (Azurite)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-and-azurite", + "program": "${workspaceFolder}/src/web/bin/Debug/net8.0/web.dll", + "args": [], + "cwd": "${workspaceFolder}/src/web", + "stopAtEntry": false, + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development", + "AZURE_STORAGE_CONNECTIONSTRING": "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://localhost:10000/devstoreaccount1;QueueEndpoint=http://localhost:10001/devstoreaccount1;TableEndpoint=http://localhost:10002/devstoreaccount1;", + "AZURITE": "true" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e3df063..e9185db 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,6 +13,15 @@ ], "problemMatcher": "$msCompile" }, + { + "label": "azurite", + "command": "./azurite.sh" + }, + { + "label": "build-and-azurite", + "dependsOrder": "sequence", + "dependsOn": ["build", "azurite"] + }, { "label": "publish", "command": "dotnet", diff --git a/azurite.sh b/azurite.sh new file mode 100755 index 0000000..13c81d6 --- /dev/null +++ b/azurite.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +CONTAINER_NAME="azurite" +IMAGE_NAME="mcr.microsoft.com/azure-storage/azurite" + +Check if the container is already running +if [ "$(docker ps -q -f name=${CONTAINER_NAME})" ]; then + echo "Container ${CONTAINER_NAME} is already running." +else + # Check if the container exists but is stopped + if [ "$(docker ps -aq -f status=exited -f name=${CONTAINER_NAME})" ]; then + echo "Starting existing container ${CONTAINER_NAME}." + docker start ${CONTAINER_NAME} + else + echo "Running a new container named ${CONTAINER_NAME}." + docker run -d -p 10000:10000 -p 10001:10001 -p 10002:10002 --name ${CONTAINER_NAME} ${IMAGE_NAME} + fi +fi diff --git a/build.sh b/build.sh index 07e40ea..3aab930 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,3 @@ -cd "${0%/*}" || exit 1 +#!/bin/bash + dotnet build ./src/web/web.csproj \ No newline at end of file diff --git a/docker-build.sh b/docker-build.sh index f825e51..aa7f74e 100755 --- a/docker-build.sh +++ b/docker-build.sh @@ -1,2 +1,3 @@ -cd "${0%/*}" || exit 1 +#!/bin/bash + docker build --tag azurestorageexplorer:local ./src \ No newline at end of file diff --git a/docker-compose/azurestorageexplorer.yaml b/docker-compose/azurestorageexplorer.yaml index 6317e77..d050205 100644 --- a/docker-compose/azurestorageexplorer.yaml +++ b/docker-compose/azurestorageexplorer.yaml @@ -1,5 +1,3 @@ -version: '3.8' - services: azurite: image: mcr.microsoft.com/azure-storage/azurite @@ -14,5 +12,6 @@ services: - "8080:8080" environment: - AZURE_STORAGE_CONNECTIONSTRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;QueueEndpoint=http://azurite:10001/devstoreaccount1;TableEndpoint=http://azurite:10002/devstoreaccount1; + - AZURITE=true depends_on: - azurite diff --git a/docker-run.sh b/docker-run.sh index 6e5f1a2..98dbf03 100755 --- a/docker-run.sh +++ b/docker-run.sh @@ -1,4 +1,4 @@ -cd "${0%/*}" || exit 1 +#!/bin/bash echo App will run on http://localhost:8080 diff --git a/justfile b/justfile new file mode 100644 index 0000000..45ed24e --- /dev/null +++ b/justfile @@ -0,0 +1,40 @@ +default: + @just --list + +# Build the solution +build: + dotnet build ./src/web/web.csproj + +# Publish and launches in localhost:5000 +publish: + #!/bin/bash + dotnet publish --configuration Release -o ./bin ./src/web/web.csproj + + OK=$? + if [ $OK -eq 0 ]; then + echo Azure Storage Explorer will be running in http://localhost:5000/ + cd bin + dotnet web.dll + cd .. + fi + +# Run unit tests +test: + dotnet test ./tests/StorageLibTests/StorageLibTests.csproj + +# Build Docker image as azurestorageexplorer:local +db: + docker build --tag azurestorageexplorer:local ./src + +# Launches the local docker image at http://localhost:8080 +dr: + echo App will run on http://localhost:8080 + docker run --rm -p 8080:8080 --name azurestorageexplorer azurestorageexplorer:local + +compose: + docker-compose -f ./docker-compose/azurestorageexplorer.yaml up + +uncompose: + docker-compose -f ./docker-compose/azurestorageexplorer.yaml down + + \ No newline at end of file diff --git a/publish.sh b/publish.sh index 07e11bc..45fde8d 100755 --- a/publish.sh +++ b/publish.sh @@ -1,4 +1,4 @@ -cd "${0%/*}" || exit 1 +#!/bin/bash dotnet publish --configuration Release -o ./bin ./src/web/web.csproj diff --git a/src/Dockerfile b/src/Dockerfile index 011477a..72214ef 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 as builder +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder WORKDIR /src COPY ./ /src diff --git a/src/StorageLibrary/AzureContainer.cs b/src/StorageLibrary/AzureContainer.cs index 11af063..54db206 100644 --- a/src/StorageLibrary/AzureContainer.cs +++ b/src/StorageLibrary/AzureContainer.cs @@ -13,8 +13,8 @@ namespace StorageLibrary { internal class AzureContainer : StorageObject, IContainer { - public AzureContainer(string account, string key, string endpoint, string connectionString) - : base(account, key, endpoint, connectionString) { } + public AzureContainer(StorageFactoryConfig config) + : base(config) { } public async Task> ListContainersAsync() { @@ -45,11 +45,11 @@ public async Task> ListBlobsAsync(string containerName, st { BlobClient blobClient = container.GetBlobClient(blobItem.Blob.Name); - wrapper = new BlobItemWrapper(blobClient.Uri.AbsoluteUri, blobItem.Blob.Properties.ContentLength); + wrapper = new BlobItemWrapper(blobClient.Uri.AbsoluteUri, blobItem.Blob.Properties.ContentLength.HasValue ? blobItem.Blob.Properties.ContentLength.Value : 0, IsAzurite); } else if (blobItem.IsPrefix) { - wrapper = new BlobItemWrapper($"{container.Uri}/{blobItem.Prefix}", 0); + wrapper = new BlobItemWrapper($"{container.Uri}/{blobItem.Prefix}", 0, IsAzurite); } if (wrapper != null && !results.Contains(wrapper)) diff --git a/src/StorageLibrary/AzureFile.cs b/src/StorageLibrary/AzureFile.cs index 996b0af..b2bd224 100644 --- a/src/StorageLibrary/AzureFile.cs +++ b/src/StorageLibrary/AzureFile.cs @@ -14,8 +14,8 @@ namespace StorageLibrary { internal class AzureFile : StorageObject, IFile { - public AzureFile(string account, string key, string endpoint, string connectionString) - : base(account, key, endpoint, connectionString) { } + public AzureFile(StorageFactoryConfig config) + : base(config) { } public async Task> ListFileSharesAsync() { diff --git a/src/StorageLibrary/AzureQueue.cs b/src/StorageLibrary/AzureQueue.cs index bcdebe4..f243baa 100644 --- a/src/StorageLibrary/AzureQueue.cs +++ b/src/StorageLibrary/AzureQueue.cs @@ -11,8 +11,9 @@ namespace StorageLibrary { internal class AzureQueue : StorageObject, IQueue { - public AzureQueue(string account, string key, string endpoint, string connectionString) - : base(account, key, endpoint, connectionString) { } + public AzureQueue(StorageFactoryConfig config) + : base(config) { } + public async Task> ListQueuesAsync() { QueueServiceClient client = new QueueServiceClient(ConnectionString); diff --git a/src/StorageLibrary/AzureTable.cs b/src/StorageLibrary/AzureTable.cs index 6698456..78eeda6 100644 --- a/src/StorageLibrary/AzureTable.cs +++ b/src/StorageLibrary/AzureTable.cs @@ -10,8 +10,8 @@ namespace StorageLibrary { internal class AzureTable : StorageObject, ITable { - public AzureTable(string account, string key, string endpoint, string connectionString) - : base(account, key, endpoint, connectionString) { } + public AzureTable(StorageFactoryConfig config) + : base(config) { } public async Task> ListTablesAsync() { diff --git a/src/StorageLibrary/Common/BlobItemWrapper.cs b/src/StorageLibrary/Common/BlobItemWrapper.cs index 78b2ae3..b7c972c 100644 --- a/src/StorageLibrary/Common/BlobItemWrapper.cs +++ b/src/StorageLibrary/Common/BlobItemWrapper.cs @@ -6,28 +6,18 @@ namespace StorageLibrary.Common public class BlobItemWrapper : IEquatable, IComparable { Uri m_internalUri; - public string Name { get => HttpUtility.UrlDecode(m_internalUri.Segments[m_internalUri.Segments.Length - 1]); } - public string Path - { - get - { - int containerPos = m_internalUri.LocalPath.IndexOf(Container) + Container.Length; - - return m_internalUri.LocalPath.Substring(containerPos, (m_internalUri.LocalPath.Length) - (containerPos) - Name.Length); - } - } - - public string Container { get => IsAzurite ? m_internalUri.Segments[2] : m_internalUri.Segments[1]; } + bool m_isAzurite = false; + public string Name { get; private set; } + public string Path { get; private set; } + public string Container { get; private set; } public string FullName { get => $"{Path}{Name}"; } - public bool IsFile { get => !m_internalUri.Segments[m_internalUri.Segments.Length - 1].EndsWith("/"); } + public bool IsFile { get; private set; } public string Url { get { return m_internalUri.OriginalString; } private set { m_internalUri = new Uri(value); } } - public bool IsAzurite { get => m_internalUri.IsLoopback; } - public long Size { get; private set; } public decimal SizeInKBs { get => (decimal)Size / 1024; } @@ -36,10 +26,16 @@ public string Url public BlobItemWrapper(string url) : this(url, 0) { } - public BlobItemWrapper(string url, long? size) + public BlobItemWrapper(string url, long size, bool fromAzurite = false) { Url = url; - Size = size.HasValue ? size.Value : 0; + Size = size; + m_isAzurite = fromAzurite; + IsFile = !m_internalUri.Segments[m_internalUri.Segments.Length - 1].EndsWith(System.IO.Path.AltDirectorySeparatorChar); + Container = m_isAzurite ? m_internalUri.Segments[2] : m_internalUri.Segments[1]; + Name = HttpUtility.UrlDecode(m_internalUri.Segments[m_internalUri.Segments.Length - 1]); + int containerPos = m_internalUri.LocalPath.IndexOf(Container) + Container.Length; + Path = m_internalUri.LocalPath.Substring(containerPos, (m_internalUri.LocalPath.Length) - (containerPos) - Name.Length); } public int CompareTo(BlobItemWrapper other) diff --git a/src/StorageLibrary/Mocks/MockContainer.cs b/src/StorageLibrary/Mocks/MockContainer.cs index 0948385..7605429 100644 --- a/src/StorageLibrary/Mocks/MockContainer.cs +++ b/src/StorageLibrary/Mocks/MockContainer.cs @@ -72,7 +72,7 @@ await Task.Run(() => if (MockUtils.FolderStructure[containerName].Contains(blobName)) throw new InvalidOperationException($"Blob '{blobName}' already exists in Container '{containerName}'"); - BlobItemWrapper blob = new BlobItemWrapper($"{MockUtils.FAKE_URL}/{containerName}/{blobName}"); + BlobItemWrapper blob = StorageFactory.GetBlobItemWrapper($"{MockUtils.FAKE_URL}/{containerName}/{blobName}"); if (!MockUtils.FolderStructure[containerName].Contains(blob.Path)) MockUtils.FolderStructure[containerName].Add(blob.Path); diff --git a/src/StorageLibrary/StorageFactory.cs b/src/StorageLibrary/StorageFactory.cs index daefab1..385b8e4 100644 --- a/src/StorageLibrary/StorageFactory.cs +++ b/src/StorageLibrary/StorageFactory.cs @@ -1,4 +1,5 @@ +using StorageLibrary.Common; using StorageLibrary.Interfaces; using StorageLibrary.Mocks; @@ -6,21 +7,43 @@ namespace StorageLibrary { public class StorageFactory { + static StorageFactory Instance; + public IQueue Queues { get; private set; } public IContainer Containers { get; set; } public ITable Tables { get; set; } public IFile Files { get; set; } + StorageFactoryConfig m_currentConfig; + public StorageFactory() - : this(string.Empty, string.Empty, string.Empty, string.Empty, true) + : this(new StorageFactoryConfig { Mock = true }) { } - public StorageFactory(string account, string key, string endpoint, string connectionString, bool mock = false) + public StorageFactory(StorageFactoryConfig config) { - Queues = mock ? new MockQueue() : new AzureQueue(account, key, endpoint, connectionString); - Containers = mock ? new MockContainer() : new AzureContainer(account, key, endpoint, connectionString); - Tables = mock ? new MockTable() : new AzureTable(account, key, endpoint, connectionString); - Files = mock ? new MockFile() : new AzureFile(account, key, endpoint, connectionString); + m_currentConfig = config; + Queues = config.Mock ? new MockQueue() : new AzureQueue(config); + Containers = config.Mock ? new MockContainer() : new AzureContainer(config); + Tables = config.Mock ? new MockTable() : new AzureTable(config); + Files = config.Mock ? new MockFile() : new AzureFile(config); + + Instance = this; } + + public static BlobItemWrapper GetBlobItemWrapper(string url, long size = 0) + { + return new BlobItemWrapper(url, size, Instance.m_currentConfig.IsAzurite); + } + } + + public class StorageFactoryConfig + { + public string Account { get; set; } + public string Key { get; set; } + public string Endpoint { get; set; } = "core.windows.net"; + public string ConnectionString { get; set; } + public bool IsAzurite { get; set; } + public bool Mock { get; set; } } } \ No newline at end of file diff --git a/src/StorageLibrary/StorageObject.cs b/src/StorageLibrary/StorageObject.cs index 61f6a5f..a4ab245 100644 --- a/src/StorageLibrary/StorageObject.cs +++ b/src/StorageLibrary/StorageObject.cs @@ -10,9 +10,16 @@ internal class StorageObject public string Endpoint { get; private set; } = "core.windows.net"; public string ConnectionString { get; private set; } + public bool IsAzurite { get; private set; } + const string CONNSTRING_TEMPLATE = "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}"; - public StorageObject(string account, string key, string endpoint, string connectionString) + + public StorageObject(StorageFactoryConfig config) + : this(config.Account, config.Key, config.Endpoint, config.ConnectionString, config.IsAzurite){ } + + public StorageObject(string account, string key, string endpoint, string connectionString, bool azurite) { + IsAzurite = azurite; if (!string.IsNullOrEmpty(connectionString)) { ConnectionString = connectionString; diff --git a/src/web/Directory.Build.props b/src/web/Directory.Build.props index b63e799..1ba2be4 100644 --- a/src/web/Directory.Build.props +++ b/src/web/Directory.Build.props @@ -1,5 +1,5 @@ - 2.17.0 + 2.17.1 \ No newline at end of file diff --git a/src/web/Pages/Blobs.razor.cs b/src/web/Pages/Blobs.razor.cs index fe94214..a095a0c 100644 --- a/src/web/Pages/Blobs.razor.cs +++ b/src/web/Pages/Blobs.razor.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; +using StorageLibrary; using StorageLibrary.Common; using web.Utils; @@ -101,7 +102,7 @@ public async Task MoveUp() public async Task EnterFolder(EventArgs args, string blobUrl) { - BlobItemWrapper blob = new BlobItemWrapper(blobUrl, 0); + BlobItemWrapper blob = StorageFactory.GetBlobItemWrapper(blobUrl); if (blob.IsFile) return; @@ -117,7 +118,7 @@ public async Task DownloadBlob(EventArgs args, string blobUrl) string path = ""; try { - BlobItemWrapper blob = new BlobItemWrapper(blobUrl, 0); + BlobItemWrapper blob = StorageFactory.GetBlobItemWrapper(blobUrl); path = await AzureStorage!.Containers.GetBlobAsync(CurrentContainer, blob.FullName); FileStream fileStream = File.OpenRead(path); @@ -143,7 +144,7 @@ public async Task DeleteBlob(EventArgs args, string blobUrl) { try { - BlobItemWrapper blob = new BlobItemWrapper(blobUrl, 0); + BlobItemWrapper blob = StorageFactory.GetBlobItemWrapper(blobUrl); await AzureStorage!.Containers.DeleteBlobAsync(CurrentContainer, blob.FullName); await LoadBlobs(); } diff --git a/src/web/Utils/Util.cs b/src/web/Utils/Util.cs index c5524f6..99cf108 100644 --- a/src/web/Utils/Util.cs +++ b/src/web/Utils/Util.cs @@ -10,14 +10,18 @@ public class Util public const string AZURE_STORAGE_ACCOUNT = "AZURE_STORAGE_ACCOUNT"; public const string AZURE_STORAGE_KEY = "AZURE_STORAGE_KEY"; public const string AZURE_STORAGE_ENDPOINT = "AZURE_STORAGE_ENDPOINT"; + public const string MOCK = "MOCK"; + public const string AZURITE = "AZURITE"; public static StorageFactory GetStorageFactory(Credentials cred) { - string? mock = Environment.GetEnvironmentVariable("MOCK"); - if (mock is not null && mock.ToLower() == bool.TrueString.ToLower()) - return new StorageFactory(); + string? mock = Environment.GetEnvironmentVariable(MOCK); + bool mockEnabled = mock is not null && mock.ToLower() == bool.TrueString.ToLower(); - return new StorageFactory(cred.Account, cred.Key, cred.Endpoint, cred.ConnectionString); + string? azurite = Environment.GetEnvironmentVariable(AZURITE); + bool azuriteEnabled = azurite is not null && azurite.ToLower() == bool.TrueString.ToLower(); + + return new StorageFactory(new StorageFactoryConfig { Account = cred.Account, Key = cred.Key, Endpoint = cred.Endpoint, ConnectionString = cred.ConnectionString, IsAzurite = azuriteEnabled, Mock = mockEnabled }); } } } diff --git a/test.sh b/test.sh index 6981c66..84978b1 100755 --- a/test.sh +++ b/test.sh @@ -1,2 +1,3 @@ -cd "${0%/*}" || exit 1 -dotnet test ./tests/StorageLibTests/StorageLibTests.csproj +#!/bin/bash + +dotnet test ./tests/StorageLibTests/StorageLibTests.csproj \ No newline at end of file diff --git a/version.sh b/version.sh index fc4d6f7..775bc73 100755 --- a/version.sh +++ b/version.sh @@ -1 +1,3 @@ +#!/bin/bash + xmllint --xpath 'string(//AzureStorageWebExplorerVersion)' ./src/web/Directory.Build.props \ No newline at end of file