Git Product home page Git Product logo

stowage's Introduction

Stowage

Nuget Nuget (with prereleases) Nuget

logo

This documentation is for Stowage v2 which is a major redesign. Version 1 documentation can be found here.

Stowage is a bloat-free .NET cloud storage kit that supports at minimum THE major โ˜ providers.

  • Independent ๐Ÿ†“. Provides an independent implementation of the โ˜ storage APIs. Because you can't just have official corporate SDKs as a single source of truth.
  • Readable. Official SDKs like the ones for AWS, Google, or Azure are overengineered and unreadable. Some are autogenerated and look just bad and foreign to .NET ecosystem. Some won't even compile without some custom rituals.
  • Beautiful ๐Ÿฆ‹. Designed to fit into .NET ecosystem, not the other way around.
  • Rich ๐Ÿ’ฐ. Provide maximum functionality. However, in addition to that, provide humanly possible way to easily extend it with new functionality, without waiting for new SDK releases.
  • Embeddable ๐Ÿ”ฑ. Has zero external dependencies, relies only on built-in .NET API. Often official SDKs have a very deep dependency tree causing a large binary sizes and endless conflicts during runtime. This one is a single .NET .dll with no dependencies whatsoever.
  • Cross Cloud ๐ŸŒฅ. Same API. Any cloud. Best decisions made for you. It's like iPhone vs Windows Phone.
  • Cross Tested โŽ. It's not just cross cloud but also cross tested (I don't know how to call this). It tests that all cloud providers behave absolutely the same on various method calls. They should validate arguments the same, throw same exceptions in the same situations, and support the same set of functionality. Sounds simple, but it's rare to find in a library. And it important, otherwise what's the point of a generic API if you need to write a lot of if()s? (or pattern matching).

This library originally came out from being frustrated on working on my another library - Storage.Net. While it's OK, most of the time I had to deal with SDK incompatibilities, breaking changes, oddnesses, and slowness, whereas most of the time users needs something simple that just works.

Getting Started

Right, time to gear up. We'll do it step by step. First, you need to install the Nuget package.

Simplest case, using the local ๐Ÿ’ฝ and writing text "I'm a page!!!" to a file called "pagefile.sys" at the root of disk C::

using Stowage;

using(IFileStorage fs = Files.Of.LocalDisk("c:\\")) {
   await fs.WriteText("pagefile.sys", "I'm a page!!!!");
}

This is local disk, yeah? But what about cloud storage, like Azure Blob Storage? Piece of cake:

using Stowage;

using(IFileStorage fs = Files.Of.AzureBlobStorage("accountName", "accountKey", "containerName")) {
   var entries = await fs.Ls();
}

โ™’ Streaming

Streaming is a first-class feature. This means the streaming is real with no workarounds or in-memory buffers, so you can upload/download files of virtually unlimited sizes. Most official SDKs do not support streaming at all - surprisingly even the cloud leader's .NET SDK doesn't. Each requires some sort of crippled down version of stream - either knowing length beforehand, or will buffer it all in memory. I don't. I stream like a stream.

Proper streaming support also means that you can transform streams as you write to them or read from them - something that is not available in the native SDKs. For instance gzipping, encryption, anything else.

Streaming is also truly compatible with synchronous and asynchronous API.

Details/Documentation

Whenever a method appears here, I assume it belongs to IFileStorage interface, unless specified.

Listing/Browsing

Use .Ls() (short for list) - very easy to remember! Everyone knows what ls does, right? Optionally allows to list entries recursively.

Reading

The core method for reading is Stream OpenRead(IOPath path) - this returns a stream from file path. Stream is the lowest level data structure. There are other helper methods that by default rely on this method, like ReadText etc. Just have a quick look:

IFileStorage fs = ...;
Stream target = ...;

// copy to another stream
using Stream s = await fs.OpenRead("/myfile.txt");

// synchronous copy:
s.CopyTo(target);

// or alternatively, asynchronous copy (preferred):
await s.CopyToAsync(target);

// if you just need text:
string content = await fs.ReadText("/myfile.txt");

Of course there are more overloaded methods you can take advantage of.

Writing

The main method Stream OpenWrite(IOPath path, ...) opens(/creates?) a file for writing. It returns a real writeable stream you can write to and close afterwards. It behaves like a stream and is a stream.

There are other overloads which support writing text etc.

Destroying ๐Ÿงจ

