gitlab-fog-azure-rm 2.4.0 → 2.5.1
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/.gitlab-ci.yml +7 -4
- data/.rubocop_todo.yml +188 -82
- data/CHANGELOG.md +8 -0
- data/gitlab-fog-azure-rm.gemspec +5 -4
- data/lib/azure/core/auth/shared_key.rb +3 -1
- data/lib/fog/azurerm/requests/storage/get_blob_url.rb +2 -2
- data/lib/fog/azurerm/requests/storage/get_container_url.rb +2 -2
- data/lib/fog/azurerm/storage.rb +124 -47
- data/lib/fog/azurerm/utilities/general.rb +1 -1
- data/lib/fog/azurerm/version.rb +1 -1
- data/test/requests/storage/test_get_blob.rb +4 -4
- data/test/requests/storage/test_get_blob_https_url.rb +54 -48
- data/test/requests/storage/test_storage_thread_safety.rb +138 -0
- data/test/test_helper.rb +15 -0
- metadata +24 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: faf6ef8f40417712f91769e2ac0dea345c7451f1457919399c1aa574cf6a44a5
|
|
4
|
+
data.tar.gz: 4596861b6a717a743ff2d250f91f3bb4cafd911cb57e0efeeae14368e04162eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f59d2e0764335c68f69462f380781e3a6f395467959f8af92f076dd4492f968d82bdc52b471f93a3fb1568e939c277e70cf6eac8fc79ed65e144b53eb1d966a2
|
|
7
|
+
data.tar.gz: ea77ec4d01cff9bebec6642ad94517c0df51246de8ab41f48241f8281841737c08da6f20ec17e9c79a9ad5d6f56bc51940c8cac169a17d226f0037c84ea5c706
|
data/.gitlab-ci.yml
CHANGED
|
@@ -4,26 +4,29 @@ include:
|
|
|
4
4
|
default:
|
|
5
5
|
image: "ruby:${RUBY_VERSION}"
|
|
6
6
|
|
|
7
|
+
variables:
|
|
8
|
+
BUNDLE_PATH: vendor
|
|
9
|
+
|
|
7
10
|
cache:
|
|
8
11
|
paths:
|
|
9
12
|
- vendor/ruby
|
|
10
13
|
|
|
11
14
|
before_script:
|
|
12
15
|
- ruby -v
|
|
13
|
-
- bundle install -j $(nproc)
|
|
16
|
+
- bundle install -j $(nproc)
|
|
14
17
|
|
|
15
18
|
test:
|
|
16
19
|
script:
|
|
17
20
|
- bundle exec rake test
|
|
18
21
|
parallel:
|
|
19
22
|
matrix:
|
|
20
|
-
- RUBY_VERSION: [ "2
|
|
23
|
+
- RUBY_VERSION: [ "3.2", "3.3", "3.4", "4.0" ]
|
|
21
24
|
|
|
22
25
|
rubocop:
|
|
23
26
|
script:
|
|
24
27
|
- bundle exec rubocop
|
|
25
28
|
variables:
|
|
26
|
-
RUBY_VERSION: "
|
|
29
|
+
RUBY_VERSION: "4.0"
|
|
27
30
|
|
|
28
31
|
integration:
|
|
29
32
|
services:
|
|
@@ -32,4 +35,4 @@ integration:
|
|
|
32
35
|
script:
|
|
33
36
|
- bundle exec rake integration
|
|
34
37
|
variables:
|
|
35
|
-
RUBY_VERSION: "
|
|
38
|
+
RUBY_VERSION: "4.0"
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,41 +1,71 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on
|
|
3
|
+
# on 2026-03-31 03:13:36 UTC using RuboCop version 1.86.0.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
|
8
8
|
|
|
9
9
|
# Offense count: 1
|
|
10
|
-
#
|
|
11
|
-
|
|
10
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
11
|
+
Gemspec/DeprecatedAttributeAssignment:
|
|
12
|
+
Exclude:
|
|
13
|
+
- 'gitlab-fog-azure-rm.gemspec'
|
|
14
|
+
|
|
15
|
+
# Offense count: 8
|
|
16
|
+
# Configuration parameters: EnforcedStyle, AllowedGems.
|
|
17
|
+
# SupportedStyles: Gemfile, gems.rb, gemspec
|
|
18
|
+
Gemspec/DevelopmentDependencies:
|
|
19
|
+
Exclude:
|
|
20
|
+
- 'gitlab-fog-azure-rm.gemspec'
|
|
21
|
+
|
|
22
|
+
# Offense count: 1
|
|
23
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
24
|
+
Gemspec/RequireMFA:
|
|
25
|
+
Exclude:
|
|
26
|
+
- 'gitlab-fog-azure-rm.gemspec'
|
|
27
|
+
|
|
28
|
+
# Offense count: 1
|
|
12
29
|
Gemspec/RequiredRubyVersion:
|
|
13
30
|
Exclude:
|
|
14
31
|
- 'gitlab-fog-azure-rm.gemspec'
|
|
15
32
|
|
|
16
|
-
# Offense count:
|
|
17
|
-
#
|
|
33
|
+
# Offense count: 21
|
|
34
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
18
35
|
Layout/EmptyLineAfterGuardClause:
|
|
19
|
-
|
|
36
|
+
Exclude:
|
|
37
|
+
- 'lib/fog/azurerm/models/storage/directories.rb'
|
|
38
|
+
- 'lib/fog/azurerm/models/storage/files.rb'
|
|
39
|
+
- 'lib/fog/azurerm/requests/storage/delete_blob.rb'
|
|
40
|
+
- 'lib/fog/azurerm/requests/storage/delete_container.rb'
|
|
41
|
+
- 'lib/fog/azurerm/requests/storage/get_blob.rb'
|
|
42
|
+
- 'lib/fog/azurerm/requests/storage/get_blob_properties.rb'
|
|
43
|
+
- 'lib/fog/azurerm/requests/storage/get_container_properties.rb'
|
|
44
|
+
- 'lib/fog/azurerm/requests/storage/list_blobs.rb'
|
|
45
|
+
- 'lib/fog/azurerm/requests/storage/list_containers.rb'
|
|
46
|
+
- 'lib/fog/azurerm/requests/storage/save_page_blob.rb'
|
|
47
|
+
- 'lib/fog/azurerm/utilities/general.rb'
|
|
48
|
+
- 'test/integration/blob.rb'
|
|
49
|
+
- 'test/requests/storage/test_list_blobs.rb'
|
|
50
|
+
- 'test/requests/storage/test_list_containers.rb'
|
|
51
|
+
- 'test/requests/storage/test_save_page_blob.rb'
|
|
52
|
+
|
|
53
|
+
# Offense count: 1
|
|
54
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
55
|
+
Layout/EmptyLinesAfterModuleInclusion:
|
|
56
|
+
Exclude:
|
|
57
|
+
- 'lib/fog/azurerm/identity/base_client.rb'
|
|
20
58
|
|
|
21
59
|
# Offense count: 1
|
|
22
|
-
#
|
|
60
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
23
61
|
# Configuration parameters: AllowAliasSyntax, AllowedMethods.
|
|
24
62
|
# AllowedMethods: alias_method, public, protected, private
|
|
25
63
|
Layout/EmptyLinesAroundAttributeAccessor:
|
|
26
64
|
Exclude:
|
|
27
65
|
- 'lib/fog/azurerm/models/storage/file.rb'
|
|
28
66
|
|
|
29
|
-
# Offense count:
|
|
30
|
-
#
|
|
31
|
-
# SupportedStyles: native, lf, crlf
|
|
32
|
-
Layout/EndOfLine:
|
|
33
|
-
Exclude:
|
|
34
|
-
- 'lib/fog/azurerm/models/resources/azure_resource.rb'
|
|
35
|
-
- 'lib/fog/azurerm/models/resources/azure_resources.rb'
|
|
36
|
-
|
|
37
|
-
# Offense count: 13
|
|
38
|
-
# Cop supports --auto-correct.
|
|
67
|
+
# Offense count: 6
|
|
68
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
39
69
|
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
|
|
40
70
|
# SupportedHashRocketStyles: key, separator, table
|
|
41
71
|
# SupportedColonStyles: key, separator, table
|
|
@@ -43,10 +73,9 @@ Layout/EndOfLine:
|
|
|
43
73
|
Layout/HashAlignment:
|
|
44
74
|
Exclude:
|
|
45
75
|
- 'lib/fog/azurerm/models/storage/file.rb'
|
|
46
|
-
- 'test/test_helper.rb'
|
|
47
76
|
|
|
48
77
|
# Offense count: 1
|
|
49
|
-
#
|
|
78
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
50
79
|
# Configuration parameters: EnforcedStyle.
|
|
51
80
|
# SupportedStyles: space, no_space
|
|
52
81
|
Layout/SpaceAroundEqualsInParameterDefault:
|
|
@@ -54,62 +83,77 @@ Layout/SpaceAroundEqualsInParameterDefault:
|
|
|
54
83
|
- 'lib/fog/azurerm/requests/storage/release_container_lease.rb'
|
|
55
84
|
|
|
56
85
|
# Offense count: 1
|
|
57
|
-
#
|
|
86
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
58
87
|
Layout/SpaceBeforeComma:
|
|
59
88
|
Exclude:
|
|
60
89
|
- 'gitlab-fog-azure-rm.gemspec'
|
|
61
90
|
|
|
91
|
+
# Offense count: 18
|
|
92
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
93
|
+
Lint/AmbiguousOperatorPrecedence:
|
|
94
|
+
Exclude:
|
|
95
|
+
- 'test/integration/blob.rb'
|
|
96
|
+
- 'test/models/storage/test_file.rb'
|
|
97
|
+
- 'test/requests/storage/test_get_blob_https_url.rb'
|
|
98
|
+
|
|
62
99
|
# Offense count: 2
|
|
63
100
|
Lint/DuplicateMethods:
|
|
64
101
|
Exclude:
|
|
65
102
|
- 'lib/fog/azurerm/models/storage/directory.rb'
|
|
66
103
|
- 'lib/fog/azurerm/models/storage/file.rb'
|
|
67
104
|
|
|
105
|
+
# Offense count: 3
|
|
106
|
+
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
|
107
|
+
Lint/EmptyBlock:
|
|
108
|
+
Exclude:
|
|
109
|
+
- 'test/requests/storage/test_get_blob.rb'
|
|
110
|
+
|
|
111
|
+
# Offense count: 8
|
|
112
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
113
|
+
Lint/NonAtomicFileOperation:
|
|
114
|
+
Exclude:
|
|
115
|
+
- 'test/integration/blob.rb'
|
|
116
|
+
|
|
68
117
|
# Offense count: 2
|
|
69
118
|
Lint/RescueException:
|
|
70
119
|
Exclude:
|
|
71
120
|
- 'lib/fog/azurerm/models/storage/directories.rb'
|
|
72
121
|
- 'lib/fog/azurerm/requests/storage/check_container_exists.rb'
|
|
73
122
|
|
|
74
|
-
# Offense count: 3
|
|
75
|
-
Lint/UselessAssignment:
|
|
76
|
-
Exclude:
|
|
77
|
-
- 'lib/fog/azurerm/storage.rb'
|
|
78
|
-
- 'test/integration/container.rb'
|
|
79
|
-
|
|
80
123
|
# Offense count: 1
|
|
124
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
81
125
|
# Configuration parameters: CheckForMethodsWithNoSideEffects.
|
|
82
126
|
Lint/Void:
|
|
83
127
|
Exclude:
|
|
84
128
|
- 'lib/fog/azurerm/models/storage/directory.rb'
|
|
85
129
|
|
|
86
|
-
# Offense count:
|
|
87
|
-
# Configuration parameters:
|
|
130
|
+
# Offense count: 38
|
|
131
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
|
88
132
|
Metrics/AbcSize:
|
|
89
133
|
Max: 66
|
|
90
134
|
|
|
91
|
-
# Offense count:
|
|
135
|
+
# Offense count: 14
|
|
92
136
|
# Configuration parameters: CountComments, CountAsOne.
|
|
93
137
|
Metrics/ClassLength:
|
|
94
|
-
Max:
|
|
138
|
+
Max: 232
|
|
95
139
|
|
|
96
|
-
# Offense count:
|
|
97
|
-
# Configuration parameters:
|
|
140
|
+
# Offense count: 6
|
|
141
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
98
142
|
Metrics/CyclomaticComplexity:
|
|
99
143
|
Max: 14
|
|
100
144
|
|
|
101
145
|
# Offense count: 98
|
|
102
|
-
# Configuration parameters: CountComments, CountAsOne,
|
|
146
|
+
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
103
147
|
Metrics/MethodLength:
|
|
104
|
-
Max:
|
|
148
|
+
Max: 110
|
|
105
149
|
|
|
106
150
|
# Offense count: 1
|
|
107
|
-
# Configuration parameters: CountKeywordArgs.
|
|
151
|
+
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
|
108
152
|
Metrics/ParameterLists:
|
|
109
153
|
Max: 6
|
|
110
154
|
|
|
111
|
-
# Offense count:
|
|
112
|
-
# Configuration parameters:
|
|
155
|
+
# Offense count: 6
|
|
156
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
113
157
|
Metrics/PerceivedComplexity:
|
|
114
158
|
Max: 14
|
|
115
159
|
|
|
@@ -120,14 +164,21 @@ Naming/ConstantName:
|
|
|
120
164
|
- 'lib/fog/azurerm/models/storage/sku_name.rb'
|
|
121
165
|
- 'lib/fog/azurerm/models/storage/sku_tier.rb'
|
|
122
166
|
|
|
123
|
-
# Offense count:
|
|
124
|
-
#
|
|
167
|
+
# Offense count: 33
|
|
168
|
+
# Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
|
|
169
|
+
# AllowedMethods: call
|
|
170
|
+
# WaywardPredicates: infinite?, nonzero?
|
|
171
|
+
Naming/PredicateMethod:
|
|
172
|
+
Enabled: false
|
|
173
|
+
|
|
174
|
+
# Offense count: 33
|
|
175
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
125
176
|
# Configuration parameters: PreferredName.
|
|
126
177
|
Naming/RescuedExceptionsVariableName:
|
|
127
178
|
Enabled: false
|
|
128
179
|
|
|
129
|
-
# Offense count:
|
|
130
|
-
#
|
|
180
|
+
# Offense count: 10
|
|
181
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
131
182
|
# Configuration parameters: EnforcedStyle.
|
|
132
183
|
# SupportedStyles: separated, grouped
|
|
133
184
|
Style/AccessorGrouping:
|
|
@@ -135,41 +186,84 @@ Style/AccessorGrouping:
|
|
|
135
186
|
- 'lib/fog/azurerm/requests/storage/save_page_blob.rb'
|
|
136
187
|
- 'test/test_helper.rb'
|
|
137
188
|
|
|
189
|
+
# Offense count: 1
|
|
190
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
191
|
+
# Configuration parameters: EnforcedStyle, AllowedParentClasses.
|
|
192
|
+
# SupportedStyles: class_keyword, class_new, class_definition
|
|
193
|
+
Style/EmptyClassDefinition:
|
|
194
|
+
Exclude:
|
|
195
|
+
- 'lib/fog/azurerm/identity/base_client.rb'
|
|
196
|
+
|
|
138
197
|
# Offense count: 2
|
|
139
|
-
#
|
|
198
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
140
199
|
Style/ExpandPathArguments:
|
|
141
200
|
Exclude:
|
|
142
201
|
- 'gitlab-fog-azure-rm.gemspec'
|
|
143
202
|
- 'lib/fog/azurerm/utilities/general.rb'
|
|
144
203
|
|
|
145
204
|
# Offense count: 2
|
|
146
|
-
#
|
|
205
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
147
206
|
Style/ExplicitBlockArgument:
|
|
148
207
|
Exclude:
|
|
149
208
|
- 'lib/fog/azurerm/models/storage/files.rb'
|
|
150
209
|
|
|
151
|
-
# Offense count:
|
|
152
|
-
#
|
|
210
|
+
# Offense count: 4
|
|
211
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
212
|
+
# Configuration parameters: AllowedVars, DefaultToNil.
|
|
213
|
+
Style/FetchEnvVar:
|
|
214
|
+
Exclude:
|
|
215
|
+
- 'lib/fog/azurerm/identity/managed_identity_client.rb'
|
|
216
|
+
- 'lib/fog/azurerm/identity/workflow_identity_client.rb'
|
|
217
|
+
|
|
218
|
+
# Offense count: 3
|
|
219
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
220
|
+
Style/FileNull:
|
|
221
|
+
Exclude:
|
|
222
|
+
- 'test/requests/storage/test_create_block_blob.rb'
|
|
223
|
+
- 'test/requests/storage/test_save_page_blob.rb'
|
|
224
|
+
|
|
225
|
+
# Offense count: 1
|
|
226
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
227
|
+
Style/FileWrite:
|
|
228
|
+
Exclude:
|
|
229
|
+
- 'test/integration/blob.rb'
|
|
230
|
+
|
|
231
|
+
# Offense count: 119
|
|
232
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
153
233
|
# Configuration parameters: EnforcedStyle.
|
|
154
234
|
# SupportedStyles: always, always_true, never
|
|
155
235
|
Style/FrozenStringLiteralComment:
|
|
156
236
|
Enabled: false
|
|
157
237
|
|
|
158
|
-
# Offense count:
|
|
159
|
-
#
|
|
238
|
+
# Offense count: 2
|
|
239
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
160
240
|
Style/IfUnlessModifier:
|
|
161
241
|
Exclude:
|
|
162
242
|
- 'lib/fog/azurerm/requests/storage/compare_container_blobs.rb'
|
|
163
243
|
- 'lib/fog/azurerm/requests/storage/wait_blob_copy_operation_to_finish.rb'
|
|
164
244
|
|
|
245
|
+
# Offense count: 2
|
|
246
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
247
|
+
Style/MapIntoArray:
|
|
248
|
+
Exclude:
|
|
249
|
+
- 'lib/fog/azurerm/models/storage/directories.rb'
|
|
250
|
+
- 'lib/fog/azurerm/models/storage/storage_accounts.rb'
|
|
251
|
+
|
|
165
252
|
# Offense count: 1
|
|
166
|
-
#
|
|
167
|
-
|
|
253
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
254
|
+
# Configuration parameters: AllowMethodComparison, ComparisonsThreshold.
|
|
255
|
+
Style/MultipleComparison:
|
|
168
256
|
Exclude:
|
|
169
|
-
- 'lib/fog/azurerm/models/
|
|
257
|
+
- 'lib/fog/azurerm/models/storage/file.rb'
|
|
258
|
+
|
|
259
|
+
# Offense count: 1
|
|
260
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
261
|
+
Style/NegatedIfElseCondition:
|
|
262
|
+
Exclude:
|
|
263
|
+
- 'lib/fog/azurerm/models/storage/storage_accounts.rb'
|
|
170
264
|
|
|
171
265
|
# Offense count: 1
|
|
172
|
-
#
|
|
266
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
173
267
|
# Configuration parameters: AllowedMethods.
|
|
174
268
|
# AllowedMethods: be, be_a, be_an, be_between, be_falsey, be_kind_of, be_instance_of, be_truthy, be_within, eq, eql, end_with, include, match, raise_error, respond_to, start_with
|
|
175
269
|
Style/NestedParenthesizedCalls:
|
|
@@ -177,8 +271,8 @@ Style/NestedParenthesizedCalls:
|
|
|
177
271
|
- 'test/test_helper.rb'
|
|
178
272
|
|
|
179
273
|
# Offense count: 1
|
|
180
|
-
#
|
|
181
|
-
# Configuration parameters:
|
|
274
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
275
|
+
# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
|
|
182
276
|
# SupportedStyles: predicate, comparison
|
|
183
277
|
Style/NumericPredicate:
|
|
184
278
|
Exclude:
|
|
@@ -186,13 +280,21 @@ Style/NumericPredicate:
|
|
|
186
280
|
- 'lib/fog/azurerm/requests/storage/get_blob.rb'
|
|
187
281
|
|
|
188
282
|
# Offense count: 2
|
|
283
|
+
# Configuration parameters: AllowedClasses.
|
|
284
|
+
Style/OneClassPerFile:
|
|
285
|
+
Exclude:
|
|
286
|
+
- 'test/test_helper.rb'
|
|
287
|
+
|
|
288
|
+
# Offense count: 3
|
|
289
|
+
# Configuration parameters: AllowedMethods.
|
|
290
|
+
# AllowedMethods: respond_to_missing?
|
|
189
291
|
Style/OptionalBooleanParameter:
|
|
190
292
|
Exclude:
|
|
191
293
|
- 'lib/fog/azurerm/async_response.rb'
|
|
192
294
|
- 'lib/fog/azurerm/utilities/general.rb'
|
|
193
295
|
|
|
194
|
-
# Offense count:
|
|
195
|
-
#
|
|
296
|
+
# Offense count: 33
|
|
297
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
196
298
|
# Configuration parameters: PreferredDelimiters.
|
|
197
299
|
Style/PercentLiteralDelimiters:
|
|
198
300
|
Exclude:
|
|
@@ -202,70 +304,74 @@ Style/PercentLiteralDelimiters:
|
|
|
202
304
|
- 'lib/fog/azurerm/requests/storage/copy_blob.rb'
|
|
203
305
|
- 'lib/fog/azurerm/requests/storage/copy_blob_from_uri.rb'
|
|
204
306
|
- 'test/api_stub/requests/storage/file.rb'
|
|
205
|
-
- 'test/test_helper.rb'
|
|
206
307
|
|
|
207
|
-
# Offense count:
|
|
208
|
-
#
|
|
209
|
-
# Configuration parameters: EnforcedStyle.
|
|
308
|
+
# Offense count: 46
|
|
309
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
310
|
+
# Configuration parameters: EnforcedStyle, AllowedCompactTypes.
|
|
210
311
|
# SupportedStyles: compact, exploded
|
|
211
312
|
Style/RaiseArgs:
|
|
212
313
|
Enabled: false
|
|
213
314
|
|
|
214
315
|
# Offense count: 1
|
|
215
|
-
#
|
|
316
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
216
317
|
Style/RedundantBegin:
|
|
217
318
|
Exclude:
|
|
218
319
|
- 'lib/fog/azurerm/storage.rb'
|
|
219
320
|
|
|
220
|
-
# Offense count:
|
|
221
|
-
#
|
|
321
|
+
# Offense count: 5
|
|
322
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
323
|
+
# Configuration parameters: AllowMultipleReturnValues.
|
|
324
|
+
Style/RedundantReturn:
|
|
325
|
+
Exclude:
|
|
326
|
+
- 'test/models/storage/test_files.rb'
|
|
327
|
+
- 'test/requests/storage/test_list_blobs.rb'
|
|
328
|
+
- 'test/requests/storage/test_list_containers.rb'
|
|
329
|
+
- 'test/requests/storage/test_save_page_blob.rb'
|
|
330
|
+
|
|
331
|
+
# Offense count: 9
|
|
332
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
222
333
|
# Configuration parameters: EnforcedStyle.
|
|
223
334
|
# SupportedStyles: implicit, explicit
|
|
224
335
|
Style/RescueStandardError:
|
|
225
336
|
Exclude:
|
|
226
337
|
- 'lib/fog/azurerm/models/storage/files.rb'
|
|
227
|
-
- 'lib/fog/azurerm/requests/storage/create_block_blob.rb'
|
|
228
|
-
- 'lib/fog/azurerm/requests/storage/multipart_save_block_blob.rb'
|
|
229
338
|
- 'lib/fog/azurerm/requests/storage/save_page_blob.rb'
|
|
230
339
|
- 'lib/fog/azurerm/requests/storage/wait_blob_copy_operation_to_finish.rb'
|
|
231
340
|
- 'test/integration/blob.rb'
|
|
232
341
|
- 'test/integration/container.rb'
|
|
233
|
-
- 'test/integration/storage_account.rb'
|
|
234
342
|
|
|
235
|
-
# Offense count:
|
|
236
|
-
#
|
|
237
|
-
# Configuration parameters:
|
|
238
|
-
|
|
239
|
-
Style/SafeNavigation:
|
|
343
|
+
# Offense count: 1
|
|
344
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
345
|
+
# Configuration parameters: AllowModifier.
|
|
346
|
+
Style/SoleNestedConditional:
|
|
240
347
|
Exclude:
|
|
241
|
-
- '
|
|
242
|
-
- 'test/integration/container.rb'
|
|
243
|
-
- 'test/integration/storage_account.rb'
|
|
348
|
+
- 'lib/fog/azurerm/async_response.rb'
|
|
244
349
|
|
|
245
350
|
# Offense count: 2
|
|
246
|
-
#
|
|
351
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
352
|
+
# Configuration parameters: Mode.
|
|
247
353
|
Style/StringConcatenation:
|
|
248
354
|
Exclude:
|
|
249
355
|
- 'test/integration/blob.rb'
|
|
250
356
|
|
|
251
|
-
# Offense count:
|
|
252
|
-
#
|
|
357
|
+
# Offense count: 10
|
|
358
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
253
359
|
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
|
254
360
|
# SupportedStyles: single_quotes, double_quotes
|
|
255
361
|
Style/StringLiterals:
|
|
256
362
|
Exclude:
|
|
257
363
|
- 'gitlab-fog-azure-rm.gemspec'
|
|
258
364
|
|
|
259
|
-
# Offense count:
|
|
260
|
-
#
|
|
365
|
+
# Offense count: 8
|
|
366
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
261
367
|
# Configuration parameters: MinSize.
|
|
262
368
|
# SupportedStyles: percent, brackets
|
|
263
369
|
Style/SymbolArray:
|
|
264
370
|
EnforcedStyle: brackets
|
|
265
371
|
|
|
266
|
-
# Offense count:
|
|
267
|
-
#
|
|
268
|
-
# Configuration parameters:
|
|
372
|
+
# Offense count: 194
|
|
373
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
374
|
+
# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
269
375
|
# URISchemes: http, https
|
|
270
376
|
Layout/LineLength:
|
|
271
|
-
Max:
|
|
377
|
+
Max: 201
|
data/CHANGELOG.md
CHANGED
data/gitlab-fog-azure-rm.gemspec
CHANGED
|
@@ -15,19 +15,20 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
spec.homepage = 'https://gitlab.com/gitlab-org/gitlab-fog-azure-rm'
|
|
16
16
|
spec.rdoc_options = %w[--charset=UTF-8]
|
|
17
17
|
spec.extra_rdoc_files = %w[README.md]
|
|
18
|
-
spec.required_ruby_version = '>= 2.
|
|
18
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
19
19
|
spec.post_install_message = 'Thanks for installing!'
|
|
20
20
|
spec.add_development_dependency 'codeclimate-test-reporter' , '~> 1.0.0'
|
|
21
|
-
spec.add_development_dependency 'minitest', '~> 5.
|
|
21
|
+
spec.add_development_dependency 'minitest', '~> 5.27'
|
|
22
22
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
23
|
-
spec.add_development_dependency 'rubocop', '~>
|
|
23
|
+
spec.add_development_dependency 'rubocop', '~> 1.86.0'
|
|
24
24
|
spec.add_development_dependency 'simplecov'
|
|
25
25
|
spec.add_development_dependency 'timecop'
|
|
26
26
|
spec.add_development_dependency 'webmock'
|
|
27
27
|
spec.add_development_dependency 'webrick', '~> 1.8'
|
|
28
28
|
|
|
29
|
+
spec.add_dependency 'base64'
|
|
29
30
|
spec.add_dependency 'faraday', "~> 2.0"
|
|
30
|
-
spec.add_dependency "faraday-follow_redirects", "~> 0.
|
|
31
|
+
spec.add_dependency "faraday-follow_redirects", "~> 0.5.0"
|
|
31
32
|
spec.add_dependency "faraday-net_http_persistent", "~> 2.0"
|
|
32
33
|
spec.add_dependency 'fog-core', '~> 2.1'
|
|
33
34
|
spec.add_dependency 'fog-json', '~> 1.2'
|
|
@@ -114,7 +114,9 @@ module Azure
|
|
|
114
114
|
# @return [String] a string with the canonicalized resource.
|
|
115
115
|
def canonicalized_resource(uri)
|
|
116
116
|
resource = '/' + account_name + (uri.path.empty? ? '/' : uri.path)
|
|
117
|
-
params =
|
|
117
|
+
params = URI.decode_www_form(uri.query.to_s)
|
|
118
|
+
.each_with_object({}) { |(key, value), hash| (hash[key] ||= []) << value }
|
|
119
|
+
.map { |k,v| [k.downcase, v] }
|
|
118
120
|
params.sort_by! { |k,_| k }
|
|
119
121
|
params.map! { |k,v| '%s:%s' % [k, v.map(&:strip).sort.join(',')] }
|
|
120
122
|
[resource, *params].join("\n")
|
|
@@ -18,9 +18,9 @@ module Fog
|
|
|
18
18
|
# This class provides the mock implementation for unit tests.
|
|
19
19
|
class Mock
|
|
20
20
|
def get_blob_url(_container_name, _blob_name, options = {})
|
|
21
|
-
url = 'https://mockaccount.blob.core.windows.net/test_container/test_blob'
|
|
21
|
+
url = +'https://mockaccount.blob.core.windows.net/test_container/test_blob'
|
|
22
22
|
url.sub!('https:', 'http:') if options[:scheme] == 'http'
|
|
23
|
-
url
|
|
23
|
+
url.freeze
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
end
|
|
@@ -24,9 +24,9 @@ module Fog
|
|
|
24
24
|
# This class provides the mock implementation for unit tests.
|
|
25
25
|
class Mock
|
|
26
26
|
def get_container_url(_container_name, options = {})
|
|
27
|
-
url = 'https://mockaccount.blob.core.windows.net/test_container?comp=list&restype=container'
|
|
27
|
+
url = +'https://mockaccount.blob.core.windows.net/test_container?comp=list&restype=container'
|
|
28
28
|
url.sub!('https:', 'http:') if options[:scheme] == 'http'
|
|
29
|
-
url
|
|
29
|
+
url.freeze
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
data/lib/fog/azurerm/storage.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'time'
|
|
4
5
|
|
|
5
6
|
module Fog
|
|
6
7
|
module AzureRM
|
|
@@ -87,6 +88,25 @@ module Fog
|
|
|
87
88
|
|
|
88
89
|
attr_accessor :options
|
|
89
90
|
|
|
91
|
+
# Immutable snapshot of a built SharedAccessSignature and the delegation
|
|
92
|
+
# key expiry it is valid until. It is published by a single reference
|
|
93
|
+
# assignment so signature_client can read it on a lock-free fast path.
|
|
94
|
+
# A nil expiry means the signature never expires (shared-key auth, which
|
|
95
|
+
# signs with the access key rather than a user delegation key).
|
|
96
|
+
class SignatureState
|
|
97
|
+
attr_reader :client, :expiry
|
|
98
|
+
|
|
99
|
+
def initialize(client, expiry)
|
|
100
|
+
@client = client
|
|
101
|
+
@expiry = expiry
|
|
102
|
+
freeze
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def covers?(requested_expiry)
|
|
106
|
+
expiry.nil? || expiry >= requested_expiry
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
90
110
|
def initialize(options)
|
|
91
111
|
begin
|
|
92
112
|
require 'azure/storage/common'
|
|
@@ -100,8 +120,8 @@ module Fog
|
|
|
100
120
|
raise e.message
|
|
101
121
|
end
|
|
102
122
|
|
|
103
|
-
@
|
|
104
|
-
@
|
|
123
|
+
@credential_mutex = Mutex.new
|
|
124
|
+
@signature_mutex = Mutex.new
|
|
105
125
|
|
|
106
126
|
options[:environment] = options[:environment] || ENV['AZURE_ENVIRONMENT'] || ENVIRONMENT_AZURE_CLOUD
|
|
107
127
|
@environment = options[:environment]
|
|
@@ -117,7 +137,7 @@ module Fog
|
|
|
117
137
|
@azure_storage_endpoint = options[:azure_storage_endpoint]
|
|
118
138
|
@azure_storage_domain = options[:azure_storage_domain]
|
|
119
139
|
|
|
120
|
-
|
|
140
|
+
@blob_client = build_blob_client(@azure_storage_token_signer)
|
|
121
141
|
end
|
|
122
142
|
|
|
123
143
|
private
|
|
@@ -142,62 +162,119 @@ module Fog
|
|
|
142
162
|
Azure::Storage::Common::Core::Auth::TokenSigner.new(cred)
|
|
143
163
|
end
|
|
144
164
|
|
|
165
|
+
# Returns a SharedAccessSignature for signing a URL that expires at
|
|
166
|
+
# requested_expiry.
|
|
167
|
+
#
|
|
168
|
+
# A user delegation key is valid for up to a week
|
|
169
|
+
# (MAX_USER_DELEGATION_KEY_SECONDS), so the built signature is cached in
|
|
170
|
+
# a frozen @signature_state snapshot and reused on a lock-free fast path.
|
|
171
|
+
# This keeps signing off the lock in the common case, so an in-flight
|
|
172
|
+
# credential or delegation-key refresh on another thread never stalls it.
|
|
173
|
+
#
|
|
174
|
+
# Only the slow path (no cached signature, or the cached delegation key
|
|
175
|
+
# no longer covers the requested expiry) takes @signature_mutex, where
|
|
176
|
+
# the delegation-key fetch and the snapshot rebuild happen atomically.
|
|
177
|
+
# Holding the lock there also collapses a burst of concurrent refetches
|
|
178
|
+
# into a single fetch.
|
|
145
179
|
def signature_client(requested_expiry)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
180
|
+
# Keep the blob client's credentials fresh without blocking signing;
|
|
181
|
+
# see maybe_refresh_credentials.
|
|
182
|
+
maybe_refresh_credentials
|
|
183
|
+
|
|
184
|
+
state = @signature_state
|
|
185
|
+
return state.client if state&.covers?(requested_expiry)
|
|
186
|
+
|
|
187
|
+
@signature_mutex.synchronize do
|
|
188
|
+
# Re-check: another thread may have rebuilt the snapshot while we
|
|
189
|
+
# were waiting for the lock.
|
|
190
|
+
state = @signature_state
|
|
191
|
+
return state.client if state&.covers?(requested_expiry)
|
|
192
|
+
|
|
193
|
+
delegation_key = fetch_user_delegation_key(requested_expiry)
|
|
194
|
+
client = Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(
|
|
195
|
+
@azure_storage_account_name,
|
|
196
|
+
@azure_storage_access_key.to_s,
|
|
197
|
+
delegation_key
|
|
198
|
+
)
|
|
199
|
+
@signature_state = SignatureState.new(client, @user_delegation_key_expiry)
|
|
200
|
+
client
|
|
153
201
|
end
|
|
154
|
-
|
|
155
|
-
@signature_client ||= Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(
|
|
156
|
-
@azure_storage_account_name,
|
|
157
|
-
access_key,
|
|
158
|
-
user_delegation_key
|
|
159
|
-
)
|
|
160
202
|
end
|
|
161
203
|
|
|
162
|
-
|
|
163
|
-
|
|
204
|
+
# Refreshes the credential-derived state (@credentials, the token signer
|
|
205
|
+
# and the blob client) when the access token is near expiry. Only the
|
|
206
|
+
# SAS URL requests reach this, through signature_client; the other blob
|
|
207
|
+
# operations read @blob_client directly and do not refresh, so this does
|
|
208
|
+
# not by itself keep their token fresh across rotation.
|
|
209
|
+
#
|
|
210
|
+
# The refresh is best-effort and non-blocking: a token fetch can be slow
|
|
211
|
+
# (or hang), so only one thread performs it at a time via a try-lock, and
|
|
212
|
+
# every other thread proceeds with the current, still-valid credentials
|
|
213
|
+
# rather than waiting. This keeps a slow token endpoint from stalling all
|
|
214
|
+
# callers that share the instance.
|
|
215
|
+
#
|
|
216
|
+
# The new signer and client are built into locals first and @credentials
|
|
217
|
+
# is published last. If building raises, @credentials is unchanged so
|
|
218
|
+
# refresh_needed? stays true and the next call retries, rather than
|
|
219
|
+
# leaving the swap half-applied with a stale signer or client.
|
|
220
|
+
def maybe_refresh_credentials
|
|
221
|
+
return unless @credential_client
|
|
222
|
+
|
|
223
|
+
current = @credentials
|
|
224
|
+
return unless current.nil? || current.refresh_needed?
|
|
225
|
+
return unless @credential_mutex.try_lock
|
|
226
|
+
|
|
227
|
+
begin
|
|
228
|
+
return unless @credentials.nil? || @credentials.refresh_needed?
|
|
164
229
|
|
|
165
|
-
if @credential_client
|
|
166
230
|
new_credentials = @credential_client.fetch_credentials_if_needed
|
|
167
|
-
|
|
231
|
+
return if new_credentials.nil? || new_credentials == @credentials
|
|
168
232
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
233
|
+
signer = access_token_signer(new_credentials.token)
|
|
234
|
+
client = build_blob_client(signer)
|
|
235
|
+
|
|
236
|
+
@azure_storage_token_signer = signer
|
|
237
|
+
@blob_client = client
|
|
238
|
+
@credentials = new_credentials
|
|
239
|
+
ensure
|
|
240
|
+
@credential_mutex.unlock
|
|
174
241
|
end
|
|
242
|
+
end
|
|
175
243
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
244
|
+
# Returns the cached user delegation key, fetching a new one when the
|
|
245
|
+
# cache is empty or no longer covers requested_expiry. Must be called
|
|
246
|
+
# while holding @signature_mutex. Returns nil for shared-key auth, where
|
|
247
|
+
# the SAS is signed with the access key instead.
|
|
248
|
+
def fetch_user_delegation_key(requested_expiry)
|
|
249
|
+
return nil unless @azure_storage_token_signer
|
|
250
|
+
|
|
251
|
+
if @user_delegation_key_expiry.nil? || @user_delegation_key_expiry < requested_expiry
|
|
252
|
+
start = Time.now
|
|
253
|
+
expiry = start + Azure::Storage::Blob::BlobConstants::MAX_USER_DELEGATION_KEY_SECONDS
|
|
254
|
+
|
|
255
|
+
@user_delegation_key = @blob_client.get_user_delegation_key(start, expiry)
|
|
256
|
+
# Cache against the expiry Azure granted (signed_expiry), not the one
|
|
257
|
+
# we requested: Azure uses its own clock and may return an earlier
|
|
258
|
+
# time, and keying on the request would over-claim validity and emit
|
|
259
|
+
# SAS URLs that Azure rejects.
|
|
260
|
+
@user_delegation_key_expiry = Time.parse(@user_delegation_key.signed_expiry)
|
|
187
261
|
end
|
|
188
262
|
|
|
189
263
|
@user_delegation_key
|
|
190
264
|
end
|
|
191
265
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
266
|
+
# Builds and returns a fully configured blob client. The caller publishes
|
|
267
|
+
# it with a single reference assignment (see initialize and
|
|
268
|
+
# maybe_refresh_credentials), so a concurrent reader of @blob_client never
|
|
269
|
+
# observes a client that is missing its filters mid-build.
|
|
270
|
+
def build_blob_client(signer)
|
|
271
|
+
azure_client = create_azure_client(signer)
|
|
272
|
+
azure_client.storage_blob_host = storage_blob_host
|
|
273
|
+
client = Azure::Storage::Blob::BlobService.new(client: azure_client, api_version: @api_version)
|
|
274
|
+
client.with_filter(Fog::AzureRM::IdentityEncodingFilter.new)
|
|
275
|
+
client.with_filter(Azure::Storage::Common::Core::Filter::ExponentialRetryPolicyFilter.new)
|
|
276
|
+
client.with_filter(Azure::Core::Http::DebugFilter.new) if @debug
|
|
277
|
+
client
|
|
201
278
|
end
|
|
202
279
|
|
|
203
280
|
def storage_blob_host
|
|
@@ -210,11 +287,11 @@ module Fog
|
|
|
210
287
|
end
|
|
211
288
|
end
|
|
212
289
|
|
|
213
|
-
def create_azure_client
|
|
290
|
+
def create_azure_client(signer)
|
|
214
291
|
Azure::Storage::Common::Client.create({
|
|
215
292
|
storage_account_name: @azure_storage_account_name,
|
|
216
293
|
storage_access_key: @azure_storage_access_key,
|
|
217
|
-
signer:
|
|
294
|
+
signer: signer
|
|
218
295
|
}.compact)
|
|
219
296
|
end
|
|
220
297
|
end
|
data/lib/fog/azurerm/version.rb
CHANGED
|
@@ -49,7 +49,7 @@ class TestGetBlob < Minitest::Test
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def test_get_blob_with_block_given_success
|
|
52
|
-
data = ''
|
|
52
|
+
data = +''
|
|
53
53
|
@blob_client.stub :get_blob_properties, @raw_cloud_blob do
|
|
54
54
|
@blob_client.stub :get_blob, @blob_with_content do
|
|
55
55
|
@service.get_blob('test_container', 'test_blob') do |chunk, _remaining_bytes, _total_bytes|
|
|
@@ -61,7 +61,7 @@ class TestGetBlob < Minitest::Test
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def test_get_blob_with_block_given_with_emtpy_blob_success
|
|
64
|
-
data = ''
|
|
64
|
+
data = +''
|
|
65
65
|
empty_raw_cloud_blob = @raw_cloud_blob
|
|
66
66
|
empty_raw_cloud_blob.properties[:content_length] = 0
|
|
67
67
|
empty_blob_with_content = [
|
|
@@ -79,7 +79,7 @@ class TestGetBlob < Minitest::Test
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
def test_get_blob_with_block_given_with_emtpy_range_success
|
|
82
|
-
data = ''
|
|
82
|
+
data = +''
|
|
83
83
|
options = {
|
|
84
84
|
start_range: 1024,
|
|
85
85
|
end_range: 1024
|
|
@@ -132,7 +132,7 @@ class TestGetBlob < Minitest::Test
|
|
|
132
132
|
end
|
|
133
133
|
|
|
134
134
|
def test_get_blob_with_block_given_mock
|
|
135
|
-
data = ''
|
|
135
|
+
data = +''
|
|
136
136
|
@mock_service.get_blob('test_container', 'test_blob') do |chunk, _remaining_bytes, _total_bytes|
|
|
137
137
|
data << chunk
|
|
138
138
|
end
|
|
@@ -118,18 +118,7 @@ class TestGetBlobHttpsUrl < Minitest::Test
|
|
|
118
118
|
|
|
119
119
|
requested_expiry = Time.now + 60
|
|
120
120
|
|
|
121
|
-
response =
|
|
122
|
-
<UserDelegationKey>
|
|
123
|
-
<SignedOid>f81d4fae-7dec-11d0-a765-00a0c91e6bf6</SignedOid>
|
|
124
|
-
<SignedTid>72f988bf-86f1-41af-91ab-2d7cd011db47</SignedTid>
|
|
125
|
-
<SignedStart>2024-09-19T00:00:00Z</SignedStart>
|
|
126
|
-
<SignedExpiry>2024-09-26T00:00:00Z</SignedExpiry>
|
|
127
|
-
<SignedService>b</SignedService>
|
|
128
|
-
<SignedVersion>2020-02-10</SignedVersion>
|
|
129
|
-
<Value>UDELEGATIONKEYXYZ....</Value>
|
|
130
|
-
<SignedKey>rL7...ABC</SignedKey>
|
|
131
|
-
</UserDelegationKey>
|
|
132
|
-
MSG
|
|
121
|
+
response = user_delegation_key_response
|
|
133
122
|
|
|
134
123
|
stub_request(:post, 'https://mockaccount.blob.core.windows.net?comp=userdelegationkey&restype=service')
|
|
135
124
|
.to_return(status: 200, headers: { 'Content-Type': 'application/xml' }, body: response)
|
|
@@ -159,29 +148,19 @@ class TestGetBlobHttpsUrl < Minitest::Test
|
|
|
159
148
|
|
|
160
149
|
ref_time = Time.now
|
|
161
150
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
151
|
+
# Each entry: [Time.now offset from ref_time, requested-expiry offset from
|
|
152
|
+
# that time, whether a fresh delegation key is expected]. A delegation key
|
|
153
|
+
# is valid for one WEEK from its start.
|
|
154
|
+
plan = [
|
|
155
|
+
[0, 1 * HOUR, true], # initial request
|
|
156
|
+
[5.5 * DAY, 1 * DAY, false], # second request during expiry window
|
|
157
|
+
[6.5 * DAY, 1 * DAY, true], # request extending past current expiry
|
|
158
|
+
[10.5 * DAY, 1 * DAY, false] # second request within new expiry
|
|
159
|
+
]
|
|
165
160
|
|
|
166
|
-
|
|
167
|
-
stubbed_times
|
|
168
|
-
|
|
169
|
-
expected_user_delegation_key_starts << stubbed_times.last
|
|
170
|
-
|
|
171
|
-
# second request during expiry window
|
|
172
|
-
stubbed_times << ref_time + 5.5 * DAY
|
|
173
|
-
requested_expiries << stubbed_times.last + 1 * DAY
|
|
174
|
-
# no additonal expected_user_delegation_key_starts
|
|
175
|
-
|
|
176
|
-
# request extending past current expiry
|
|
177
|
-
stubbed_times << ref_time + 6.5 * DAY
|
|
178
|
-
requested_expiries << stubbed_times.last + 1 * DAY
|
|
179
|
-
expected_user_delegation_key_starts << stubbed_times.last
|
|
180
|
-
|
|
181
|
-
# second request within new expiry
|
|
182
|
-
stubbed_times << ref_time + 10.5 * DAY
|
|
183
|
-
requested_expiries << stubbed_times.last + 1 * DAY
|
|
184
|
-
# no additional expected_user_delegation_key_starts
|
|
161
|
+
stubbed_times = plan.map { |now_offset,| ref_time + now_offset }
|
|
162
|
+
requested_expiries = stubbed_times.each_with_index.map { |time, i| time + plan[i][1] }
|
|
163
|
+
expected_user_delegation_key_starts = stubbed_times.select.with_index { |_, i| plan[i][2] }
|
|
185
164
|
|
|
186
165
|
user_delegation_key_starts = []
|
|
187
166
|
mock_user_delegation_key = lambda do |start, expiry|
|
|
@@ -190,7 +169,7 @@ class TestGetBlobHttpsUrl < Minitest::Test
|
|
|
190
169
|
|
|
191
170
|
key = Azure::Storage::Common::Service::UserDelegationKey.new
|
|
192
171
|
key.signed_start = "start-#{start.to_i}"
|
|
193
|
-
key.signed_expiry =
|
|
172
|
+
key.signed_expiry = expiry.utc.iso8601 # Azure grants the full requested window here
|
|
194
173
|
key.value = 'delegation-key'
|
|
195
174
|
key
|
|
196
175
|
end
|
|
@@ -201,7 +180,7 @@ class TestGetBlobHttpsUrl < Minitest::Test
|
|
|
201
180
|
end
|
|
202
181
|
|
|
203
182
|
requested_expiries.each do
|
|
204
|
-
mock_token_signer.expect(:sign, 'test-signature', [/\Ar\n.+test_blob\n.+\nstart-\d+\
|
|
183
|
+
mock_token_signer.expect(:sign, 'test-signature', [/\Ar\n.+test_blob\n.+\nstart-\d+\n\S+/m])
|
|
205
184
|
end
|
|
206
185
|
|
|
207
186
|
Time.stub :now, -> { stubbed_times.first } do
|
|
@@ -220,6 +199,42 @@ class TestGetBlobHttpsUrl < Minitest::Test
|
|
|
220
199
|
assert_equal expected_user_delegation_key_starts, user_delegation_key_starts
|
|
221
200
|
end
|
|
222
201
|
|
|
202
|
+
# Regression: the delegation-key cache keys on the expiry Azure granted
|
|
203
|
+
# (signed_expiry), not the requested one. Keying on the request would serve a
|
|
204
|
+
# cached key for a window Azure already ended, so this asserts a refetch.
|
|
205
|
+
def test_delegation_key_cache_uses_signed_expiry_not_requested
|
|
206
|
+
service = Fog::AzureRM::Storage.new(storage_account_credentials_with_token_signer)
|
|
207
|
+
blob_client = service.instance_variable_get(:@blob_client)
|
|
208
|
+
|
|
209
|
+
ref_time = Time.now
|
|
210
|
+
granted_expiry = (ref_time + 2 * DAY).utc # Azure grants far less than the 1-week request
|
|
211
|
+
|
|
212
|
+
starts = []
|
|
213
|
+
mock_key = lambda do |start, _expiry|
|
|
214
|
+
starts << start
|
|
215
|
+
key = Azure::Storage::Common::Service::UserDelegationKey.new
|
|
216
|
+
key.signed_start = "start-#{start.to_i}"
|
|
217
|
+
key.signed_expiry = granted_expiry.iso8601
|
|
218
|
+
key.value = 'delegation-key'
|
|
219
|
+
key
|
|
220
|
+
end
|
|
221
|
+
mock_new_signer = ->(_token) { mock_token_signer }
|
|
222
|
+
2.times { mock_token_signer.expect(:sign, 'test-signature', [String]) }
|
|
223
|
+
|
|
224
|
+
Time.stub :now, -> { ref_time } do
|
|
225
|
+
blob_client.stub :get_user_delegation_key, mock_key do
|
|
226
|
+
Azure::Core::Auth::Signer.stub :new, mock_new_signer do
|
|
227
|
+
service.get_blob_https_url('test_container', 'test_blob', ref_time + 1 * HOUR)
|
|
228
|
+
# Requested expiry is past Azure's granted window but within start+MAX.
|
|
229
|
+
service.get_blob_https_url('test_container', 'test_blob', ref_time + 3 * DAY)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
assert_equal 2, starts.length,
|
|
235
|
+
'expected a refetch once the requested expiry exceeded the granted signed_expiry'
|
|
236
|
+
end
|
|
237
|
+
|
|
223
238
|
def test_get_blob_https_url_mock
|
|
224
239
|
assert_equal "#{@url}?#{@token}", @mock_service.get_blob_https_url('test_container', 'test_blob', Time.now.utc + 3600)
|
|
225
240
|
assert_equal "#{@url}?#{@token}", @mock_service.get_object_url('test_container', 'test_blob', Time.now.utc + 3600)
|
|
@@ -247,18 +262,9 @@ class TestGetBlobHttpsUrl < Minitest::Test
|
|
|
247
262
|
)
|
|
248
263
|
|
|
249
264
|
# Setup user delegation key responses
|
|
250
|
-
delegation_response =
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
265
|
+
delegation_response = user_delegation_key_response(
|
|
266
|
+
value: 'UDELEGATIONKEY_INITIAL', signed_key: 'rL7...INITIAL'
|
|
267
|
+
)
|
|
262
268
|
|
|
263
269
|
stub_request(:post, 'https://mockaccount.blob.core.windows.net?comp=userdelegationkey&restype=service')
|
|
264
270
|
.to_return(
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
require File.expand_path '../../test_helper', __dir__
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'azure/storage/common'
|
|
4
|
+
require 'azure/storage/blob'
|
|
5
|
+
require 'azure/core/auth/signer'
|
|
6
|
+
|
|
7
|
+
# Tests for the concurrency guarantees of SAS signing. Each drives the public
|
|
8
|
+
# get_blob_https_url (which calls signature_client and maybe_refresh_credentials)
|
|
9
|
+
# and checks one property in isolation, so no private methods or stress loops
|
|
10
|
+
# are needed.
|
|
11
|
+
class TestStorageThreadSafety < Minitest::Test
|
|
12
|
+
CONTAINER = 'test_container'.freeze
|
|
13
|
+
BLOB = 'test_blob'.freeze
|
|
14
|
+
|
|
15
|
+
# A blob client stand-in that needs no network. Optionally parks on its first
|
|
16
|
+
# with_filter call so a test can inspect state while a rebuild is in progress.
|
|
17
|
+
class FakeBlobClient
|
|
18
|
+
def initialize(&on_first_filter)
|
|
19
|
+
@filters = []
|
|
20
|
+
@on_first_filter = on_first_filter
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_filter(filter)
|
|
24
|
+
@on_first_filter.call if @filters.empty? && @on_first_filter
|
|
25
|
+
@filters << filter
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def generate_uri(path, *)
|
|
30
|
+
URI.parse("https://mockaccount.blob.core.windows.net/#{path}")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_user_delegation_key(*)
|
|
34
|
+
key = Azure::Storage::Common::Service::UserDelegationKey.new
|
|
35
|
+
key.value = 'ZGVsZWdhdGlvbg==' # any string; Base64.decode64 is lenient
|
|
36
|
+
key.signed_expiry = '2999-12-31T23:59:59Z' # far future so the cached key covers the request
|
|
37
|
+
key
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Stands in for @signature_mutex and fails the test if the lock is taken.
|
|
42
|
+
class RaisingMutex
|
|
43
|
+
def synchronize
|
|
44
|
+
raise 'signature_client took the lock when it should have hit the cache'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def setup
|
|
49
|
+
@service = Fog::AzureRM::Storage.new(storage_account_credentials_with_token_signer)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def in_one_hour
|
|
53
|
+
Time.now.utc + 3600
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def credentials(expires_at)
|
|
57
|
+
Fog::AzureRM::Identity::Credentials.new('token', expires_at)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def credential_client(&fetch)
|
|
61
|
+
client = Object.new
|
|
62
|
+
client.define_singleton_method(:fetch_credentials_if_needed, &fetch)
|
|
63
|
+
client
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# A cached signature that still covers the requested expiry is returned
|
|
67
|
+
# without taking @signature_mutex, so signing never waits on a refresh.
|
|
68
|
+
def test_cached_signature_is_returned_without_locking
|
|
69
|
+
@service.instance_variable_set(:@blob_client, FakeBlobClient.new)
|
|
70
|
+
@service.get_blob_https_url(CONTAINER, BLOB, in_one_hour) # builds and caches it
|
|
71
|
+
|
|
72
|
+
@service.instance_variable_set(:@signature_mutex, RaisingMutex.new)
|
|
73
|
+
|
|
74
|
+
url = @service.get_blob_https_url(CONTAINER, BLOB, in_one_hour)
|
|
75
|
+
assert_match %r{\Ahttps://mockaccount\.blob\.core\.windows\.net/#{CONTAINER}/#{BLOB}\?}, url
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# While one thread is stuck in a slow token fetch, another caller must still
|
|
79
|
+
# return instead of blocking on the credential lock.
|
|
80
|
+
def test_a_slow_credential_refresh_does_not_block_other_callers
|
|
81
|
+
entered_fetch = Queue.new
|
|
82
|
+
finish_fetch = Queue.new
|
|
83
|
+
fresh = credentials(Time.now + 3600)
|
|
84
|
+
|
|
85
|
+
slow_client = credential_client do
|
|
86
|
+
entered_fetch << true
|
|
87
|
+
finish_fetch.pop # simulate a slow or hanging token endpoint
|
|
88
|
+
fresh
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Azure::Storage::Blob::BlobService.stub(:new, ->(*, **) { FakeBlobClient.new }) do
|
|
92
|
+
@service.instance_variable_set(:@blob_client, FakeBlobClient.new)
|
|
93
|
+
@service.instance_variable_set(:@credential_client, slow_client)
|
|
94
|
+
@service.instance_variable_set(:@credentials, credentials(Time.now - 1))
|
|
95
|
+
|
|
96
|
+
refreshing = Thread.new { @service.get_blob_https_url(CONTAINER, BLOB, in_one_hour) }
|
|
97
|
+
entered_fetch.pop # the refresh now holds @credential_mutex inside the fetch
|
|
98
|
+
|
|
99
|
+
other = Thread.new { @service.get_blob_https_url(CONTAINER, BLOB, in_one_hour) }
|
|
100
|
+
returned = other.join(5)
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
refute_nil returned, 'a second caller blocked while one thread was refreshing credentials'
|
|
104
|
+
ensure
|
|
105
|
+
finish_fetch << true
|
|
106
|
+
[refreshing, other].each(&:join)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# A rebuilt blob client is published only once it is fully built, so a
|
|
112
|
+
# concurrent reader never sees one that is missing its filters.
|
|
113
|
+
def test_rebuilt_blob_client_is_published_only_when_fully_built
|
|
114
|
+
original = @service.instance_variable_get(:@blob_client)
|
|
115
|
+
fresh = credentials(Time.now + 3600)
|
|
116
|
+
@service.instance_variable_set(:@credential_client, credential_client { fresh })
|
|
117
|
+
@service.instance_variable_set(:@credentials, credentials(Time.now - 1))
|
|
118
|
+
|
|
119
|
+
mid_build = Queue.new
|
|
120
|
+
resume = Queue.new
|
|
121
|
+
half_built = FakeBlobClient.new do
|
|
122
|
+
mid_build << true
|
|
123
|
+
resume.pop
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
Azure::Storage::Blob::BlobService.stub(:new, ->(*, **) { half_built }) do
|
|
127
|
+
builder = Thread.new { @service.get_blob_https_url(CONTAINER, BLOB, in_one_hour) }
|
|
128
|
+
mid_build.pop # the new client exists but its filters are not applied yet
|
|
129
|
+
observed = @service.instance_variable_get(:@blob_client)
|
|
130
|
+
resume << true
|
|
131
|
+
builder.join
|
|
132
|
+
|
|
133
|
+
refute_same half_built, observed, 'the half-built client was published before its filters were applied'
|
|
134
|
+
assert_same original, observed
|
|
135
|
+
assert_same half_built, @service.instance_variable_get(:@blob_client)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -79,6 +79,21 @@ def mock_token_signer
|
|
|
79
79
|
@mock_token_signer ||= Minitest::Mock.new(Azure::Core::Auth::Signer.new('access-token'))
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
def user_delegation_key_response(value: 'UDELEGATIONKEYXYZ....', signed_key: 'rL7...ABC')
|
|
83
|
+
<<~MSG
|
|
84
|
+
<UserDelegationKey>
|
|
85
|
+
<SignedOid>f81d4fae-7dec-11d0-a765-00a0c91e6bf6</SignedOid>
|
|
86
|
+
<SignedTid>72f988bf-86f1-41af-91ab-2d7cd011db47</SignedTid>
|
|
87
|
+
<SignedStart>2024-09-19T00:00:00Z</SignedStart>
|
|
88
|
+
<SignedExpiry>2024-09-26T00:00:00Z</SignedExpiry>
|
|
89
|
+
<SignedService>b</SignedService>
|
|
90
|
+
<SignedVersion>2020-02-10</SignedVersion>
|
|
91
|
+
<Value>#{value}</Value>
|
|
92
|
+
<SignedKey>#{signed_key}</SignedKey>
|
|
93
|
+
</UserDelegationKey>
|
|
94
|
+
MSG
|
|
95
|
+
end
|
|
96
|
+
|
|
82
97
|
# Mock Class for Blob
|
|
83
98
|
class MockBlob
|
|
84
99
|
def initialize
|
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.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shaffan Chaudhry
|
|
@@ -18,7 +18,7 @@ authors:
|
|
|
18
18
|
autorequire:
|
|
19
19
|
bindir: bin
|
|
20
20
|
cert_chain: []
|
|
21
|
-
date:
|
|
21
|
+
date: 2026-09-10 00:00:00.000000000 Z
|
|
22
22
|
dependencies:
|
|
23
23
|
- !ruby/object:Gem::Dependency
|
|
24
24
|
name: codeclimate-test-reporter
|
|
@@ -40,14 +40,14 @@ dependencies:
|
|
|
40
40
|
requirements:
|
|
41
41
|
- - "~>"
|
|
42
42
|
- !ruby/object:Gem::Version
|
|
43
|
-
version: 5.
|
|
43
|
+
version: '5.27'
|
|
44
44
|
type: :development
|
|
45
45
|
prerelease: false
|
|
46
46
|
version_requirements: !ruby/object:Gem::Requirement
|
|
47
47
|
requirements:
|
|
48
48
|
- - "~>"
|
|
49
49
|
- !ruby/object:Gem::Version
|
|
50
|
-
version: 5.
|
|
50
|
+
version: '5.27'
|
|
51
51
|
- !ruby/object:Gem::Dependency
|
|
52
52
|
name: rake
|
|
53
53
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -68,14 +68,14 @@ dependencies:
|
|
|
68
68
|
requirements:
|
|
69
69
|
- - "~>"
|
|
70
70
|
- !ruby/object:Gem::Version
|
|
71
|
-
version:
|
|
71
|
+
version: 1.86.0
|
|
72
72
|
type: :development
|
|
73
73
|
prerelease: false
|
|
74
74
|
version_requirements: !ruby/object:Gem::Requirement
|
|
75
75
|
requirements:
|
|
76
76
|
- - "~>"
|
|
77
77
|
- !ruby/object:Gem::Version
|
|
78
|
-
version:
|
|
78
|
+
version: 1.86.0
|
|
79
79
|
- !ruby/object:Gem::Dependency
|
|
80
80
|
name: simplecov
|
|
81
81
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -132,6 +132,20 @@ dependencies:
|
|
|
132
132
|
- - "~>"
|
|
133
133
|
- !ruby/object:Gem::Version
|
|
134
134
|
version: '1.8'
|
|
135
|
+
- !ruby/object:Gem::Dependency
|
|
136
|
+
name: base64
|
|
137
|
+
requirement: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: '0'
|
|
142
|
+
type: :runtime
|
|
143
|
+
prerelease: false
|
|
144
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
145
|
+
requirements:
|
|
146
|
+
- - ">="
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: '0'
|
|
135
149
|
- !ruby/object:Gem::Dependency
|
|
136
150
|
name: faraday
|
|
137
151
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -152,14 +166,14 @@ dependencies:
|
|
|
152
166
|
requirements:
|
|
153
167
|
- - "~>"
|
|
154
168
|
- !ruby/object:Gem::Version
|
|
155
|
-
version: 0.
|
|
169
|
+
version: 0.5.0
|
|
156
170
|
type: :runtime
|
|
157
171
|
prerelease: false
|
|
158
172
|
version_requirements: !ruby/object:Gem::Requirement
|
|
159
173
|
requirements:
|
|
160
174
|
- - "~>"
|
|
161
175
|
- !ruby/object:Gem::Version
|
|
162
|
-
version: 0.
|
|
176
|
+
version: 0.5.0
|
|
163
177
|
- !ruby/object:Gem::Dependency
|
|
164
178
|
name: faraday-net_http_persistent
|
|
165
179
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -462,6 +476,7 @@ files:
|
|
|
462
476
|
- test/requests/storage/test_release_blob_lease.rb
|
|
463
477
|
- test/requests/storage/test_release_container_lease.rb
|
|
464
478
|
- test/requests/storage/test_save_page_blob.rb
|
|
479
|
+
- test/requests/storage/test_storage_thread_safety.rb
|
|
465
480
|
- test/requests/storage/test_wait_blob_copy_operation_to_finish.rb
|
|
466
481
|
- test/test_helper.rb
|
|
467
482
|
- test/unit/test_credentials.rb
|
|
@@ -482,7 +497,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
482
497
|
requirements:
|
|
483
498
|
- - ">="
|
|
484
499
|
- !ruby/object:Gem::Version
|
|
485
|
-
version: 2.
|
|
500
|
+
version: 3.2.0
|
|
486
501
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
487
502
|
requirements:
|
|
488
503
|
- - ">="
|