azure-armrest 0.8.4 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|