Skip to content

Commit

Permalink
Merge pull request #145 from sebagomez/feat/azurite-support
Browse files Browse the repository at this point in the history
Azurite support
  • Loading branch information
sebagomez authored Apr 2, 2024
2 parents c23b5ab + 4a2840d commit 197ee9d
Show file tree
Hide file tree
Showing 21 changed files with 272 additions and 89 deletions.
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,28 @@ Or deploy it wherever you want thanks to [docker images](https://hub.docker.com/

# Azure Storage Explorer

Azure Storage Web Explorer makes it easier for developers to browse and manage Blobs, Queues and Tables from Azure Storage. You'll no longer have to install a local client to do that. It was originally developed in C# with asp.net and WebForms 2.0, but now it has been migrated to .NET ~~Core 2.1, 2.2, 3.1, 5.0, 6, 7~~ 8 and ~~Angular~~. *Edit:* Sick and tired of all del npm module and dependency hell I moved this project to a Blazor Server app.
Original blog post from 2009! https://sgomez.blogspot.com/2009/11/mi-first-useful-azure-application.html

Azure Storage Web Explorer makes it easier for developers to browse and manage Blobs, Queues and Tables from Azure Storage. You'll no longer have to install a local client to do that. It was originally developed in C# with asp.net and WebForms 2.0, but now it has been migrated to .NET ~~Core 2.1, 2.2, 3.1, 5.0, 6, 7~~ 8 and ~~Angular~~.

To login just enter your account name and key or SAS ([Shared Access Signature](https://docs.microsoft.com/en-us/azure/storage/storage-create-storage-account#manage-your-storage-account))
*Edit:* Sick and tired of all del npm module and dependency hell I moved this project to a Blazor Server app.

![Blobs](res/containers.png)

## Login

To login just enter your account name and key or [Shared Access Signature](https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview), or, a full [Connection String](https://learn.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string).

The Connection String can also allow you to connect to a local Azurite or potentionally (I have not been able to test it) to Azure Government.

![Login](res/ASE_Login.png)

### Environment Varibales

You can also set up these fields in environment variables and Azure Storage Explorer will go straight to the home page if it could successfully authenticate.

These variables are `AZURE_STORAGE_CONNECTIONSTRING`, `AZURE_STORAGE_ACCOUNT`, `AZURE_STORAGE_KEY`, and `AZURE_STORAGE_ENDPOINT`. The connection string takes precedence over the others, meaning if you set it, no more variables will be read. On the other hand, if the connection string variable is not set, all the rest variables will be read and they all have to be present.

## Exploring

**Blobs**: Create public or private Containers and Blobs (only BlockBlobs for now). Download or delete your blobs.

Expand Down Expand Up @@ -76,7 +91,7 @@ At the root of the project just execute the ./build.sh script

Just execute the [./publish.sh](./publish.sh) script on the root folder on the repo. Kestrell will kick in and you'll see in the terminal what port number was asigned, navigate to that port, in my case http://localhost:5000 and that's it!

![CMD](https://github.com/sebagomez/azurestorageexplorer/blob/master/res/local_run.png?raw=true)
![CMD](res/local_run.png)


## Docker
Expand All @@ -91,8 +106,31 @@ docker run --rm -it -p 8000:8080 sebagomez/azurestorageexplorer

Then open your browser and navigate to http://localhost:8000, and voilá!

## Docker Compose

There's now a Docker Compose manifest in this repo that allows you to spin [Azurite](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage) and Azure Storage web Explorer. In the manifest you can see that the `AZURE_STORAGE_CONNECTIONSTRING` environment variable is already set up to connect to Azurite; so fter spinning up the containers you can navigate to http://localhost:8080 and you should be already logged in to Azurite.


```sh
docker-compose -f ./docker-compose/azurestorageexplorer.yaml up
```

## Kubernetes

A [deployment]() and a [service]() are available in the [k8s](./k8s/) folder. If you have `kubectl` locally configured with a cluster just apply them and you'll have an instance of Azure Storage Explorer running in your cluster.

```sh
kubectl apply -f ./k8s
```
Port-forward to a local host to easyly test your set up

```sh
kubectl port-forward svc/azurestorageexplorer 8080:8080
```
and access http://localhost:8080

### Helm

As of version 2.7.1 there's a new Helm chart with this project ready to be deployed in your favorite K8s cluster.
If you want this app to run in your cluster, make sure you have [helm](https://helm.sh/docs/intro/install/) installed on your system.

Expand All @@ -110,7 +148,7 @@ helm install azurestorageexplorer sebagomez/azurestorageexplorer

The helm chart provides a deployment and a service, you can enable port-forwarding to that service with the following command:
```sh
kubectl port-forward service/azurestorageexplorer 8080:80
kubectl port-forward service/azurestorageexplorer 8080:8080
```

or, you can follow helm instructions the get the application URL:
Expand Down
18 changes: 18 additions & 0 deletions docker-compose/azurestorageexplorer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: '3.8'

services:
azurite:
image: mcr.microsoft.com/azure-storage/azurite
ports:
- "10000:10000"
- "10001:10001"
- "10002:10002"

azurestorageexplorer:
image: azurestorageexplorer:local
ports:
- "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;
depends_on:
- azurite
2 changes: 1 addition & 1 deletion docker-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ cd "${0%/*}" || exit 1

echo App will run on http://localhost:8080

docker run --rm -p 8080:80 --name azurestorageexplorer azurestorageexplorer:local
docker run --rm -p 8080:8080 --name azurestorageexplorer azurestorageexplorer:local
28 changes: 28 additions & 0 deletions k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: azurestorageexplorer
name: azurestorageexplorer
spec:
replicas: 1
selector:
matchLabels:
app: azurestorageexplorer
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
app: azurestorageexplorer
spec:
containers:
- image: sebagomez/azurestorageexplorer
imagePullPolicy: Always
name: azurestorageexplorer
ports:
- containerPort: 8080
protocol: TCP
15 changes: 15 additions & 0 deletions k8s/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: azurestorageexplorer
name: azurestorageexplorer
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app: azurestorageexplorer
sessionAffinity: None
type: ClusterIP
Binary file added res/ASE_Login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions src/StorageLibrary/AzureContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ namespace StorageLibrary
{
internal class AzureContainer : StorageObject, IContainer
{
public AzureContainer(string account, string key, string endpoint)
: base(account, key, endpoint) { }
public AzureContainer(string account, string key, string endpoint, string connectionString)
: base(account, key, endpoint, connectionString) { }

public async Task<List<CloudBlobContainerWrapper>> ListContainersAsync()
{
Expand Down Expand Up @@ -54,7 +54,7 @@ public async Task<List<BlobItemWrapper>> ListBlobsAsync(string containerName, st
}
else if (blobItem.IsPrefix)
{
wrapper = new BlobItemWrapper($"{container.Uri}{localPath}{blobItem.Prefix}",0);
wrapper = new BlobItemWrapper($"{container.Uri}{localPath}{blobItem.Prefix}", 0);
}

if (wrapper != null && !results.Contains(wrapper))
Expand Down
6 changes: 3 additions & 3 deletions src/StorageLibrary/AzureFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ namespace StorageLibrary
{
internal class AzureFile : StorageObject, IFile
{
public AzureFile(string account, string key, string endpoint)
: base(account, key, endpoint) { }
public AzureFile(string account, string key, string endpoint, string connectionString)
: base(account, key, endpoint, connectionString) { }

public async Task<List<FileShareWrapper>> ListFileSharesAsync()
{
Expand All @@ -40,7 +40,7 @@ public async Task<List<FileShareItemWrapper>> ListFilesAndDirsAsync(string share
List<FileShareItemWrapper> items = new List<FileShareItemWrapper>();

var files = dir.GetFilesAndDirectoriesAsync();
await foreach(var file in files)
await foreach (var file in files)
{
var uriBuilder = new UriBuilder(dir.Uri);
uriBuilder.Path += $"/{file.Name}";
Expand Down
6 changes: 3 additions & 3 deletions src/StorageLibrary/AzureQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ namespace StorageLibrary
{
internal class AzureQueue : StorageObject, IQueue
{
public AzureQueue(string account, string key, string endpoint)
: base(account, key, endpoint) { }
public AzureQueue(string account, string key, string endpoint, string connectionString)
: base(account, key, endpoint, connectionString) { }
public async Task<List<QueueWrapper>> ListQueuesAsync()
{
QueueServiceClient client = new QueueServiceClient(ConnectionString);
List<QueueWrapper> results = new List<QueueWrapper>();
await foreach (var q in client.GetQueuesAsync())
results.Add(new QueueWrapper { Name = q.Name } );
results.Add(new QueueWrapper { Name = q.Name });

return results;
}
Expand Down
6 changes: 3 additions & 3 deletions src/StorageLibrary/AzureTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ namespace StorageLibrary
{
internal class AzureTable : StorageObject, ITable
{
public AzureTable(string account, string key, string endpoint)
: base(account, key, endpoint) { }
public AzureTable(string account, string key, string endpoint, string connectionString)
: base(account, key, endpoint, connectionString) { }

public async Task<List<TableWrapper>> ListTablesAsync()
{
TableServiceClient client = new TableServiceClient(ConnectionString);
List<TableWrapper> results = new List<TableWrapper>();
await foreach (var table in client.QueryAsync())
results.Add(new TableWrapper { Name = table.Name});
results.Add(new TableWrapper { Name = table.Name });

return results;
}
Expand Down
13 changes: 6 additions & 7 deletions src/StorageLibrary/StorageFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@ public class StorageFactory
{
public IQueue Queues { get; private set; }
public IContainer Containers { get; set; }

public ITable Tables { get; set; }
public IFile Files { get; set; }

public StorageFactory()
: this(string.Empty, string.Empty, string.Empty, true)
: this(string.Empty, string.Empty, string.Empty, string.Empty, true)
{ }

public StorageFactory(string account, string key, string endpoint = "core.windows.net", bool mock = false)
public StorageFactory(string account, string key, string endpoint, string connectionString, bool mock = false)
{
Queues = mock ? new MockQueue():new AzureQueue(account, key, endpoint);
Containers = mock ? new MockContainer():new AzureContainer(account, key, endpoint);
Tables = mock ? new MockTable() : new AzureTable(account, key, endpoint);
Files = mock ? new MockFile() : new AzureFile(account,key, endpoint);
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);
}
}
}
4 changes: 2 additions & 2 deletions src/StorageLibrary/StorageLibrary.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Version>2.3.2</Version>
<Version>2.4.0</Version>
<Authors>Sebastián Gómez</Authors>
<Copyright>(C) 2018-2023 @sebagomez. All rights reserved.</Copyright>
<Copyright>(C) 2018-2024 @sebagomez</Copyright>
<RepositoryUrl>https://github.com/sebagomez/azurestorageexplorer</RepositoryUrl>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
Expand Down
49 changes: 27 additions & 22 deletions src/StorageLibrary/StorageObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,33 @@

namespace StorageLibrary
{
internal class StorageObject
{
public string Account { get; private set; }
public string Key { get; private set; }
public string Endpoint { get; private set; }
public string ConnectionString { get; private set; }
internal class StorageObject
{
public string Account { get; private set; }
public string Key { get; private set; }
public string Endpoint { get; private set; } = "core.windows.net";
public string ConnectionString { get; private set; }

const string CONNSTRING_TEMPLATE = "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}";
public StorageObject(string account, string key, string endpoint = "core.windows.net")
{
if (key.Contains("SharedAccessSignature="))
{
ConnectionString = key;
}
else
{
Account = account;
Key = key;
Endpoint = endpoint;
const string CONNSTRING_TEMPLATE = "DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1};EndpointSuffix={2}";
public StorageObject(string account, string key, string endpoint, string connectionString)
{
if (!string.IsNullOrEmpty(connectionString))
{
ConnectionString = connectionString;
}
else if (key.Contains("SharedAccessSignature="))
{
ConnectionString = key;
}
else
{
Account = account;
Key = key;
if (!string.IsNullOrEmpty(endpoint))
Endpoint = endpoint;

ConnectionString = string.Format(CONNSTRING_TEMPLATE, Account, Key, Endpoint);
}
}
}
ConnectionString = string.Format(CONNSTRING_TEMPLATE, Account, Key, Endpoint);
}
}
}
}
15 changes: 0 additions & 15 deletions src/StorageLibrary/Util/Client.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/web/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<AzureStorageWebExplorerVersion>2.15.3</AzureStorageWebExplorerVersion>
<AzureStorageWebExplorerVersion>2.16.0</AzureStorageWebExplorerVersion>
</PropertyGroup>
</Project>
4 changes: 2 additions & 2 deletions src/web/Pages/BaseComponent.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class BaseComponent
NavigationManager? NavManager { get; set; }

[Inject]
ProtectedSessionStorage? SessionStorage {get; set;}
ProtectedSessionStorage? SessionStorage { get; set; }

[Parameter]
public string? Selected { get; set; }
Expand All @@ -33,7 +33,7 @@ public void Increment(Counter counter)
{
counter.Inc();
}

public virtual Task SelectionDeletedAsync()
{
StateHasChanged();
Expand Down
Loading

0 comments on commit 197ee9d

Please sign in to comment.