gitlab-fog-azure-rm 2.3.0 → 2.4.0
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 +4 -0
- data/gitlab-fog-azure-rm.gemspec +1 -0
- data/lib/fog/azurerm/identity/credentials.rb +1 -1
- data/lib/fog/azurerm/identity/default_credentials.rb +2 -6
- data/lib/fog/azurerm/storage.rb +41 -21
- data/lib/fog/azurerm/version.rb +1 -1
- data/test/requests/storage/test_get_blob_https_url.rb +70 -0
- data/test/test_helper.rb +1 -0
- data/test/unit/test_credentials.rb +44 -0
- data/test/unit/test_default_credentials.rb +28 -44
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61aaa2ad5deaa2d7cfc03c52d87f504147a30bb21656fa8d74a0a1e2c1335b7f
|
4
|
+
data.tar.gz: 170d555337f12f9262ecc0a482871d7e88429f0dd968a3d4ee3c0ec3d42300dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5bf104672cd6913ce0c701bc8da7da11d0e80a3a383adf9be76188712b5d2cdcc3f13ea1512c12775226999c266c357ca9b0a1bb01e03196773beb4a1f6c1d9
|
7
|
+
data.tar.gz: 354cd43f816764b22005eb1d0c4e89eecfd55d31b776107904e8f3b1c704b09bd596244517dba24869a2552258cbe98477daaec61ab7fd0042ecd9d21e58dc11
|
data/CHANGELOG.md
CHANGED
data/gitlab-fog-azure-rm.gemspec
CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency 'rake', '~> 13.0'
|
23
23
|
spec.add_development_dependency 'rubocop', '~> 0.89.1'
|
24
24
|
spec.add_development_dependency 'simplecov'
|
25
|
+
spec.add_development_dependency 'timecop'
|
25
26
|
spec.add_development_dependency 'webmock'
|
26
27
|
spec.add_development_dependency 'webrick', '~> 1.8'
|
27
28
|
|
@@ -11,7 +11,6 @@ module Fog
|
|
11
11
|
def initialize(options)
|
12
12
|
@options = options
|
13
13
|
@credential_client = nil
|
14
|
-
@credentials = nil
|
15
14
|
end
|
16
15
|
|
17
16
|
def fetch_credentials_if_needed
|
@@ -37,7 +36,7 @@ module Fog
|
|
37
36
|
client = klass.new(options)
|
38
37
|
|
39
38
|
begin
|
40
|
-
credentials = client.
|
39
|
+
credentials = client.fetch_credentials_if_needed
|
41
40
|
rescue Fog::AzureRM::Identity::BaseClient::FetchCredentialsError
|
42
41
|
next
|
43
42
|
end
|
@@ -48,10 +47,7 @@ module Fog
|
|
48
47
|
end
|
49
48
|
end
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
@credentials = credentials
|
54
|
-
@credentials
|
50
|
+
@credential_client
|
55
51
|
end
|
56
52
|
end
|
57
53
|
end
|
data/lib/fog/azurerm/storage.rb
CHANGED
@@ -100,6 +100,9 @@ module Fog
|
|
100
100
|
raise e.message
|
101
101
|
end
|
102
102
|
|
103
|
+
@user_delegation_key_mutex = Mutex.new
|
104
|
+
@blob_client_mutex = Mutex.new
|
105
|
+
|
103
106
|
options[:environment] = options[:environment] || ENV['AZURE_ENVIRONMENT'] || ENVIRONMENT_AZURE_CLOUD
|
104
107
|
@environment = options[:environment]
|
105
108
|
@options = options
|
@@ -114,24 +117,7 @@ module Fog
|
|
114
117
|
@azure_storage_endpoint = options[:azure_storage_endpoint]
|
115
118
|
@azure_storage_domain = options[:azure_storage_domain]
|
116
119
|
|
117
|
-
|
118
|
-
@azure_storage_endpoint ||
|
119
|
-
if @azure_storage_domain.nil? || @azure_storage_domain.empty?
|
120
|
-
get_blob_endpoint(@azure_storage_account_name, true, @environment)
|
121
|
-
else
|
122
|
-
get_blob_endpoint_with_domain(@azure_storage_account_name, true, @azure_storage_domain)
|
123
|
-
end
|
124
|
-
|
125
|
-
azure_client = Azure::Storage::Common::Client.create({
|
126
|
-
storage_account_name: @azure_storage_account_name,
|
127
|
-
storage_access_key: @azure_storage_access_key,
|
128
|
-
signer: @azure_storage_token_signer
|
129
|
-
}.compact)
|
130
|
-
azure_client.storage_blob_host = storage_blob_host
|
131
|
-
@blob_client = Azure::Storage::Blob::BlobService.new(client: azure_client, api_version: @api_version)
|
132
|
-
@blob_client.with_filter(Fog::AzureRM::IdentityEncodingFilter.new)
|
133
|
-
@blob_client.with_filter(Azure::Storage::Common::Core::Filter::ExponentialRetryPolicyFilter.new)
|
134
|
-
@blob_client.with_filter(Azure::Core::Http::DebugFilter.new) if @debug
|
120
|
+
refresh_blob_client
|
135
121
|
end
|
136
122
|
|
137
123
|
private
|
@@ -177,11 +163,16 @@ module Fog
|
|
177
163
|
return nil unless @azure_storage_token_signer
|
178
164
|
|
179
165
|
if @credential_client
|
180
|
-
|
181
|
-
|
166
|
+
new_credentials = @credential_client.fetch_credentials_if_needed
|
167
|
+
changed = new_credentials != @credentials
|
168
|
+
|
169
|
+
if changed
|
170
|
+
@credentials = new_credentials
|
171
|
+
@azure_storage_token_signer = token_signer
|
172
|
+
refresh_blob_client
|
173
|
+
end
|
182
174
|
end
|
183
175
|
|
184
|
-
@user_delegation_key_mutex ||= Mutex.new
|
185
176
|
@user_delegation_key_mutex.synchronize do
|
186
177
|
if @user_delegation_key_expiry.nil? || @user_delegation_key_expiry < requested_expiry
|
187
178
|
start = Time.now
|
@@ -197,6 +188,35 @@ module Fog
|
|
197
188
|
|
198
189
|
@user_delegation_key
|
199
190
|
end
|
191
|
+
|
192
|
+
def refresh_blob_client
|
193
|
+
@blob_client_mutex.synchronize do
|
194
|
+
azure_client = create_azure_client
|
195
|
+
azure_client.storage_blob_host = storage_blob_host
|
196
|
+
@blob_client = Azure::Storage::Blob::BlobService.new(client: azure_client, api_version: @api_version)
|
197
|
+
@blob_client.with_filter(Fog::AzureRM::IdentityEncodingFilter.new)
|
198
|
+
@blob_client.with_filter(Azure::Storage::Common::Core::Filter::ExponentialRetryPolicyFilter.new)
|
199
|
+
@blob_client.with_filter(Azure::Core::Http::DebugFilter.new) if @debug
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def storage_blob_host
|
204
|
+
return @azure_storage_endpoint if @azure_storage_endpoint
|
205
|
+
|
206
|
+
if @azure_storage_domain.nil? || @azure_storage_domain.empty?
|
207
|
+
get_blob_endpoint(@azure_storage_account_name, true, @environment)
|
208
|
+
else
|
209
|
+
get_blob_endpoint_with_domain(@azure_storage_account_name, true, @azure_storage_domain)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def create_azure_client
|
214
|
+
Azure::Storage::Common::Client.create({
|
215
|
+
storage_account_name: @azure_storage_account_name,
|
216
|
+
storage_access_key: @azure_storage_access_key,
|
217
|
+
signer: @azure_storage_token_signer
|
218
|
+
}.compact)
|
219
|
+
end
|
200
220
|
end
|
201
221
|
end
|
202
222
|
end
|
data/lib/fog/azurerm/version.rb
CHANGED
@@ -224,4 +224,74 @@ class TestGetBlobHttpsUrl < Minitest::Test
|
|
224
224
|
assert_equal "#{@url}?#{@token}", @mock_service.get_blob_https_url('test_container', 'test_blob', Time.now.utc + 3600)
|
225
225
|
assert_equal "#{@url}?#{@token}", @mock_service.get_object_url('test_container', 'test_blob', Time.now.utc + 3600)
|
226
226
|
end
|
227
|
+
|
228
|
+
def test_blob_client_refreshes_when_credentials_expire
|
229
|
+
# Setup an expired initial token
|
230
|
+
initial_token_response = {
|
231
|
+
'access_token' => 'initial_token',
|
232
|
+
'expires_on' => (Time.now + ::Fog::AzureRM::Identity::Credentials::EXPIRATION_BUFFER + 10).to_i
|
233
|
+
}
|
234
|
+
|
235
|
+
# Setup refreshed token response
|
236
|
+
refreshed_token_response = {
|
237
|
+
'access_token' => 'refreshed_token',
|
238
|
+
'expires_on' => (Time.now + ::Fog::AzureRM::Identity::Credentials::EXPIRATION_BUFFER * 3).to_i
|
239
|
+
}
|
240
|
+
|
241
|
+
# Mock the identity endpoint to return initial token first, then refreshed token
|
242
|
+
stub_request(:get, "#{Fog::AzureRM::Identity::IDENTITY_ENDPOINT}?api-version=#{Fog::AzureRM::Identity::API_VERSION}&resource=https://storage.azure.com")
|
243
|
+
.with(headers: { 'Metadata' => 'true' })
|
244
|
+
.to_return(
|
245
|
+
{ status: 200, body: initial_token_response.to_json },
|
246
|
+
{ status: 200, body: refreshed_token_response.to_json }
|
247
|
+
)
|
248
|
+
|
249
|
+
# Setup user delegation key responses
|
250
|
+
delegation_response = <<~MSG
|
251
|
+
<UserDelegationKey>
|
252
|
+
<SignedOid>f81d4fae-7dec-11d0-a765-00a0c91e6bf6</SignedOid>
|
253
|
+
<SignedTid>72f988bf-86f1-41af-91ab-2d7cd011db47</SignedTid>
|
254
|
+
<SignedStart>2024-09-19T00:00:00Z</SignedStart>
|
255
|
+
<SignedExpiry>2024-09-26T00:00:00Z</SignedExpiry>
|
256
|
+
<SignedService>b</SignedService>
|
257
|
+
<SignedVersion>2020-02-10</SignedVersion>
|
258
|
+
<Value>UDELEGATIONKEY_INITIAL</Value>
|
259
|
+
<SignedKey>rL7...INITIAL</SignedKey>
|
260
|
+
</UserDelegationKey>
|
261
|
+
MSG
|
262
|
+
|
263
|
+
stub_request(:post, 'https://mockaccount.blob.core.windows.net?comp=userdelegationkey&restype=service')
|
264
|
+
.to_return(
|
265
|
+
{ status: 200, headers: { 'Content-Type': 'application/xml' }, body: delegation_response }
|
266
|
+
)
|
267
|
+
|
268
|
+
service = Fog::AzureRM::Storage.new(storage_account_managed_identity)
|
269
|
+
initial_blob_client = service.instance_variable_get(:@blob_client)
|
270
|
+
refute_nil initial_blob_client
|
271
|
+
|
272
|
+
# First request should not refresh the blob client and token
|
273
|
+
requested_expiry = Time.now + 60
|
274
|
+
url1 = service.get_blob_https_url('test_container', 'test_blob', requested_expiry)
|
275
|
+
refute_nil url1
|
276
|
+
assert url1.include?('https://mockaccount.blob.core.windows.net/test_container/test_blob')
|
277
|
+
|
278
|
+
second_blob_client = service.instance_variable_get(:@blob_client)
|
279
|
+
assert_equal initial_blob_client.object_id, second_blob_client.object_id
|
280
|
+
|
281
|
+
Timecop.travel(Time.now + ::Fog::AzureRM::Identity::Credentials::EXPIRATION_BUFFER) do
|
282
|
+
credentials = service.instance_variable_get(:@credentials)
|
283
|
+
assert credentials.refresh_needed?
|
284
|
+
|
285
|
+
# Second request should tigger credential refresh and blob client update
|
286
|
+
url2 = service.get_blob_https_url('test_container', 'test_blob', requested_expiry)
|
287
|
+
third_blob_client = service.instance_variable_get(:@blob_client)
|
288
|
+
credentials = service.instance_variable_get(:@credentials)
|
289
|
+
refute credentials.refresh_needed?
|
290
|
+
|
291
|
+
refute_nil url2
|
292
|
+
assert url2.include?('https://mockaccount.blob.core.windows.net/test_container/test_blob')
|
293
|
+
refute_nil third_blob_client
|
294
|
+
refute_equal third_blob_client.object_id, second_blob_client.object_id
|
295
|
+
end
|
296
|
+
end
|
227
297
|
end
|
data/test/test_helper.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path '../test_helper', __dir__
|
2
|
+
|
3
|
+
class TestCredentials < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@token = 'test-access-token'
|
6
|
+
@expires_at = Time.now + 3600 # 1 hour from now
|
7
|
+
@credentials = Fog::AzureRM::Identity::Credentials.new(@token, @expires_at)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_initialize
|
11
|
+
assert_equal @token, @credentials.token
|
12
|
+
assert_equal @expires_at, @credentials.expires_at
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_refresh_needed_when_expires_at_is_nil
|
16
|
+
credentials = Fog::AzureRM::Identity::Credentials.new(@token, nil)
|
17
|
+
assert credentials.refresh_needed?
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_refresh_needed_when_token_expires_soon
|
21
|
+
# Token expires in 5 minutes (less than 10 minute buffer)
|
22
|
+
expires_soon = Time.now + 300
|
23
|
+
credentials = Fog::AzureRM::Identity::Credentials.new(@token, expires_soon)
|
24
|
+
assert credentials.refresh_needed?
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_refresh_not_needed_when_token_has_time_left
|
28
|
+
# Token expires in 15 minutes (more than 10 minute buffer)
|
29
|
+
expires_later = Time.now + 900
|
30
|
+
credentials = Fog::AzureRM::Identity::Credentials.new(@token, expires_later)
|
31
|
+
refute credentials.refresh_needed?
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_refresh_needed_exactly_at_buffer_time
|
35
|
+
# Token expires exactly at buffer time (10 minutes)
|
36
|
+
expires_at_buffer = Time.now + Fog::AzureRM::Identity::Credentials::EXPIRATION_BUFFER
|
37
|
+
credentials = Fog::AzureRM::Identity::Credentials.new(@token, expires_at_buffer)
|
38
|
+
assert credentials.refresh_needed?
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_expiration_buffer_constant
|
42
|
+
assert_equal 600, Fog::AzureRM::Identity::Credentials::EXPIRATION_BUFFER
|
43
|
+
end
|
44
|
+
end
|
@@ -9,7 +9,6 @@ class TestDefaultCredentials < Minitest::Test
|
|
9
9
|
def test_initialize
|
10
10
|
assert_instance_of Fog::AzureRM::DefaultCredentials, @default_credentials
|
11
11
|
assert_nil @default_credentials.instance_variable_get(:@credential_client)
|
12
|
-
assert_nil @default_credentials.instance_variable_get(:@credentials)
|
13
12
|
end
|
14
13
|
|
15
14
|
def test_fetch_credentials_if_needed_with_no_credential_client
|
@@ -28,70 +27,55 @@ class TestDefaultCredentials < Minitest::Test
|
|
28
27
|
end
|
29
28
|
|
30
29
|
def test_credential_client_with_workflow_identity
|
31
|
-
|
32
|
-
mock_workflow_client.expect :fetch_credentials, 'fake_workflow_credentials'
|
30
|
+
workflow_client = Struct.new(:fetch_credentials_if_needed).new('fake_workflow_credentials')
|
33
31
|
|
34
|
-
Fog::AzureRM::Identity::WorkflowIdentityClient.stub :new,
|
35
|
-
|
36
|
-
assert_equal
|
32
|
+
Fog::AzureRM::Identity::WorkflowIdentityClient.stub :new, workflow_client do
|
33
|
+
client = @default_credentials.send(:credential_client)
|
34
|
+
assert_equal workflow_client, client
|
35
|
+
assert_equal workflow_client, @default_credentials.instance_variable_get(:@credential_client)
|
37
36
|
end
|
38
|
-
|
39
|
-
mock_workflow_client.verify
|
40
37
|
end
|
41
38
|
|
42
39
|
def test_credential_client_with_managed_identity
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
credentials = @default_credentials.send(:credential_client)
|
52
|
-
assert_equal 'fake_managed_credentials', credentials
|
40
|
+
workflow_client = Struct.new(:fetch_credentials_if_needed).new
|
41
|
+
managed_client = Struct.new(:fetch_credentials_if_needed).new('fake_managed_credentials')
|
42
|
+
|
43
|
+
Fog::AzureRM::Identity::WorkflowIdentityClient.stub :new, workflow_client do
|
44
|
+
Fog::AzureRM::Identity::ManagedIdentityClient.stub :new, managed_client do
|
45
|
+
client = @default_credentials.send(:credential_client)
|
46
|
+
assert_equal managed_client, client
|
47
|
+
assert_equal managed_client, @default_credentials.instance_variable_get(:@credential_client)
|
53
48
|
end
|
54
49
|
end
|
55
|
-
|
56
|
-
mock_workflow_client.verify
|
57
|
-
mock_managed_client.verify
|
58
50
|
end
|
59
51
|
|
60
52
|
def test_credential_client_with_failed_workflow_identity
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
53
|
+
workflow_client = Class.new do
|
54
|
+
def fetch_credentials_if_needed
|
55
|
+
raise ::Fog::AzureRM::Identity::BaseClient::FetchCredentialsError, 'Failed to fetch credentials'
|
56
|
+
end
|
57
|
+
end.new
|
65
58
|
|
66
|
-
|
67
|
-
mock_managed_client.expect :fetch_credentials, 'fake_managed_credentials'
|
59
|
+
managed_client = Struct.new(:fetch_credentials_if_needed).new('fake_managed_credentials')
|
68
60
|
|
69
|
-
Fog::AzureRM::Identity::WorkflowIdentityClient.stub :new,
|
70
|
-
Fog::AzureRM::Identity::ManagedIdentityClient.stub :new,
|
71
|
-
|
72
|
-
assert_equal
|
61
|
+
Fog::AzureRM::Identity::WorkflowIdentityClient.stub :new, workflow_client do
|
62
|
+
Fog::AzureRM::Identity::ManagedIdentityClient.stub :new, managed_client do
|
63
|
+
client = @default_credentials.send(:credential_client)
|
64
|
+
assert_equal managed_client, client
|
65
|
+
assert_equal managed_client, @default_credentials.instance_variable_get(:@credential_client)
|
73
66
|
end
|
74
67
|
end
|
75
|
-
|
76
|
-
mock_workflow_client.verify
|
77
|
-
mock_managed_client.verify
|
78
68
|
end
|
79
69
|
|
80
70
|
def test_credential_client_with_no_credentials
|
81
|
-
|
82
|
-
|
71
|
+
workflow_client = Struct.new(:fetch_credentials_if_needed).new
|
72
|
+
managed_client = Struct.new(:fetch_credentials_if_needed).new
|
83
73
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
Fog::AzureRM::Identity::WorkflowIdentityClient.stub :new, mock_workflow_client do
|
88
|
-
Fog::AzureRM::Identity::ManagedIdentityClient.stub :new, mock_managed_client do
|
74
|
+
Fog::AzureRM::Identity::WorkflowIdentityClient.stub :new, workflow_client do
|
75
|
+
Fog::AzureRM::Identity::ManagedIdentityClient.stub :new, managed_client do
|
89
76
|
assert_nil @default_credentials.send(:credential_client)
|
90
77
|
assert_nil @default_credentials.instance_variable_get(:@credential_client)
|
91
78
|
end
|
92
79
|
end
|
93
|
-
|
94
|
-
mock_workflow_client.verify
|
95
|
-
mock_managed_client.verify
|
96
80
|
end
|
97
81
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-fog-azure-rm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shaffan Chaudhry
|
@@ -15,9 +15,10 @@ authors:
|
|
15
15
|
- Azeem Sajid
|
16
16
|
- Maham Nazir
|
17
17
|
- Abbas Sheikh
|
18
|
+
autorequire:
|
18
19
|
bindir: bin
|
19
20
|
cert_chain: []
|
20
|
-
date:
|
21
|
+
date: 2025-09-30 00:00:00.000000000 Z
|
21
22
|
dependencies:
|
22
23
|
- !ruby/object:Gem::Dependency
|
23
24
|
name: codeclimate-test-reporter
|
@@ -89,6 +90,20 @@ dependencies:
|
|
89
90
|
- - ">="
|
90
91
|
- !ruby/object:Gem::Version
|
91
92
|
version: '0'
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: timecop
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
type: :development
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
92
107
|
- !ruby/object:Gem::Dependency
|
93
108
|
name: webmock
|
94
109
|
requirement: !ruby/object:Gem::Requirement
|
@@ -237,6 +252,7 @@ dependencies:
|
|
237
252
|
version: 1.10.8
|
238
253
|
description: This is a stripped-down fork of fog-azure-rm that enables Azure Blob
|
239
254
|
Storage to be used with CarrierWave and Fog.
|
255
|
+
email:
|
240
256
|
executables: []
|
241
257
|
extensions: []
|
242
258
|
extra_rdoc_files:
|
@@ -448,6 +464,7 @@ files:
|
|
448
464
|
- test/requests/storage/test_save_page_blob.rb
|
449
465
|
- test/requests/storage/test_wait_blob_copy_operation_to_finish.rb
|
450
466
|
- test/test_helper.rb
|
467
|
+
- test/unit/test_credentials.rb
|
451
468
|
- test/unit/test_default_credentials.rb
|
452
469
|
- test/unit/test_managed_identity_client.rb
|
453
470
|
- test/unit/test_shared_access_signature_generator.rb
|
@@ -472,7 +489,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
472
489
|
- !ruby/object:Gem::Version
|
473
490
|
version: '0'
|
474
491
|
requirements: []
|
475
|
-
rubygems_version: 3.
|
492
|
+
rubygems_version: 3.5.22
|
493
|
+
signing_key:
|
476
494
|
specification_version: 4
|
477
495
|
summary: Module for the 'fog' gem to support Azure Blob Storage with CarrierWave and
|
478
496
|
Fog.
|