azure-blob 0.5.9.1 → 0.7.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 +9 -0
- data/README.md +28 -8
- data/Rakefile +11 -0
- data/lib/azure_blob/client.rb +29 -8
- data/lib/azure_blob/http.rb +2 -0
- data/lib/azure_blob/identity_token.rb +7 -16
- data/lib/azure_blob/instance_metadata_service.rb +28 -0
- data/lib/azure_blob/version.rb +1 -1
- data/lib/azure_blob/workload_identity.rb +41 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fd3246ce35e01647d7aaa6e5819320799d347a96d0b95b9bc4d4d89939ce9a1
|
|
4
|
+
data.tar.gz: b6ace232dddeed3415294e866115420a91935b7332c79494e5b17c9a2e91eb52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f375dca9e699671f5fc165b8463b313f1cc823d5cb6f3f0666cffc22665fa7becb7d075f61dd0cbb131cf85ddc14c47b37b236d6d28b276b2d13b89cc7626660
|
|
7
|
+
data.tar.gz: 06bfe2d031d6deb2a58da359258df5923ffab3c23d6edf0a36ee4adb93ee4ac69a82d50d571e97b4cfdff10913f2e6c4a834f5338258c33f0b0a0336de1cde17
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] 2025-01-05
|
|
4
|
+
|
|
5
|
+
- Add optional `timeout` parameter to all API methods
|
|
6
|
+
- Add `AzureBlob::Http::TimeoutError` for handling Azure `OperationTimedOut` errors
|
|
7
|
+
|
|
8
|
+
## [0.6.0] 2025-12-01
|
|
9
|
+
|
|
10
|
+
- Add support for AKS workload identity
|
|
11
|
+
|
|
3
12
|
## [0.5.9.1] 2025-06-30
|
|
4
13
|
|
|
5
14
|
- Fix a regression in #13 + fix add a test
|
data/README.md
CHANGED
|
@@ -26,14 +26,13 @@ microsoft:
|
|
|
26
26
|
|
|
27
27
|
### Managed Identity (Entra ID)
|
|
28
28
|
|
|
29
|
-
AzureBlob supports managed identities on
|
|
29
|
+
AzureBlob supports managed identities on:
|
|
30
30
|
- Azure VM
|
|
31
31
|
- App Service
|
|
32
|
+
- AKS (Azure Kubernetes Service) with workload identity
|
|
32
33
|
- Azure Functions (Untested but should work)
|
|
33
34
|
- Azure Containers (Untested but should work)
|
|
34
35
|
|
|
35
|
-
AKS support will likely require more work. Contributions are welcome.
|
|
36
|
-
|
|
37
36
|
To authenticate through managed identities instead of a shared key, omit `storage_access_key` from your `storage.yml` file and pass in the identity `principal_id`.
|
|
38
37
|
|
|
39
38
|
ActiveStorage config example:
|
|
@@ -46,6 +45,21 @@ prod:
|
|
|
46
45
|
principal_id: 71b34410-4c50-451d-b456-95ead1b18cce
|
|
47
46
|
```
|
|
48
47
|
|
|
48
|
+
#### AKS with Workload Identity
|
|
49
|
+
|
|
50
|
+
ActiveStorage config example:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
prod:
|
|
54
|
+
service: AzureBlob
|
|
55
|
+
container: container_name
|
|
56
|
+
storage_account_name: account_name
|
|
57
|
+
use_managed_identities: true
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
> uses `AZURE_CLIENT_ID`, `AZURE_TENANT_ID` and `AZURE_FEDERATED_TOKEN_FILE` environment variables, made available by AKS cluster when Azure AD Workload Identity is set up properly.
|
|
61
|
+
|
|
62
|
+
|
|
49
63
|
### Azurite
|
|
50
64
|
|
|
51
65
|
To use Azurite, pass the `storage_blob_host` config key with the Azurite URL (`http://127.0.0.1:10000/devstoreaccount1` by default)
|
|
@@ -126,20 +140,26 @@ A dev environment is supplied through Nix with [devenv](https://devenv.sh/).
|
|
|
126
140
|
|
|
127
141
|
To test with Entra ID, the `AZURE_ACCESS_KEY` environment variable must be unset and the code must be ran or proxied through a VPS with the proper roles.
|
|
128
142
|
|
|
129
|
-
For cost saving, the terraform
|
|
130
|
-
To create the
|
|
143
|
+
For cost saving, the terraform variables `create_vm`, `create_app_service`, and `create_aks` are false by default.
|
|
144
|
+
To create the VM, App Service, and/or AKS cluster, create a var file `var.tfvars` containing:
|
|
131
145
|
|
|
132
146
|
```
|
|
133
147
|
create_vm = true
|
|
134
148
|
create_app_service = true
|
|
149
|
+
create_aks = true
|
|
135
150
|
```
|
|
136
151
|
and re-apply terraform: `terraform apply -var-file=var.tfvars`.
|
|
137
152
|
|
|
138
|
-
This will create the
|
|
153
|
+
This will create the infrastructure and required managed identities.
|
|
154
|
+
|
|
155
|
+
**Testing:**
|
|
156
|
+
- `bin/rake test_azure_vm` - Establishes a VPN connection to the Azure VM and runs tests using node identity
|
|
157
|
+
- `bin/rake test_app_service` - Establishes a VPN connection to the App Service container and runs tests
|
|
158
|
+
- `bin/rake test_aks` - Establishes a VPN connection to the AKS cluster and runs tests using workload identity
|
|
139
159
|
|
|
140
|
-
|
|
160
|
+
You might be prompted for a sudo password when the VPN starts (sshuttle).
|
|
141
161
|
|
|
142
|
-
After you are done, run terraform again without the var file (`terraform apply`) to destroy
|
|
162
|
+
After you are done, run terraform again without the var file (`terraform apply`) to destroy all resources.
|
|
143
163
|
|
|
144
164
|
#### Cleanup
|
|
145
165
|
|
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/aks_vpn"
|
|
8
9
|
require_relative "test/support/azurite"
|
|
9
10
|
|
|
10
11
|
Minitest::TestTask.create(:test_rails) do
|
|
@@ -49,6 +50,16 @@ ensure
|
|
|
49
50
|
vpn.kill
|
|
50
51
|
end
|
|
51
52
|
|
|
53
|
+
task :test_aks do |t|
|
|
54
|
+
vpn = AksVpn.new
|
|
55
|
+
ENV["AZURE_CLIENT_ID"] = vpn.client_id
|
|
56
|
+
ENV["AZURE_TENANT_ID"] = vpn.tenant_id
|
|
57
|
+
ENV["AZURE_FEDERATED_TOKEN_FILE"] = vpn.token_file
|
|
58
|
+
Rake::Task["test_entra_id"].execute
|
|
59
|
+
ensure
|
|
60
|
+
vpn.kill
|
|
61
|
+
end
|
|
62
|
+
|
|
52
63
|
task :test_azurite do |t|
|
|
53
64
|
azurite = Azurite.new
|
|
54
65
|
# Azurite well-known credentials
|
data/lib/azure_blob/client.rb
CHANGED
|
@@ -70,6 +70,7 @@ module AzureBlob
|
|
|
70
70
|
# Ending point in bytes
|
|
71
71
|
def get_blob(key, options = {})
|
|
72
72
|
uri = generate_uri("#{container}/#{key}")
|
|
73
|
+
uri.query = URI.encode_www_form(timeout: options[:timeout]) if options[:timeout]
|
|
73
74
|
|
|
74
75
|
headers = {
|
|
75
76
|
"x-ms-range": options[:start] && "bytes=#{options[:start]}-#{options[:end]}",
|
|
@@ -92,6 +93,7 @@ module AzureBlob
|
|
|
92
93
|
def copy_blob(key, source_key, options = {})
|
|
93
94
|
source_client = options.delete(:source_client) || self
|
|
94
95
|
uri = generate_uri("#{container}/#{key}")
|
|
96
|
+
uri.query = URI.encode_www_form(timeout: options[:timeout]) if options[:timeout]
|
|
95
97
|
|
|
96
98
|
source_uri = source_client.signed_uri(source_key, permissions: "r", expiry: Time.at(Time.now.to_i + 300).utc.iso8601)
|
|
97
99
|
|
|
@@ -114,6 +116,7 @@ module AzureBlob
|
|
|
114
116
|
# Sets the value of the x-ms-delete-snapshots header. Default to +include+
|
|
115
117
|
def delete_blob(key, options = {})
|
|
116
118
|
uri = generate_uri("#{container}/#{key}")
|
|
119
|
+
uri.query = URI.encode_www_form(timeout: options[:timeout]) if options[:timeout]
|
|
117
120
|
|
|
118
121
|
headers = {
|
|
119
122
|
"x-ms-delete-snapshots": options[:delete_snapshots] || "include",
|
|
@@ -152,6 +155,7 @@ module AzureBlob
|
|
|
152
155
|
prefix: options[:prefix].to_s.gsub(/\\/, "/"),
|
|
153
156
|
}
|
|
154
157
|
query[:maxresults] = options[:max_results] if options[:max_results]
|
|
158
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
155
159
|
uri.query = URI.encode_www_form(**query)
|
|
156
160
|
|
|
157
161
|
fetcher = ->(marker) do
|
|
@@ -172,6 +176,7 @@ module AzureBlob
|
|
|
172
176
|
# To check for blob presence, look for `blob_exist?` as `get_blob_properties` raises on missing blob.
|
|
173
177
|
def get_blob_properties(key, options = {})
|
|
174
178
|
uri = generate_uri("#{container}/#{key}")
|
|
179
|
+
uri.query = URI.encode_www_form(timeout: options[:timeout]) if options[:timeout]
|
|
175
180
|
|
|
176
181
|
response = Http.new(uri, additional_headers(options), signer:).head
|
|
177
182
|
|
|
@@ -196,7 +201,9 @@ module AzureBlob
|
|
|
196
201
|
# Returns a hash of the blob's tags.
|
|
197
202
|
def get_blob_tags(key, options = {})
|
|
198
203
|
uri = generate_uri("#{container}/#{key}")
|
|
199
|
-
|
|
204
|
+
query = { comp: "tags" }
|
|
205
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
206
|
+
uri.query = URI.encode_www_form(**query)
|
|
200
207
|
response = Http.new(uri, additional_headers(options), signer:).get
|
|
201
208
|
|
|
202
209
|
Tags.from_response(response).to_h
|
|
@@ -209,7 +216,9 @@ module AzureBlob
|
|
|
209
216
|
# This can be used to see if the container exist or obtain metadata.
|
|
210
217
|
def get_container_properties(options = {})
|
|
211
218
|
uri = generate_uri(container)
|
|
212
|
-
|
|
219
|
+
query = { restype: "container" }
|
|
220
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
221
|
+
uri.query = URI.encode_www_form(**query)
|
|
213
222
|
response = Http.new(uri, additional_headers(options), signer:, raise_on_error: false).head
|
|
214
223
|
|
|
215
224
|
Container.new(response)
|
|
@@ -232,7 +241,9 @@ module AzureBlob
|
|
|
232
241
|
headers[:"x-ms-blob-public-access"] = options[:public_access] if [ "container", "blob" ].include?(options[:public_access])
|
|
233
242
|
headers.merge!(additional_headers(options))
|
|
234
243
|
|
|
235
|
-
|
|
244
|
+
query = { restype: "container" }
|
|
245
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
246
|
+
uri.query = URI.encode_www_form(**query)
|
|
236
247
|
response = Http.new(uri, headers, signer:).put
|
|
237
248
|
end
|
|
238
249
|
|
|
@@ -241,7 +252,9 @@ module AzureBlob
|
|
|
241
252
|
# Calls to {Delete Container}[https://learn.microsoft.com/en-us/rest/api/storageservices/delete-container]
|
|
242
253
|
def delete_container(options = {})
|
|
243
254
|
uri = generate_uri(container)
|
|
244
|
-
|
|
255
|
+
query = { restype: "container" }
|
|
256
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
257
|
+
uri.query = URI.encode_www_form(**query)
|
|
245
258
|
response = Http.new(uri, additional_headers(options), signer:).delete
|
|
246
259
|
end
|
|
247
260
|
|
|
@@ -283,6 +296,7 @@ module AzureBlob
|
|
|
283
296
|
# Will be saved on the blob in Azure.
|
|
284
297
|
def create_append_blob(key, options = {})
|
|
285
298
|
uri = generate_uri("#{container}/#{key}")
|
|
299
|
+
uri.query = URI.encode_www_form(timeout: options[:timeout]) if options[:timeout]
|
|
286
300
|
|
|
287
301
|
headers = {
|
|
288
302
|
"x-ms-blob-type": "AppendBlob",
|
|
@@ -306,7 +320,9 @@ module AzureBlob
|
|
|
306
320
|
# The checksum must be the checksum of the block not the blob.
|
|
307
321
|
def append_blob_block(key, content, options = {})
|
|
308
322
|
uri = generate_uri("#{container}/#{key}")
|
|
309
|
-
|
|
323
|
+
query = { comp: "appendblock" }
|
|
324
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
325
|
+
uri.query = URI.encode_www_form(**query)
|
|
310
326
|
|
|
311
327
|
headers = {
|
|
312
328
|
"Content-Length": content_size(content),
|
|
@@ -330,7 +346,9 @@ module AzureBlob
|
|
|
330
346
|
def put_blob_block(key, index, content, options = {})
|
|
331
347
|
block_id = generate_block_id(index)
|
|
332
348
|
uri = generate_uri("#{container}/#{key}")
|
|
333
|
-
|
|
349
|
+
query = { comp: "block", blockid: block_id }
|
|
350
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
351
|
+
uri.query = URI.encode_www_form(**query)
|
|
334
352
|
|
|
335
353
|
headers = {
|
|
336
354
|
"Content-Length": content_size(content),
|
|
@@ -358,7 +376,9 @@ module AzureBlob
|
|
|
358
376
|
block_list = BlockList.new(block_ids)
|
|
359
377
|
content = block_list.to_s
|
|
360
378
|
uri = generate_uri("#{container}/#{key}")
|
|
361
|
-
|
|
379
|
+
query = { comp: "blocklist" }
|
|
380
|
+
query[:timeout] = options[:timeout] if options[:timeout]
|
|
381
|
+
uri.query = URI.encode_www_form(**query)
|
|
362
382
|
|
|
363
383
|
headers = {
|
|
364
384
|
"Content-Length": content_size(content),
|
|
@@ -386,7 +406,7 @@ module AzureBlob
|
|
|
386
406
|
block_size = options[:block_size] || DEFAULT_BLOCK_SIZE
|
|
387
407
|
block_count = (content_size(content).to_f / block_size).ceil
|
|
388
408
|
block_ids = block_count.times.map do |i|
|
|
389
|
-
put_blob_block(key, i, content.read(block_size))
|
|
409
|
+
put_blob_block(key, i, content.read(block_size), options.slice(:timeout))
|
|
390
410
|
end
|
|
391
411
|
|
|
392
412
|
commit_blob_blocks(key, block_ids, options)
|
|
@@ -395,6 +415,7 @@ module AzureBlob
|
|
|
395
415
|
def put_blob_single(key, content, options = {})
|
|
396
416
|
content = StringIO.new(content) if content.is_a? String
|
|
397
417
|
uri = generate_uri("#{container}/#{key}")
|
|
418
|
+
uri.query = URI.encode_www_form(timeout: options[:timeout]) if options[:timeout]
|
|
398
419
|
|
|
399
420
|
headers = {
|
|
400
421
|
"x-ms-blob-type": "BlockBlob",
|
data/lib/azure_blob/http.rb
CHANGED
|
@@ -21,6 +21,7 @@ module AzureBlob
|
|
|
21
21
|
class FileNotFoundError < Error; end
|
|
22
22
|
class ForbiddenError < Error; end
|
|
23
23
|
class IntegrityError < Error; end
|
|
24
|
+
class TimeoutError < Error; end
|
|
24
25
|
|
|
25
26
|
include REXML
|
|
26
27
|
|
|
@@ -99,6 +100,7 @@ module AzureBlob
|
|
|
99
100
|
|
|
100
101
|
ERROR_CODE_MAPPINGS = {
|
|
101
102
|
"Md5Mismatch" => IntegrityError,
|
|
103
|
+
"OperationTimedOut" => TimeoutError,
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
def sanitize_headers
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
+
require_relative "instance_metadata_service"
|
|
2
|
+
require_relative "workload_identity"
|
|
1
3
|
require "json"
|
|
2
4
|
|
|
3
5
|
module AzureBlob
|
|
4
6
|
class IdentityToken
|
|
5
|
-
RESOURCE_URI = "https://storage.azure.com/"
|
|
6
7
|
EXPIRATION_BUFFER = 600 # 10 minutes
|
|
7
8
|
|
|
8
|
-
IDENTITY_ENDPOINT = ENV["IDENTITY_ENDPOINT"] || "http://169.254.169.254/metadata/identity/oauth2/token"
|
|
9
|
-
API_VERSION = ENV["IDENTITY_ENDPOINT"] ? "2019-08-01" : "2018-02-01"
|
|
10
|
-
|
|
11
9
|
def initialize(principal_id: nil)
|
|
12
|
-
@
|
|
13
|
-
|
|
14
|
-
'api-version': API_VERSION,
|
|
15
|
-
resource: RESOURCE_URI,
|
|
16
|
-
}
|
|
17
|
-
params[:principal_id] = principal_id if principal_id
|
|
18
|
-
@identity_uri.query = URI.encode_www_form(params)
|
|
10
|
+
@service = AzureBlob::WorkloadIdentity.federated_token? ?
|
|
11
|
+
AzureBlob::WorkloadIdentity.new : AzureBlob::InstanceMetadataService.new(principal_id: principal_id)
|
|
19
12
|
end
|
|
20
13
|
|
|
21
14
|
def to_s
|
|
@@ -31,13 +24,11 @@ module AzureBlob
|
|
|
31
24
|
|
|
32
25
|
def refresh
|
|
33
26
|
return unless expired?
|
|
34
|
-
headers = { "Metadata" => "true" }
|
|
35
|
-
headers["X-IDENTITY-HEADER"] = ENV["IDENTITY_HEADER"] if ENV["IDENTITY_HEADER"]
|
|
36
27
|
|
|
37
28
|
attempt = 0
|
|
38
29
|
begin
|
|
39
30
|
attempt += 1
|
|
40
|
-
response = JSON.parse(
|
|
31
|
+
response = JSON.parse(service.request)
|
|
41
32
|
rescue AzureBlob::Http::Error => error
|
|
42
33
|
if should_retry?(error, attempt)
|
|
43
34
|
attempt = 1 if error.status == 410
|
|
@@ -48,7 +39,7 @@ module AzureBlob
|
|
|
48
39
|
raise
|
|
49
40
|
end
|
|
50
41
|
@token = response["access_token"]
|
|
51
|
-
@expiration =
|
|
42
|
+
@expiration = service.expiration(response)
|
|
52
43
|
end
|
|
53
44
|
|
|
54
45
|
def should_retry?(error, attempt)
|
|
@@ -61,6 +52,6 @@ module AzureBlob
|
|
|
61
52
|
end
|
|
62
53
|
EXPONENTIAL_BACKOFF = [ 2, 6, 14, 30 ]
|
|
63
54
|
|
|
64
|
-
attr_reader :
|
|
55
|
+
attr_reader :service, :expiration, :token
|
|
65
56
|
end
|
|
66
57
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module AzureBlob
|
|
2
|
+
class InstanceMetadataService # :nodoc:
|
|
3
|
+
IDENTITY_ENDPOINT = ENV["IDENTITY_ENDPOINT"] || "http://169.254.169.254/metadata/identity/oauth2/token"
|
|
4
|
+
API_VERSION = ENV["IDENTITY_ENDPOINT"] ? "2019-08-01" : "2018-02-01"
|
|
5
|
+
RESOURCE_URI = "https://storage.azure.com/"
|
|
6
|
+
|
|
7
|
+
def initialize(principal_id: nil)
|
|
8
|
+
@identity_uri = URI.parse(IDENTITY_ENDPOINT)
|
|
9
|
+
params = {
|
|
10
|
+
'api-version': API_VERSION,
|
|
11
|
+
resource: RESOURCE_URI,
|
|
12
|
+
}
|
|
13
|
+
params[:principal_id] = principal_id if principal_id
|
|
14
|
+
@identity_uri.query = URI.encode_www_form(params)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def request
|
|
18
|
+
headers = { "Metadata" => "true" }
|
|
19
|
+
headers["X-IDENTITY-HEADER"] = ENV["IDENTITY_HEADER"] if ENV["IDENTITY_HEADER"]
|
|
20
|
+
|
|
21
|
+
AzureBlob::Http.new(@identity_uri, headers).get
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def expiration(response)
|
|
25
|
+
Time.at(response["expires_on"].to_i)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/azure_blob/version.rb
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module AzureBlob
|
|
2
|
+
class WorkloadIdentity # :nodoc:
|
|
3
|
+
IDENTITY_ENDPOINT = "https://login.microsoftonline.com/#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/token"
|
|
4
|
+
CLIENT_ID = ENV["AZURE_CLIENT_ID"]
|
|
5
|
+
SCOPE = "https://storage.azure.com/.default"
|
|
6
|
+
GRANT_TYPE = "client_credentials"
|
|
7
|
+
CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
|
|
8
|
+
|
|
9
|
+
FEDERATED_TOKEN_FILE = ENV["AZURE_FEDERATED_TOKEN_FILE"].to_s
|
|
10
|
+
|
|
11
|
+
def self.federated_token?
|
|
12
|
+
!FEDERATED_TOKEN_FILE.empty?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@identity_uri = URI.parse(IDENTITY_ENDPOINT)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def request
|
|
20
|
+
AzureBlob::Http.new(@identity_uri).post(
|
|
21
|
+
URI.encode_www_form(
|
|
22
|
+
client_id: CLIENT_ID,
|
|
23
|
+
scope: SCOPE,
|
|
24
|
+
client_assertion_type: CLIENT_ASSERTION_TYPE,
|
|
25
|
+
client_assertion: federated_token,
|
|
26
|
+
grant_type: GRANT_TYPE
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def expiration(response)
|
|
32
|
+
Time.now + response["expires_in"].to_i
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def federated_token
|
|
38
|
+
File.read(FEDERATED_TOKEN_FILE).strip
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: azure-blob
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joé Dupuis
|
|
@@ -49,11 +49,13 @@ files:
|
|
|
49
49
|
- lib/azure_blob/errors.rb
|
|
50
50
|
- lib/azure_blob/http.rb
|
|
51
51
|
- lib/azure_blob/identity_token.rb
|
|
52
|
+
- lib/azure_blob/instance_metadata_service.rb
|
|
52
53
|
- lib/azure_blob/metadata.rb
|
|
53
54
|
- lib/azure_blob/shared_key_signer.rb
|
|
54
55
|
- lib/azure_blob/tags.rb
|
|
55
56
|
- lib/azure_blob/user_delegation_key.rb
|
|
56
57
|
- lib/azure_blob/version.rb
|
|
58
|
+
- lib/azure_blob/workload_identity.rb
|
|
57
59
|
homepage: https://github.com/testdouble/azure-blob
|
|
58
60
|
licenses:
|
|
59
61
|
- MIT
|