azure-armrest 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_local.yml +3 -0
- data/CHANGES +3 -0
- data/lib/azure/armrest/armrest_collection.rb +2 -2
- data/lib/azure/armrest/armrest_service.rb +3 -3
- data/lib/azure/armrest/model/base_model.rb +83 -11
- data/lib/azure/armrest/model/storage_account.rb +35 -12
- data/lib/azure/armrest/resource_group_based_service.rb +7 -2
- data/lib/azure/armrest/storage_account_service.rb +26 -20
- data/lib/azure/armrest/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 271d67cb70166d8d00b30c234d1ecb6f26cfaaf5
|
4
|
+
data.tar.gz: adfdb6724dd8ca45b8314ba48537ff2ad229f469
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae90f2ed141868131b93df713ddae8e54b559ffd503da223fc3d6a9daad01cb3bf1b14d0c87e6ffc54156933407b93268d964dadf65b5a22cadfa1831a755ced
|
7
|
+
data.tar.gz: 4a0da229be85bd145421d215cfa064e7cebf745fe754b058f9d2a54fc2c0140ffff3492854877168e6b815b304dad0aa72c8588582e6dd990e98f7775e1981ff
|
data/.rubocop_local.yml
CHANGED
data/CHANGES
CHANGED
@@ -16,9 +16,9 @@ module Azure
|
|
16
16
|
# using +klass+ to generate the list elements. In addition, both the
|
17
17
|
# response headers and continuation token are set.
|
18
18
|
#
|
19
|
-
def create_from_response(response, klass = nil)
|
19
|
+
def create_from_response(response, klass = nil, skip_accessors_definition = false)
|
20
20
|
json_response = JSON.parse(response)
|
21
|
-
array = new(json_response['value'].map { |hash| klass.new(hash) })
|
21
|
+
array = new(json_response['value'].map { |hash| klass.new(hash, skip_accessors_definition) })
|
22
22
|
|
23
23
|
array.response_code = response.code
|
24
24
|
array.response_headers = response.headers
|
@@ -357,13 +357,13 @@ module Azure
|
|
357
357
|
end
|
358
358
|
|
359
359
|
# Make additional calls and concatenate the results if a continuation URL is found.
|
360
|
-
def get_all_results(response)
|
361
|
-
results = Azure::Armrest::ArmrestCollection.create_from_response(response, model_class)
|
360
|
+
def get_all_results(response, skip_accessors_definition = false)
|
361
|
+
results = Azure::Armrest::ArmrestCollection.create_from_response(response, model_class, skip_accessors_definition)
|
362
362
|
nextlink = results.next_link
|
363
363
|
|
364
364
|
while nextlink
|
365
365
|
response = rest_get_without_encoding(nextlink)
|
366
|
-
more = Azure::Armrest::ArmrestCollection.create_from_response(response, model_class)
|
366
|
+
more = Azure::Armrest::ArmrestCollection.create_from_response(response, model_class, skip_accessors_definition)
|
367
367
|
results.concat(more)
|
368
368
|
nextlink = more.next_link
|
369
369
|
end
|
@@ -23,6 +23,58 @@ module Azure
|
|
23
23
|
|
24
24
|
attr_hash :tags
|
25
25
|
|
26
|
+
# Defines attr_reader methods for the given set of attributes and
|
27
|
+
# expected hash key. Used to define methods that can be used internally
|
28
|
+
# that avoid needing to use methods defined from
|
29
|
+
# `add_accessor_methods`/`__setobj__`
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
# class Vm < Azure::ArmRest::BaseModel
|
33
|
+
# attr_from_hash :name => :Name
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# json_string = {'name' => 'Deathstar'}
|
37
|
+
#
|
38
|
+
# vm = Vm.new(json_string)
|
39
|
+
# vm.name_from_hash
|
40
|
+
# #=> "Deathstar"
|
41
|
+
#
|
42
|
+
# # If the attr_from_hash can also support multiple attrs in a single
|
43
|
+
# # call, and nested params
|
44
|
+
#
|
45
|
+
# class Host < Azure::ArmRest::BaseModel
|
46
|
+
# attr_from_hash :name => :Name,
|
47
|
+
# :address => [:Properties, :ipAddress],
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# json_string = {'name' => 'Hoth', :Properties => {:ipAddress => '123.123.123.123'}}
|
51
|
+
#
|
52
|
+
# host = Host.new(json_string)
|
53
|
+
# host.name_from_hash
|
54
|
+
# #=> "Hoth"
|
55
|
+
# host.address_from_hash
|
56
|
+
# #=> "123.123.123.123"
|
57
|
+
#
|
58
|
+
def self.attr_from_hash(attrs = {})
|
59
|
+
file, line, _ = caller.first.split(":")
|
60
|
+
attrs.each do |attr_name, keys|
|
61
|
+
keys = Array(keys)
|
62
|
+
first_key = keys.shift
|
63
|
+
method_def = [
|
64
|
+
"def #{attr_name}_from_hash",
|
65
|
+
" return @#{attr_name}_from_hash if defined?(@#{attr_name}_from_hash)",
|
66
|
+
" @#{attr_name}_from_hash = __getobj__[:#{first_key}] || __getobj__[\"#{first_key}\"]",
|
67
|
+
"end"
|
68
|
+
]
|
69
|
+
keys.each do |hash_key|
|
70
|
+
method_def.insert(-2, " @#{attr_name}_from_hash = @#{attr_name}_from_hash[:#{hash_key}] || @#{attr_name}_from_hash[\"#{hash_key}\"]")
|
71
|
+
end
|
72
|
+
class_eval(method_def.join("; "), file, line.to_i)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private_class_method :attr_from_hash
|
77
|
+
|
26
78
|
attr_accessor :response_headers
|
27
79
|
attr_accessor :response_code
|
28
80
|
|
@@ -48,7 +100,7 @@ module Azure
|
|
48
100
|
# # Or you can get back the original JSON if necessary.
|
49
101
|
# person.to_json # => Returns original JSON
|
50
102
|
#
|
51
|
-
def initialize(json)
|
103
|
+
def initialize(json, skip_accessors_definition = false)
|
52
104
|
# Find the exclusion list for the model of next level (@embed_model)
|
53
105
|
# '#' is the separator between levels. Remove attributes
|
54
106
|
# before the first separator.
|
@@ -63,15 +115,24 @@ module Azure
|
|
63
115
|
@json = json
|
64
116
|
end
|
65
117
|
|
66
|
-
|
118
|
+
@hashobj = @hash.dup
|
119
|
+
__setobj__ unless skip_accessors_definition
|
67
120
|
end
|
68
121
|
|
69
122
|
def resource_group
|
70
|
-
@resource_group ||=
|
123
|
+
@resource_group ||= begin
|
124
|
+
id_from_hash[/resourcegroups\/(.*?[^\/]+)?/i, 1]
|
125
|
+
rescue
|
126
|
+
nil
|
127
|
+
end
|
71
128
|
end
|
72
129
|
|
73
130
|
def subscription_id
|
74
|
-
@subscription_id ||=
|
131
|
+
@subscription_id ||= begin
|
132
|
+
id_from_hash[/subscriptions\/(.*?[^\/]+)?/i, 1]
|
133
|
+
rescue
|
134
|
+
nil
|
135
|
+
end
|
75
136
|
end
|
76
137
|
|
77
138
|
attr_writer :resource_group
|
@@ -148,21 +209,29 @@ module Azure
|
|
148
209
|
@hashobj
|
149
210
|
end
|
150
211
|
|
212
|
+
# Do not use this method directly.
|
213
|
+
#
|
214
|
+
# Will only attempt to fetch the id from the @hashobj once, so even it it
|
215
|
+
# is nil, it will cache that value, and return that on subsequent calls.
|
216
|
+
def id_from_hash
|
217
|
+
return @id_from_hash if defined?(@id_from_hash)
|
218
|
+
@id_from_hash = __getobj__[:id] || __getobj__["id"]
|
219
|
+
end
|
220
|
+
|
151
221
|
# Create snake_case accessor methods for all hash attributes
|
152
222
|
# Use _alias if an accessor conflicts with existing methods
|
153
|
-
def __setobj__
|
154
|
-
@hashobj = obj
|
223
|
+
def __setobj__
|
155
224
|
excl_list = self.class.send(:excl_list)
|
156
|
-
|
225
|
+
@hashobj.each do |key, value|
|
157
226
|
snake = key.to_s.tr(' ', '_').underscore
|
158
227
|
snake.tr!('.', '_')
|
159
228
|
|
160
229
|
unless excl_list.include?(snake) # Must deal with nested models
|
161
230
|
if value.kind_of?(Array)
|
162
231
|
newval = value.map { |elem| elem.kind_of?(Hash) ? nested_object(snake.camelize.singularize, elem) : elem }
|
163
|
-
|
232
|
+
@hashobj[key] = newval
|
164
233
|
elsif value.kind_of?(Hash)
|
165
|
-
|
234
|
+
@hashobj[key] = nested_object(snake.camelize, value)
|
166
235
|
end
|
167
236
|
end
|
168
237
|
|
@@ -212,8 +281,11 @@ module Azure
|
|
212
281
|
|
213
282
|
class StorageAccount < BaseModel; end
|
214
283
|
class StorageAccountKey < StorageAccount
|
215
|
-
|
216
|
-
|
284
|
+
attr_from_hash :key_name => :keyName,
|
285
|
+
:value => :value
|
286
|
+
|
287
|
+
def key1; key_name_from_hash == 'key1' ? value_from_hash : nil; end
|
288
|
+
def key2; key_name_from_hash == 'key2' ? value_from_hash : nil; end
|
217
289
|
def key; key1 || key2; end
|
218
290
|
end
|
219
291
|
|
@@ -5,10 +5,18 @@ require 'nokogiri'
|
|
5
5
|
module Azure
|
6
6
|
module Armrest
|
7
7
|
class StorageAccount < BaseModel
|
8
|
+
attr_from_hash :name => :name,
|
9
|
+
:blob_endpoint => [:properties, :primaryEndpoints, :blob]
|
10
|
+
|
8
11
|
# Classes used to wrap container and blob information.
|
9
|
-
class Container < BaseModel
|
12
|
+
class Container < BaseModel
|
13
|
+
attr_from_hash :name => :Name
|
14
|
+
end
|
10
15
|
class ContainerProperty < BaseModel; end
|
11
|
-
class Blob < BaseModel
|
16
|
+
class Blob < BaseModel
|
17
|
+
attr_from_hash :name => :Name,
|
18
|
+
:lease_state => [:Properties, :LeaseState]
|
19
|
+
end
|
12
20
|
class BlobProperty < BaseModel; end
|
13
21
|
class PrivateImage < BlobProperty; end
|
14
22
|
class BlobServiceProperty < BaseModel; end
|
@@ -34,7 +42,7 @@ module Azure
|
|
34
42
|
# The parent configuration object
|
35
43
|
attr_accessor :configuration
|
36
44
|
|
37
|
-
def initialize(json)
|
45
|
+
def initialize(json, skip_accessors_definition = false)
|
38
46
|
super
|
39
47
|
@storage_api_version = '2016-05-31'
|
40
48
|
end
|
@@ -424,14 +432,20 @@ module Azure
|
|
424
432
|
raise ArgumentError, "No access key specified" unless key
|
425
433
|
|
426
434
|
query = "comp=list"
|
427
|
-
|
435
|
+
skip_defs = options[:skip_accessors_definition]
|
436
|
+
|
437
|
+
options.each do |okey, ovalue|
|
438
|
+
unless okey == :skip_accessors_definition
|
439
|
+
query += "&#{okey}=#{[ovalue].flatten.join(',')}"
|
440
|
+
end
|
441
|
+
end
|
428
442
|
|
429
443
|
response = blob_response(key, query)
|
430
444
|
|
431
445
|
doc = Nokogiri::XML(response.body)
|
432
446
|
|
433
447
|
results = doc.xpath('//Containers/Container').collect do |element|
|
434
|
-
Container.new(Hash.from_xml(element.to_s)['Container'])
|
448
|
+
Container.new(Hash.from_xml(element.to_s)['Container'], skip_defs)
|
435
449
|
end
|
436
450
|
|
437
451
|
results.concat(next_marker_results(doc, :containers, key, options))
|
@@ -468,7 +482,7 @@ module Azure
|
|
468
482
|
def blob_properties(container, blob, key = access_key, options = {})
|
469
483
|
raise ArgumentError, "No access key specified" unless key
|
470
484
|
|
471
|
-
url = File.join(
|
485
|
+
url = File.join(blob_endpoint_from_hash, container, blob)
|
472
486
|
url += "?snapshot=" + options[:date] if options[:date]
|
473
487
|
|
474
488
|
headers = build_headers(url, key, :blob, :verb => 'HEAD')
|
@@ -482,7 +496,7 @@ module Azure
|
|
482
496
|
:ssl_verify => configuration.ssl_verify
|
483
497
|
)
|
484
498
|
|
485
|
-
BlobProperty.new(response.headers.merge(:container => container, :name => blob))
|
499
|
+
BlobProperty.new(response.headers.merge(:container => container, :name => blob), options[:skip_accessors_definition])
|
486
500
|
end
|
487
501
|
|
488
502
|
# Update the given +blob+ in +container+ with the provided options. The
|
@@ -559,7 +573,13 @@ module Azure
|
|
559
573
|
raise ArgumentError, "No access key specified" unless key
|
560
574
|
|
561
575
|
query = "restype=container&comp=list"
|
562
|
-
|
576
|
+
skip_defs = options[:skip_accessors_definition]
|
577
|
+
|
578
|
+
options.each do |okey, ovalue|
|
579
|
+
unless okey == :skip_accessors_definition
|
580
|
+
query += "&#{okey}=#{[ovalue].flatten.join(',')}"
|
581
|
+
end
|
582
|
+
end
|
563
583
|
|
564
584
|
response = blob_response(key, query, container)
|
565
585
|
|
@@ -567,7 +587,7 @@ module Azure
|
|
567
587
|
|
568
588
|
results = doc.xpath('//Blobs/Blob').collect do |node|
|
569
589
|
hash = Hash.from_xml(node.to_s)['Blob'].merge(:container => container)
|
570
|
-
hash.key?('Snapshot') ? BlobSnapshot.new(hash) : Blob.new(hash)
|
590
|
+
hash.key?('Snapshot') ? BlobSnapshot.new(hash, skip_defs) : Blob.new(hash, skip_defs)
|
571
591
|
end
|
572
592
|
|
573
593
|
results.concat(next_marker_results(doc, :blobs, container, key, options))
|
@@ -582,10 +602,13 @@ module Azure
|
|
582
602
|
|
583
603
|
array = []
|
584
604
|
mutex = Mutex.new
|
605
|
+
opts = {
|
606
|
+
:skip_accessors_definition => options[:skip_accessors_definition]
|
607
|
+
}
|
585
608
|
|
586
|
-
Parallel.each(containers(key), :in_threads => max_threads) do |container|
|
609
|
+
Parallel.each(containers(key, opts), :in_threads => max_threads) do |container|
|
587
610
|
begin
|
588
|
-
mutex.synchronize { array.concat(blobs(container.
|
611
|
+
mutex.synchronize { array.concat(blobs(container.name_from_hash, key, options)) }
|
589
612
|
rescue Errno::ECONNREFUSED, Azure::Armrest::TimeoutException => err
|
590
613
|
msg = "Unable to gather blob information for #{container.name}: #{err}"
|
591
614
|
Azure::Armrest::Configuration.log.try(:log, Logger::WARN, msg)
|
@@ -938,7 +961,7 @@ module Azure
|
|
938
961
|
# the url and submit an http request.
|
939
962
|
#
|
940
963
|
def blob_response(key, query, *args)
|
941
|
-
url = File.join(
|
964
|
+
url = File.join(blob_endpoint_from_hash, *args) + "?#{query}"
|
942
965
|
headers = build_headers(url, key, 'blob')
|
943
966
|
|
944
967
|
ArmrestService.send(
|
@@ -97,8 +97,9 @@ module Azure
|
|
97
97
|
url = build_url
|
98
98
|
url = yield(url) || url if block_given?
|
99
99
|
|
100
|
+
skip_accessors_definition = filter.delete(:skip_accessors_definition) || false
|
100
101
|
response = rest_get(url)
|
101
|
-
results = get_all_results(response)
|
102
|
+
results = get_all_results(response, skip_accessors_definition)
|
102
103
|
|
103
104
|
if filter.empty?
|
104
105
|
results
|
@@ -106,7 +107,11 @@ module Azure
|
|
106
107
|
results.select do |obj|
|
107
108
|
filter.all? do |method_name, value|
|
108
109
|
if value.kind_of?(String)
|
109
|
-
|
110
|
+
if skip_accessors_definition
|
111
|
+
obj[method_name.to_s].casecmp(value).zero?
|
112
|
+
else
|
113
|
+
obj.public_send(method_name).casecmp(value).zero?
|
114
|
+
end
|
110
115
|
else
|
111
116
|
obj.public_send(method_name) == value
|
112
117
|
end
|
@@ -104,7 +104,7 @@ module Azure
|
|
104
104
|
#
|
105
105
|
# If you want a plain hash, use the list_account_keys method instead.
|
106
106
|
#
|
107
|
-
def list_account_key_objects(account_name, group = configuration.resource_group)
|
107
|
+
def list_account_key_objects(account_name, group = configuration.resource_group, skip_accessors_definition = false)
|
108
108
|
validate_resource_group(group)
|
109
109
|
|
110
110
|
unless recent_api_version?
|
@@ -113,7 +113,7 @@ module Azure
|
|
113
113
|
|
114
114
|
url = build_url(group, account_name, 'listKeys')
|
115
115
|
response = rest_post(url)
|
116
|
-
JSON.parse(response.body)['keys'].map { |hash| StorageAccountKey.new(hash) }
|
116
|
+
JSON.parse(response.body)['keys'].map { |hash| StorageAccountKey.new(hash, skip_accessors_definition) }
|
117
117
|
end
|
118
118
|
|
119
119
|
alias list_storage_account_key_objects list_account_key_objects
|
@@ -172,7 +172,7 @@ module Azure
|
|
172
172
|
# Note that for string values the comparison is caseless.
|
173
173
|
#
|
174
174
|
def list_all_private_images(filter = {})
|
175
|
-
storage_accounts = list_all(filter)
|
175
|
+
storage_accounts = list_all(filter.merge(:skip_accessors_definition => true))
|
176
176
|
get_private_images(storage_accounts)
|
177
177
|
end
|
178
178
|
|
@@ -187,7 +187,7 @@ module Azure
|
|
187
187
|
# sas.list_private_images(your_resource_group)
|
188
188
|
#
|
189
189
|
def list_private_images(group = configuration.resource_group)
|
190
|
-
storage_accounts = list(group)
|
190
|
+
storage_accounts = list(group, true)
|
191
191
|
get_private_images(storage_accounts)
|
192
192
|
end
|
193
193
|
|
@@ -273,30 +273,36 @@ module Azure
|
|
273
273
|
|
274
274
|
Parallel.each(storage_accounts, :in_threads => configuration.max_threads) do |storage_account|
|
275
275
|
begin
|
276
|
-
key = get_account_key(storage_account)
|
276
|
+
key = get_account_key(storage_account, true)
|
277
277
|
rescue Azure::Armrest::ApiException
|
278
278
|
next # Most likely due to incomplete or failed provisioning.
|
279
279
|
else
|
280
280
|
storage_account.access_key = key
|
281
281
|
end
|
282
282
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
next unless blob.
|
283
|
+
init_opts = { :skip_accessors_definition => true }
|
284
|
+
storage_account.containers(storage_account.access_key, init_opts).each do |container|
|
285
|
+
next if container.name_from_hash =~ /^bootdiagnostics/i
|
286
|
+
storage_account.blobs(container.name_from_hash, storage_account.access_key, init_opts).each do |blob|
|
287
|
+
next unless File.extname(blob.name_from_hash).casecmp('.vhd').zero?
|
288
|
+
next unless blob.lease_state_from_hash.casecmp('available').zero?
|
288
289
|
|
289
290
|
# In rare cases the endpoint will be unreachable. Warn and move on.
|
290
291
|
begin
|
291
|
-
blob_properties = storage_account.blob_properties(
|
292
|
+
blob_properties = storage_account.blob_properties(
|
293
|
+
blob[:container],
|
294
|
+
blob.name_from_hash,
|
295
|
+
storage_account.access_key,
|
296
|
+
:skip_accessors_definition => true
|
297
|
+
)
|
292
298
|
rescue Errno::ECONNREFUSED, Azure::Armrest::TimeoutException => err
|
293
299
|
msg = "Unable to collect blob properties for #{blob.name}/#{blob.container}: #{err}"
|
294
300
|
log('warn', msg)
|
295
301
|
next
|
296
302
|
end
|
297
303
|
|
298
|
-
next unless blob_properties
|
299
|
-
next unless blob_properties
|
304
|
+
next unless blob_properties[:x_ms_meta_microsoftazurecompute_osstate]
|
305
|
+
next unless blob_properties[:x_ms_meta_microsoftazurecompute_osstate].casecmp('generalized').zero?
|
300
306
|
|
301
307
|
mutex.synchronize do
|
302
308
|
results << blob_to_private_image_object(storage_account, blob, blob_properties)
|
@@ -315,11 +321,11 @@ module Azure
|
|
315
321
|
hash = blob.to_h.merge(
|
316
322
|
:storage_account => storage_account.to_h,
|
317
323
|
:blob_properties => blob_properties.to_h,
|
318
|
-
:operating_system => blob_properties
|
324
|
+
:operating_system => blob_properties[:x_ms_meta_microsoftazurecompute_ostype],
|
319
325
|
:uri => File.join(
|
320
|
-
storage_account.
|
321
|
-
blob
|
322
|
-
blob.
|
326
|
+
storage_account.blob_endpoint_from_hash,
|
327
|
+
blob[:container],
|
328
|
+
blob.name_from_hash
|
323
329
|
)
|
324
330
|
)
|
325
331
|
|
@@ -329,11 +335,11 @@ module Azure
|
|
329
335
|
# Get the key for the given +storage_acct+ using the appropriate method
|
330
336
|
# depending on the api-version.
|
331
337
|
#
|
332
|
-
def get_account_key(storage_acct)
|
338
|
+
def get_account_key(storage_acct, skip_accessors_definition = false)
|
333
339
|
if recent_api_version?
|
334
|
-
list_account_key_objects(storage_acct.
|
340
|
+
list_account_key_objects(storage_acct.name_from_hash, storage_acct.resource_group, skip_accessors_definition).first.key
|
335
341
|
else
|
336
|
-
list_account_keys(storage_acct.
|
342
|
+
list_account_keys(storage_acct.name_from_hash, storage_acct.resource_group).fetch('key1')
|
337
343
|
end
|
338
344
|
end
|
339
345
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: azure-armrest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel J. Berger
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2017-10-
|
14
|
+
date: 2017-10-10 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: json
|
@@ -302,7 +302,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
302
302
|
version: '0'
|
303
303
|
requirements: []
|
304
304
|
rubyforge_project:
|
305
|
-
rubygems_version: 2.6.
|
305
|
+
rubygems_version: 2.6.14
|
306
306
|
signing_key:
|
307
307
|
specification_version: 4
|
308
308
|
summary: An interface for ARM/JSON Azure REST API
|