Rm(IOPath path) trashes files or folders (or both) with options to do it recursively!

Other

There are other useful utility methods:

  • bool Exists(IOPath path) that checks for file existence. It supposed to be really efficient, hence a separate method.
  • Ren renames files and folders.
  • and more are coming - check IFileStorage interface to be up to date.

Supported Storage Systems (Built-In)

Instantiation instructions are in the code documentation (IntelliSense?) - I prefer this to writing out here locally.

Below are some details worth mentioning.

AWS S3

In AWS, the path addressing style is the following:

/bucket/path/object

Ls on the root folder returns list of buckets in the AWS account, whether you do have access to them or not.

Authentication

The most usual way to authenticate with S3 is to use the following method:

IFileStorage storage = Files.Of.AmazonS3(key, secret, region);

These are what Amazon calls "long-term" credentials. If you are using STS, the same method overload allows you to pass sessionToken.

Another way to authenticate is using CLI profile. This is useful when you machine is already authenticated using aws cli, awsume or similar tools that write credentials and configuration to ~/.aws/credentials and ~/.aws/config.

You only need to pass the profile name (and only if it's not a default one):

IFileStorage storage = Files.Of.AmazonS3FromCliProfile();

This method has other default parameters, such as regionName which can be specified or overridden if not found in CLI configuration.

Minio

Minio is essentially using the standard S3 protocol, but addressing style is slightly different. There is a helper extension that somewhat simplifies Minio authentication:

IFileStorage storage = Files.Of.Minio(endpoint, key, secret);

Azure Blob Storage

In Azure Blob Storage, path addressing style is the following:

/container/path/object

Note that there is no storage account in the path, mostly because Shared Key authentication is storage account scoped, not tenant scoped.

Ls on the root folder returns list of containers in the storage account.

Authentication

Azure provider supports authentication with Shared Key:

IFileStorage storage = Files.Of.AzureBlobStorage(accountName, sharedKey);

Since v2, authentication with Entra Id service principals is supported too:

IFileStorage storage = Files.Of.AzureBlobStorage(
    accountName,
    new ClientSecretCredential(tenantId, clientId, clientSecret));

Interactive authentication with user credentials, and managed identities are not yet supported, but watch this space.

Emulator

Azure emulator is supported, just use AzureBlobStorageWithLocalEmulator() method to connect to it.

๐Ÿ“ˆ Extending

There are many ways to extend functionality:

  1. Documentation. You might think it's not extending anything, however if user is not aware for some functionality it doesn't exist. Documenting it is making it available, hence extending. You must be slightly mad to follow my style of writing though.
  2. New functionality. Adding utility methods like copying files inside or between accounts, automatic JSON serialisation etc. is always good. Look IFileStorage interface and PolyfilledFileStorage. In most cases these two files are enough to add pure business logic. Not counting unit tests. Which you must write. Otherwise it's easier to do the whole thing by myself. Which is what will happen according to my experience.
  3. Native optimisations. Some functionality is generic, and some depends on a specific cloud provider. For instance, one can copy a file by downloading it locally, and uploading with a new name. Or utilise a native REST call that accepts source and target file name, if it exists. Involves digging deeper into specific provider's API.

When contributing a new provider, it's way more preferrable to embed it's code in the library, provided that:

  • there are no extra nuget dependencies.
  • it's cross-platform.

I'm a strong advocate of simplicity and not going to repeat the mistake of turning this into a nuget tree dependency hell!

โ” Who?

Related Projects

  • RCLONE - cross-platform open-source cloud sync tool.
  • Storage.Net - the roots of this project.

๐Ÿ’ฐ Contributing

You are welcome to contribute in any form, however I wouldn't bother, especially financially. Don't bother buying me a โ˜•, I can do it myself real cheap. During my years of OSS development everyone I know (including myself) have only lost money. Why I'm still doing this? Probably because it's just cool and I'm enjoying it.

stowage's People

Contributors

aloneguid avatar iamkoch avatar javidsho avatar zitmen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stowage's Issues

How do I use this against a local directory?

Library is great - documentation is .....spotty at best.....

I'm trying to use Stowage against a local directory for development, and Azure Blob container for production. But I can't even get the local disk part to work...

I have tried this code (and a great many variations of it - with or without trailing backslash, with or without file pattern, using full path in the .Of.LocalDisk() call or only when doing .Ls()) - so far, without any success:

using (IFileStorage fs = Files.Of.LocalDisk(@"D:\"))
{
    var entries = await fs.Ls(@"\projects\mailtemplates", false);
}

I keep getting this error:

ArgumentException: path needs to be a folder (Parameter 'path')

What am I doing wrong? I would like to get all the files (or preferably: all files matching a certain pattern) from this folder ....

Anonymous S3 access

I would like to have anonymous access to S3. In my scenario access is authorized through VPC policies and doesn't require signed requests. However the software will automatically sign the AWS request even if I don't specify access keys.

Would it be possible to support anonymous S3 calls (ie. without the AWS signature)

How to use Azurite (emulator) for Azure Blob Storage.

Hi ๐Ÿ‘‹

The previous version Storage.Net is supporting the emu account while in development. It doesn't seem like stowage is supporting it. I looked at the source code and couldn't find any way of connecting, easily, to the emulator. Therefore, I revised the current implementation and do have one issue.

To address the issue, I used the same approach as @iamkoch which is allowing to override the azure endpoint. It worked great; however, it looks like using the http://127.0.0.1:10000/devstoreaccount1 endpoint with a container (emu in this example) lead to an issue with the emulator not looking at the right account. To address that, I had to set my ContainerName to devstoreaccount1/emu.

Here is the code I changed to get things working. If you prefer, I can create a pull request to start a discussion.

Files.cs

public static IFileStorage AzureBlobStorage(this IFilesFactory _, string accountName, string sharedKey, string containerName = null)
{
   return AzureBlobStorage(_, new Uri($"https://{accountName}.blob.core.windows.net"), accountName, sharedKey, containerName);
}

public static IFileStorage AzureBlobStorage(this IFilesFactory _, Uri endpoint, string accountName, string sharedKey, string containerName = null)
{
   return new AzureBlobFileStorage(endpoint, containerName, new SharedKeyAuthHandler(accountName, sharedKey));
}

AzureBlobFileStorage.cs

public AzureBlobFileStorage(Uri endpoint, string containerName, DelegatingHandler authHandler) : base(endpoint, authHandler)
{
   if(endpoint is null)
      throw new ArgumentNullException(nameof(endpoint));
   if(string.IsNullOrEmpty(containerName))
      throw new ArgumentException($"'{nameof(containerName)}' cannot be null or empty", nameof(containerName));
   if(authHandler is null)
      throw new ArgumentNullException(nameof(authHandler));

   _containerName = containerName;
}

I guess I could keep it that way, but I find it a bit confusing to put the storage account in the container name when in development. Haven't tested in production with Azure Storage yet.

Thanks,

Strong named / signed assemblies

Hi,

Thanks for this library, it is really useful.

Would you entertain the idea of creating strong named / signed assemblies. Currently I am getting the following warning:
CSC : warning CS8002: Referenced assembly 'Stowage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have a strong name

Allow injection of custom HTTP client handlers

Hi,

may I ask what is the recommendation for injecting custom delegating handlers? I can't find how to do this without reflection.
If it's not currently available, I would like to kindly raise a feature request ๐Ÿ˜Š
It would be very helpful to be able to implement retries, logging, throttling and other useful features.

Thank you,
Martin

Writing Azure blob larger than 10MiB fails with 400 Bad Request

Hi,

I am writing large blobs to Azure blob storage and I keep getting response 400 Bad Request (The specified blob or block content is invalid). I tracked down the reason to a wrong block ID.
The documentation says (https://learn.microsoft.com/en-us/rest/api/storageservices/put-block?tabs=azure-ad):

For a specified blob, all block IDs must be the same length. If a block is uploaded with a block ID of a different length than the block IDs for any existing uncommitted blocks, the service returns error response code 400 (Bad Request).

The stream write fails always on blob with ID 10, so once we don't get single digit ID, we are braking the requirement.
Since there is a limit of 50k blocks, I suggest to pad the ID to a 6 digit string.

Thanks,
Martin

S3 signature is not calculated correctly

Hi,

I am testing with S3 compatible storage (Minio) and I am experiencing issues where I am getting 403 Forbidden during authentication. Error code is SignatureDoesNotMatch.
I have found 2 causes:

  1. if endpoint contains port other than 80 or 443, the port must be included in the host header
  2. if the path ends with a path separator it must not be url encoded (I verified this issue also with AWS S3)

Best,
Martin

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.