azure-blob 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +37 -0
- data/Rakefile +23 -0
- data/lib/active_storage/service/azure_blob_service.rb +2 -1
- data/lib/azure_blob/client.rb +48 -16
- data/lib/azure_blob/shared_key_signer.rb +9 -2
- data/lib/azure_blob/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f67999e9a3c71ecac32f52a65dae1e4bf82e76704f97ecdaed08c84b66df5630
|
4
|
+
data.tar.gz: 19fedb6d95a7d1c14da8baa8e3f608b4c3ae8e99f76a68daa5a54efe7213858a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42405d38134512ed6ac88372c8a2e7383835e2d3558280b8dad0570532d905c6edaba6bfb84bd3fd2478f3a4456d575da5448bb223e016b89f99a96bd00bcec2
|
7
|
+
data.tar.gz: f5148357bb3ba47ea5d730ebc123a2ab901e8c9380d7c7d6620cf6fd6623ec495f9dc0659434be42458888f6a936ffd221bd9f27591f3fbce2f0b0361fee5b9f
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -46,6 +46,30 @@ prod:
|
|
46
46
|
principal_id: 71b34410-4c50-451d-b456-95ead1b18cce
|
47
47
|
```
|
48
48
|
|
49
|
+
### Azurite
|
50
|
+
|
51
|
+
To use Azurite, pass the `storage_blob_host` config key with the Azurite URL (`http://127.0.0.1:10000/devstoreaccount1` by default)
|
52
|
+
and the Azurite credentials (`devstoreaccount1` and `Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==` by default).
|
53
|
+
|
54
|
+
Example:
|
55
|
+
|
56
|
+
```
|
57
|
+
dev:
|
58
|
+
service: AzureBlob
|
59
|
+
container: container_name
|
60
|
+
storage_account_name: devstoreaccount1
|
61
|
+
storage_access_key: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
|
62
|
+
storage_blob_host: http://127.0.0.1:10000/devstoreaccount1
|
63
|
+
```
|
64
|
+
|
65
|
+
You'll have to create the container before you can start uploading files.
|
66
|
+
You can do so using Azure CLI, Azure Storage Explorer, or by running:
|
67
|
+
|
68
|
+
`bin/rails runner "ActiveStorage::Blob.service.client.tap{|client| client.create_container unless client.get_container_properties.present?}.tap { |client| puts 'done!' if client.get_container_properties.present?}"`
|
69
|
+
|
70
|
+
Make sure that `config.active_storage.service = :dev` is set to your azurite configuration.
|
71
|
+
Container names can't have any special characters, or you'll get an error.
|
72
|
+
|
49
73
|
## Standalone
|
50
74
|
|
51
75
|
Instantiate a client with your account name, an access key and the container name:
|
@@ -71,6 +95,19 @@ client.delete_blob(path)
|
|
71
95
|
|
72
96
|
For the full list of methods: https://www.rubydoc.info/gems/azure-blob/AzureBlob/Client
|
73
97
|
|
98
|
+
## options
|
99
|
+
|
100
|
+
### Lazy loading
|
101
|
+
|
102
|
+
The client is configured to raise an error early for missing credentials, causing it to crash before becoming healthy. This behavior can sometimes be undesirable, such as during assets precompilation.
|
103
|
+
|
104
|
+
To enable lazy loading and ignore missing credentials, set the `lazy` option:
|
105
|
+
|
106
|
+
`AzureBlob::Client.new(account_name: nil, access_key: nil, container: nil, lazy: true)`
|
107
|
+
|
108
|
+
or add `lazy: true` to your `config/storage.yml` for Active Storage.
|
109
|
+
|
110
|
+
|
74
111
|
## Contributing
|
75
112
|
|
76
113
|
### Dev environment
|
data/Rakefile
CHANGED
@@ -5,6 +5,7 @@ require "minitest/test_task"
|
|
5
5
|
require "azure_blob"
|
6
6
|
require_relative "test/support/app_service_vpn"
|
7
7
|
require_relative "test/support/azure_vm_vpn"
|
8
|
+
require_relative "test/support/azurite"
|
8
9
|
|
9
10
|
Minitest::TestTask.create(:test_rails) do
|
10
11
|
self.test_globs = [ "test/rails/**/test_*.rb",
|
@@ -39,6 +40,28 @@ ensure
|
|
39
40
|
vpn.kill
|
40
41
|
end
|
41
42
|
|
43
|
+
task :test_azurite do |t|
|
44
|
+
azurite = Azurite.new
|
45
|
+
# Azurite well-known credentials
|
46
|
+
# https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage#well-known-storage-account-and-key
|
47
|
+
account_name = ENV["AZURE_ACCOUNT_NAME"] = "devstoreaccount1"
|
48
|
+
access_key = ENV["AZURE_ACCESS_KEY"] = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
|
49
|
+
host = ENV["STORAGE_BLOB_HOST"] = "http://127.0.0.1:10000/devstoreaccount1"
|
50
|
+
ENV["TESTING_AZURITE"] = "true"
|
51
|
+
|
52
|
+
# Create containers
|
53
|
+
private_container = AzureBlob::Client.new(account_name:, access_key:, host:, container: ENV["AZURE_PRIVATE_CONTAINER"])
|
54
|
+
public_container = AzureBlob::Client.new(account_name:, access_key:, host:, container: ENV["AZURE_PUBLIC_CONTAINER"])
|
55
|
+
# public_container.delete_container
|
56
|
+
private_container.create_container unless private_container.get_container_properties.present?
|
57
|
+
public_container.create_container(public_access: true) unless public_container.get_container_properties.present?
|
58
|
+
|
59
|
+
Rake::Task["test_client"].execute
|
60
|
+
Rake::Task["test_rails"].execute
|
61
|
+
ensure
|
62
|
+
azurite.kill
|
63
|
+
end
|
64
|
+
|
42
65
|
task :test_entra_id do |t|
|
43
66
|
ENV["AZURE_ACCESS_KEY"] = nil
|
44
67
|
Rake::Task["test"].execute
|
@@ -35,13 +35,14 @@ module ActiveStorage
|
|
35
35
|
class Service::AzureBlobService < Service
|
36
36
|
attr_reader :client, :container, :signer
|
37
37
|
|
38
|
-
def initialize(storage_account_name:, storage_access_key: nil, container:, public: false, **options)
|
38
|
+
def initialize(storage_account_name:, storage_access_key: nil, container:, storage_blob_host: nil, public: false, **options)
|
39
39
|
@container = container
|
40
40
|
@public = public
|
41
41
|
@client = AzureBlob::Client.new(
|
42
42
|
account_name: storage_account_name,
|
43
43
|
access_key: storage_access_key,
|
44
44
|
container: container,
|
45
|
+
host: storage_blob_host,
|
45
46
|
**options)
|
46
47
|
end
|
47
48
|
|
data/lib/azure_blob/client.rb
CHANGED
@@ -15,22 +15,15 @@ module AzureBlob
|
|
15
15
|
# AzureBlob Client class. You interact with the Azure Blob api
|
16
16
|
# through an instance of this class.
|
17
17
|
class Client
|
18
|
-
def initialize(account_name:, access_key: nil, principal_id: nil, container:, **options)
|
18
|
+
def initialize(account_name:, access_key: nil, principal_id: nil, container:, host: nil, **options)
|
19
19
|
@account_name = account_name
|
20
20
|
@container = container
|
21
|
+
@host = host
|
21
22
|
@cloud_regions = options[:cloud_regions]&.to_sym || :global
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
if !using_managed_identities && no_access_key
|
27
|
-
raise AzureBlob::Error.new(
|
28
|
-
"`access_key` cannot be empty. To use managed identities instead, pass a `principal_id` or set `use_managed_identities` to true."
|
29
|
-
)
|
30
|
-
end
|
31
|
-
@signer = using_managed_identities ?
|
32
|
-
AzureBlob::EntraIdSigner.new(account_name:, host:, principal_id:) :
|
33
|
-
AzureBlob::SharedKeySigner.new(account_name:, access_key:)
|
23
|
+
@access_key = access_key
|
24
|
+
@principal_id = principal_id
|
25
|
+
@use_managed_identities = options[:use_managed_identities]
|
26
|
+
signer unless options[:lazy]
|
34
27
|
end
|
35
28
|
|
36
29
|
# Create a blob of type block. Will automatically split the the blob in multiple block and send the blob in pieces (blocks) if the blob is too big.
|
@@ -149,7 +142,8 @@ module AzureBlob
|
|
149
142
|
#
|
150
143
|
# Calls to {Get Blob Properties}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob-properties]
|
151
144
|
#
|
152
|
-
# This can be used to
|
145
|
+
# This can be used to obtain metadata such as content type, disposition, checksum or Azure custom metadata.
|
146
|
+
# To check for blob presence, look for `blob_exist?` as `get_blob_properties` raises on missing blob.
|
153
147
|
def get_blob_properties(key, options = {})
|
154
148
|
uri = generate_uri("#{container}/#{key}")
|
155
149
|
|
@@ -158,6 +152,15 @@ module AzureBlob
|
|
158
152
|
Blob.new(response)
|
159
153
|
end
|
160
154
|
|
155
|
+
# Returns a boolean indicating if the blob exists.
|
156
|
+
#
|
157
|
+
# Calls to {Get Blob Properties}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob-properties]
|
158
|
+
def blob_exist?(key, options = {})
|
159
|
+
get_blob_properties(key, options).present?
|
160
|
+
rescue AzureBlob::Http::FileNotFoundError
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
161
164
|
# Returns the tags associated with a blob
|
162
165
|
#
|
163
166
|
# Calls to the {Get Blob Tags}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-blob-tags] endpoint.
|
@@ -185,13 +188,24 @@ module AzureBlob
|
|
185
188
|
Container.new(response)
|
186
189
|
end
|
187
190
|
|
191
|
+
# Returns a boolean indicating if the container exists.
|
192
|
+
#
|
193
|
+
# Calls to {Get Container Properties}[https://learn.microsoft.com/en-us/rest/api/storageservices/get-container-properties]
|
194
|
+
def container_exist?(options = {})
|
195
|
+
get_container_properties(options = {}).present?
|
196
|
+
end
|
197
|
+
|
188
198
|
# Create the container
|
189
199
|
#
|
190
200
|
# Calls to {Create Container}[https://learn.microsoft.com/en-us/rest/api/storageservices/create-container]
|
191
201
|
def create_container(options = {})
|
192
202
|
uri = generate_uri(container)
|
203
|
+
headers = {}
|
204
|
+
headers[:"x-ms-blob-public-access"] = "blob" if options[:public_access]
|
205
|
+
headers[:"x-ms-blob-public-access"] = options[:public_access] if ["container","blob"].include?(options[:public_access])
|
206
|
+
|
193
207
|
uri.query = URI.encode_www_form(restype: "container")
|
194
|
-
response = Http.new(uri, signer:).put
|
208
|
+
response = Http.new(uri, headers, signer:).put
|
195
209
|
end
|
196
210
|
|
197
211
|
# Delete the container
|
@@ -359,6 +373,24 @@ module AzureBlob
|
|
359
373
|
@host ||= "https://#{account_name}.blob.#{CLOUD_REGIONS_SUFFIX[cloud_regions]}"
|
360
374
|
end
|
361
375
|
|
362
|
-
|
376
|
+
def signer
|
377
|
+
@signer ||=
|
378
|
+
begin
|
379
|
+
no_access_key = access_key.nil? || access_key&.empty?
|
380
|
+
using_managed_identities = no_access_key && !principal_id.nil? || use_managed_identities
|
381
|
+
|
382
|
+
if !using_managed_identities && no_access_key
|
383
|
+
raise AzureBlob::Error.new(
|
384
|
+
"`access_key` cannot be empty. To use managed identities instead, pass a `principal_id` or set `use_managed_identities` to true."
|
385
|
+
)
|
386
|
+
end
|
387
|
+
|
388
|
+
using_managed_identities ?
|
389
|
+
AzureBlob::EntraIdSigner.new(account_name:, host:, principal_id:) :
|
390
|
+
AzureBlob::SharedKeySigner.new(account_name:, access_key:, host:)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
attr_reader :account_name, :container, :http, :cloud_regions, :access_key, :principal_id, :use_managed_identities
|
363
395
|
end
|
364
396
|
end
|
@@ -7,9 +7,11 @@ require_relative "canonicalized_resource"
|
|
7
7
|
|
8
8
|
module AzureBlob
|
9
9
|
class SharedKeySigner # :nodoc:
|
10
|
-
def initialize(account_name:, access_key:)
|
10
|
+
def initialize(account_name:, access_key:, host:)
|
11
11
|
@account_name = account_name
|
12
12
|
@access_key = Base64.decode64(access_key)
|
13
|
+
@host = host
|
14
|
+
@remove_prefix = @host.include?("/#{@account_name}")
|
13
15
|
end
|
14
16
|
|
15
17
|
def authorization_header(uri:, verb:, headers: {})
|
@@ -39,6 +41,11 @@ module AzureBlob
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def sas_token(uri, options = {})
|
44
|
+
if remove_prefix
|
45
|
+
uri = uri.clone
|
46
|
+
uri.path = uri.path.delete_prefix("/#{account_name}")
|
47
|
+
end
|
48
|
+
|
42
49
|
to_sign = [
|
43
50
|
options[:permissions],
|
44
51
|
options[:start],
|
@@ -99,6 +106,6 @@ module AzureBlob
|
|
99
106
|
end
|
100
107
|
end
|
101
108
|
|
102
|
-
attr_reader :access_key, :account_name
|
109
|
+
attr_reader :access_key, :account_name, :remove_prefix
|
103
110
|
end
|
104
111
|
end
|
data/lib/azure_blob/version.rb
CHANGED