azure-armrest 0.2.10 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -2
- data/CHANGES +16 -10
- data/README.md +4 -3
- data/azure-armrest.gemspec +1 -0
- data/lib/azure/armrest.rb +3 -1
- data/lib/azure/armrest/armrest_service.rb +111 -364
- data/lib/azure/armrest/billing/usage_service.rb +54 -0
- data/lib/azure/armrest/configuration.rb +258 -0
- data/lib/azure/armrest/exception.rb +4 -25
- data/lib/azure/armrest/model/base_model.rb +24 -4
- data/lib/azure/armrest/model/storage_account.rb +18 -9
- data/lib/azure/armrest/resource_group_based_service.rb +1 -1
- data/lib/azure/armrest/resource_group_service.rb +3 -3
- data/lib/azure/armrest/resource_service.rb +35 -26
- data/lib/azure/armrest/storage_account_service.rb +2 -24
- data/lib/azure/armrest/version.rb +1 -1
- data/lib/azure/armrest/virtual_machine_service.rb +6 -5
- metadata +18 -2
@@ -0,0 +1,54 @@
|
|
1
|
+
module Azure
|
2
|
+
module Armrest
|
3
|
+
module Billing
|
4
|
+
class UsageService < ArmrestService
|
5
|
+
# Creates and returns a new UsageService object.
|
6
|
+
#
|
7
|
+
def initialize(configuration, options = {})
|
8
|
+
options = options.merge('api_version' => '2015-06-01-preview')
|
9
|
+
super(configuration, 'subscriptions', 'Microsoft.Commerce', options)
|
10
|
+
end
|
11
|
+
|
12
|
+
# List usage details. The +options+ hash may include the following
|
13
|
+
# filters:
|
14
|
+
#
|
15
|
+
# :reportedStartTime # e.g. 2016-05-30T00:00:00Z. Mandatory.
|
16
|
+
# :reportedEndTime # e.g. 2016-06-01T00:00:00Z. Mandatory.
|
17
|
+
# :aggregationGranularity # Either 'Daily' or 'Hourly'. Default is Daily.
|
18
|
+
# :showDetails # Either true or false. Default is true.
|
19
|
+
# :continuationToken # Token received from previous call. No default.
|
20
|
+
#
|
21
|
+
# The :reportedStartTime and :reportedEndTime values should be in
|
22
|
+
# UTC + iso8601 format. For "Daily" aggregation, the time should be set
|
23
|
+
# to midnight. For "Hourly" aggregation, only the hour should be
|
24
|
+
# set, with minutes and seconds set to "00".
|
25
|
+
#
|
26
|
+
def list(options = {})
|
27
|
+
url = build_url(options)
|
28
|
+
response = rest_get(url)
|
29
|
+
JSON.parse(response)['value'].map { |hash| Azure::Armrest::Usage.new(hash) }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def build_url(options = {})
|
35
|
+
url = File.join(
|
36
|
+
Azure::Armrest::COMMON_URI,
|
37
|
+
configuration.subscription_id,
|
38
|
+
'providers',
|
39
|
+
@provider,
|
40
|
+
'UsageAggregates'
|
41
|
+
)
|
42
|
+
|
43
|
+
url << "?api-version=#{@api_version}"
|
44
|
+
|
45
|
+
options.each do |key, value|
|
46
|
+
url << "&#{key}=#{value}"
|
47
|
+
end
|
48
|
+
|
49
|
+
url
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
module Azure
|
2
|
+
module Armrest
|
3
|
+
class Configuration
|
4
|
+
# Clear all class level caches. Typically used for testing only.
|
5
|
+
def self.clear_caches
|
6
|
+
# Used to store unique token information.
|
7
|
+
@token_cache = Hash.new { |h, k| h[k] = [] }
|
8
|
+
end
|
9
|
+
|
10
|
+
clear_caches # Clear caches at load time.
|
11
|
+
|
12
|
+
# Retrieve the cached token for a configuration.
|
13
|
+
# Return both the token and its expiration date, or nil if not cached
|
14
|
+
def self.retrieve_token(configuration)
|
15
|
+
@token_cache[configuration.hash]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Cache the token for a configuration that a token has been fetched from Azure
|
19
|
+
def self.cache_token(configuration)
|
20
|
+
raise ArgumentError, "Configuration does not have a token" if configuration.token.nil?
|
21
|
+
@token_cache[configuration.hash] = [configuration.token, configuration.token_expiration]
|
22
|
+
end
|
23
|
+
|
24
|
+
# The api-version string
|
25
|
+
attr_accessor :api_version
|
26
|
+
|
27
|
+
# The client ID used to gather token information.
|
28
|
+
attr_accessor :client_id
|
29
|
+
|
30
|
+
# The client key used to gather token information.
|
31
|
+
attr_accessor :client_key
|
32
|
+
|
33
|
+
# The tenant ID used to gather token information.
|
34
|
+
attr_accessor :tenant_id
|
35
|
+
|
36
|
+
# The subscription ID used for each http request.
|
37
|
+
attr_accessor :subscription_id
|
38
|
+
|
39
|
+
# The resource group used for http requests.
|
40
|
+
attr_accessor :resource_group
|
41
|
+
|
42
|
+
# The grant type. The default is client_credentials.
|
43
|
+
attr_accessor :grant_type
|
44
|
+
|
45
|
+
# The content type specified for http requests. The default is 'application/json'
|
46
|
+
attr_accessor :content_type
|
47
|
+
|
48
|
+
# The accept type specified for http request results. The default is 'application/json'
|
49
|
+
attr_accessor :accept
|
50
|
+
|
51
|
+
# Proxy to be used for all http requests.
|
52
|
+
attr_accessor :proxy
|
53
|
+
|
54
|
+
# SSL version to be used for all http requests.
|
55
|
+
attr_accessor :ssl_version
|
56
|
+
|
57
|
+
# SSL verify mode for all http requests.
|
58
|
+
attr_accessor :ssl_verify
|
59
|
+
|
60
|
+
# Namespace providers, their resource types, locations and supported api-version strings.
|
61
|
+
attr_reader :providers
|
62
|
+
|
63
|
+
# Yields a new Azure::Armrest::Configuration objects. Note that you must
|
64
|
+
# specify a client_id, client_key, tenant_id and subscription_id. All other
|
65
|
+
# parameters are optional.
|
66
|
+
#
|
67
|
+
# Example:
|
68
|
+
#
|
69
|
+
# config = Azure::Armrest::Configuration.new(
|
70
|
+
# :client_id => 'xxxx',
|
71
|
+
# :client_key => 'yyyy',
|
72
|
+
# :tenant_id => 'zzzz',
|
73
|
+
# :subscription_id => 'abcd'
|
74
|
+
# )
|
75
|
+
#
|
76
|
+
# If you specify a :resource_group, that group will be used for resource
|
77
|
+
# group based service class requests. Otherwise, you will need to specify
|
78
|
+
# a resource group for most service methods.
|
79
|
+
#
|
80
|
+
# Although you can specify an :api_version, it is typically overridden
|
81
|
+
# by individual service classes.
|
82
|
+
#
|
83
|
+
def initialize(args)
|
84
|
+
# Use defaults, and override with provided arguments
|
85
|
+
options = {
|
86
|
+
:api_version => '2015-01-01',
|
87
|
+
:accept => 'application/json',
|
88
|
+
:content_type => 'application/json',
|
89
|
+
:grant_type => 'client_credentials',
|
90
|
+
:proxy => ENV['http_proxy'],
|
91
|
+
:ssl_version => 'TLSv1',
|
92
|
+
}.merge(args.symbolize_keys)
|
93
|
+
|
94
|
+
user_token = options.delete(:token)
|
95
|
+
user_token_expiration = options.delete(:token_expiration)
|
96
|
+
|
97
|
+
options.each { |key, value| send("#{key}=", value) }
|
98
|
+
|
99
|
+
unless client_id && client_key && tenant_id && subscription_id
|
100
|
+
raise ArgumentError, "client_id, client_key, tenant_id and subscription_id must all be specified"
|
101
|
+
end
|
102
|
+
|
103
|
+
if user_token && user_token_expiration
|
104
|
+
set_token(user_token, user_token_expiration)
|
105
|
+
elsif user_token || user_token_expiration
|
106
|
+
raise ArgumentError, "token and token_expiration must be both specified"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Allows for URI objects or Strings.
|
110
|
+
@proxy = @proxy.to_s if @proxy
|
111
|
+
|
112
|
+
@providers = fetch_providers
|
113
|
+
set_provider_api_versions
|
114
|
+
end
|
115
|
+
|
116
|
+
def hash
|
117
|
+
[tenant_id, client_id, client_key].join('_').hash
|
118
|
+
end
|
119
|
+
|
120
|
+
def eql?(other)
|
121
|
+
return true if equal?(other)
|
122
|
+
return false unless self.class == other.class
|
123
|
+
tenant_id == other.tenant_id && client_id == other.client_id && client_key == other.client_key
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the token for the current cache key, or sets it if it does not
|
127
|
+
# exist or it has expired.
|
128
|
+
#
|
129
|
+
def token
|
130
|
+
ensure_token
|
131
|
+
@token
|
132
|
+
end
|
133
|
+
|
134
|
+
# Set the token value and expiration time.
|
135
|
+
#
|
136
|
+
def set_token(token, token_expiration)
|
137
|
+
validate_token_time(token_expiration)
|
138
|
+
|
139
|
+
@token, @token_expiration = token, token_expiration.utc
|
140
|
+
self.class.cache_token(self)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the expiration datetime of the current token
|
144
|
+
#
|
145
|
+
def token_expiration
|
146
|
+
ensure_token
|
147
|
+
@token_expiration
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return the default api version for the given provider and service
|
151
|
+
def provider_default_api_version(provider, service)
|
152
|
+
@provider_api_versions[provider.downcase][service.downcase]
|
153
|
+
end
|
154
|
+
|
155
|
+
# The name of the file or handle used to log http requests.
|
156
|
+
#--
|
157
|
+
# We have to do a little extra work here to convert a possible
|
158
|
+
# file handle to a file name.
|
159
|
+
#
|
160
|
+
def self.log
|
161
|
+
file = RestClient.log.instance_variable_get("@target_file")
|
162
|
+
file || RestClient.log
|
163
|
+
end
|
164
|
+
|
165
|
+
# Sets the log to +output+, which can be a file or a handle.
|
166
|
+
#
|
167
|
+
def self.log=(output)
|
168
|
+
RestClient.log = output
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def ensure_token
|
174
|
+
@token, @token_expiration = self.class.retrieve_token(self) if @token.nil?
|
175
|
+
fetch_token if @token.nil? || Time.now.utc > @token_expiration
|
176
|
+
end
|
177
|
+
|
178
|
+
# Don't allow tokens from the past to be set.
|
179
|
+
#
|
180
|
+
def validate_token_time(time)
|
181
|
+
if time.utc < Time.now.utc
|
182
|
+
raise ArgumentError, 'token_expiration date invalid'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Build a one-time lookup hash that sets the appropriate api-version
|
187
|
+
# string for a given provider and resource type. If possible, select
|
188
|
+
# a non-preview version that is not set in the future. Otherwise, just
|
189
|
+
# just the most recent one.
|
190
|
+
#
|
191
|
+
def set_provider_api_versions
|
192
|
+
# A lookup table for getting api-version strings per provider and service.
|
193
|
+
@provider_api_versions = Hash.new { |hash, key| hash[key] = {} }
|
194
|
+
|
195
|
+
providers.each do |rp|
|
196
|
+
rp.resource_types.each do |rt|
|
197
|
+
if rt.api_versions.any? { |v| v !~ /preview/i && Time.parse(v).utc <= Time.now.utc }
|
198
|
+
api_version = rt.api_versions.reject do |version|
|
199
|
+
version =~ /preview/i || Time.parse(version).utc > Time.now.utc
|
200
|
+
end.first
|
201
|
+
else
|
202
|
+
api_version = rt.api_versions.first
|
203
|
+
end
|
204
|
+
|
205
|
+
namespace = rp['namespace'].downcase # Avoid name collision
|
206
|
+
resource_type = rt.resource_type.downcase
|
207
|
+
|
208
|
+
@provider_api_versions[namespace][resource_type] = api_version
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def fetch_providers
|
214
|
+
uri = Addressable::URI.join(Azure::Armrest::RESOURCE, 'providers')
|
215
|
+
uri.query = "api-version=#{api_version}"
|
216
|
+
|
217
|
+
response = ArmrestService.send(
|
218
|
+
:rest_get,
|
219
|
+
:url => uri.to_s,
|
220
|
+
:proxy => proxy,
|
221
|
+
:ssl_version => ssl_version,
|
222
|
+
:ssl_verify => ssl_verify,
|
223
|
+
:headers => {
|
224
|
+
:content_type => content_type,
|
225
|
+
:authorization => token
|
226
|
+
}
|
227
|
+
)
|
228
|
+
|
229
|
+
JSON.parse(response.body)['value'].map { |hash| Azure::Armrest::ResourceProvider.new(hash) }
|
230
|
+
end
|
231
|
+
|
232
|
+
def fetch_token
|
233
|
+
token_url = File.join(Azure::Armrest::AUTHORITY, tenant_id, 'oauth2/token')
|
234
|
+
|
235
|
+
response = JSON.parse(
|
236
|
+
ArmrestService.send(
|
237
|
+
:rest_post,
|
238
|
+
:url => token_url,
|
239
|
+
:proxy => proxy,
|
240
|
+
:ssl_version => ssl_version,
|
241
|
+
:ssl_verify => ssl_verify,
|
242
|
+
:payload => {
|
243
|
+
:grant_type => grant_type,
|
244
|
+
:client_id => client_id,
|
245
|
+
:client_secret => client_key,
|
246
|
+
:resource => Azure::Armrest::RESOURCE
|
247
|
+
}
|
248
|
+
)
|
249
|
+
)
|
250
|
+
|
251
|
+
@token = 'Bearer ' + response['access_token']
|
252
|
+
@token_expiration = Time.now.utc + response['expires_in'].to_i
|
253
|
+
|
254
|
+
self.class.cache_token(self)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -4,30 +4,15 @@ module Azure
|
|
4
4
|
attr_accessor :cause
|
5
5
|
attr_writer :message
|
6
6
|
|
7
|
-
# Create a new Armrest::Exception object. The +message+ should be an
|
8
|
-
# error string, while +cause_exception+ is typically set to the
|
9
|
-
# raw RestClient exception.
|
10
|
-
#
|
11
|
-
# You will not typically use this object directly.
|
12
|
-
#
|
13
7
|
def initialize(message = nil, cause_exception = nil)
|
14
8
|
@message = message
|
15
9
|
@cause = cause_exception
|
16
10
|
end
|
17
11
|
|
18
|
-
# The stringified version (message) of the exception.
|
19
|
-
#
|
20
12
|
def to_s
|
21
|
-
|
22
|
-
"#{message} (cause: #{cause})"
|
23
|
-
else
|
24
|
-
message
|
25
|
-
end
|
13
|
+
message
|
26
14
|
end
|
27
15
|
|
28
|
-
# The error message or, if the message is not set, the name of the
|
29
|
-
# exception class.
|
30
|
-
#
|
31
16
|
def message
|
32
17
|
@message || self.class.name
|
33
18
|
end
|
@@ -36,24 +21,16 @@ module Azure
|
|
36
21
|
class ApiException < Exception
|
37
22
|
attr_accessor :code
|
38
23
|
|
39
|
-
# Create a new ApiException class. The +code+ is the error code.
|
40
|
-
#
|
41
|
-
# This class serves as the parent
|
42
24
|
def initialize(code, message, cause_exception)
|
43
25
|
@code = code
|
44
26
|
super(message, cause_exception)
|
45
27
|
end
|
46
28
|
|
47
|
-
# A stringified version of the error. If self is a plain ApiException,
|
48
|
-
# then the cause is included to aid in debugging.
|
49
|
-
#
|
50
29
|
def to_s
|
51
|
-
"[#{code}] #{
|
30
|
+
"[#{code}] #{message}"
|
52
31
|
end
|
53
32
|
end
|
54
33
|
|
55
|
-
# A list of predefined exceptions that we wrap around RestClient exceptions.
|
56
|
-
|
57
34
|
class ResourceNotFoundException < ApiException; end
|
58
35
|
|
59
36
|
class BadRequestException < ApiException; end
|
@@ -64,5 +41,7 @@ module Azure
|
|
64
41
|
|
65
42
|
class GatewayTimeoutException < ApiException; end
|
66
43
|
|
44
|
+
class TooManyRequestsException < ApiException; end
|
45
|
+
|
67
46
|
end
|
68
47
|
end
|
@@ -89,11 +89,30 @@ module Azure
|
|
89
89
|
@json
|
90
90
|
end
|
91
91
|
|
92
|
+
def inspect_method_list
|
93
|
+
methods(false).reject { |m| m.to_s.end_with?('=') }
|
94
|
+
end
|
95
|
+
private :inspect_method_list
|
96
|
+
|
92
97
|
def inspect
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
98
|
+
Kernel.instance_method(:to_s).bind(self).call.chomp!('>') <<
|
99
|
+
' ' <<
|
100
|
+
inspect_method_list.map { |m| "#{m}=#{send(m).inspect}" }.join(', ') <<
|
101
|
+
'>'
|
102
|
+
end
|
103
|
+
|
104
|
+
def pretty_print(q)
|
105
|
+
q.object_address_group(self) {
|
106
|
+
q.seplist(inspect_method_list, lambda { q.text ',' }) {|v|
|
107
|
+
q.breakable
|
108
|
+
q.text v.to_s
|
109
|
+
q.text '='
|
110
|
+
q.group(1) {
|
111
|
+
q.breakable ''
|
112
|
+
q.pp(send(v))
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
97
116
|
end
|
98
117
|
|
99
118
|
def ==(other)
|
@@ -173,6 +192,7 @@ module Azure
|
|
173
192
|
class ResourceGroup < BaseModel; end
|
174
193
|
class ResourceProvider < BaseModel; end
|
175
194
|
class Sku < BaseModel; end
|
195
|
+
class Usage < BaseModel; end
|
176
196
|
|
177
197
|
class StorageAccount < BaseModel; end
|
178
198
|
class StorageAccountKey < StorageAccount
|
@@ -169,7 +169,8 @@ module Azure
|
|
169
169
|
|
170
170
|
headers = build_headers(url, key, :blob, :verb => 'HEAD')
|
171
171
|
|
172
|
-
response = ArmrestService.
|
172
|
+
response = ArmrestService.send(
|
173
|
+
:rest_head,
|
173
174
|
:url => url,
|
174
175
|
:headers => headers,
|
175
176
|
:proxy => proxy,
|
@@ -192,7 +193,8 @@ module Azure
|
|
192
193
|
|
193
194
|
headers = build_headers(url, key)
|
194
195
|
|
195
|
-
response = ArmrestService.
|
196
|
+
response = ArmrestService.send(
|
197
|
+
:rest_get,
|
196
198
|
:url => url,
|
197
199
|
:headers => headers,
|
198
200
|
:proxy => proxy,
|
@@ -283,7 +285,8 @@ module Azure
|
|
283
285
|
|
284
286
|
headers = build_headers(dst_url, key, :blob, options)
|
285
287
|
|
286
|
-
response = ArmrestService.
|
288
|
+
response = ArmrestService.send(
|
289
|
+
:rest_put,
|
287
290
|
:url => dst_url,
|
288
291
|
:payload => '',
|
289
292
|
:headers => headers,
|
@@ -305,7 +308,8 @@ module Azure
|
|
305
308
|
|
306
309
|
headers = build_headers(url, key, :blob, :verb => 'DELETE')
|
307
310
|
|
308
|
-
|
311
|
+
ArmrestService.send(
|
312
|
+
:rest_delete,
|
309
313
|
:url => url,
|
310
314
|
:headers => headers,
|
311
315
|
:proxy => proxy,
|
@@ -335,7 +339,8 @@ module Azure
|
|
335
339
|
options = options.merge(data)
|
336
340
|
headers = build_headers(url, key, :blob, options)
|
337
341
|
|
338
|
-
response = ArmrestService.
|
342
|
+
response = ArmrestService.send(
|
343
|
+
:rest_put,
|
339
344
|
:url => url,
|
340
345
|
:payload => '',
|
341
346
|
:headers => headers,
|
@@ -355,7 +360,8 @@ module Azure
|
|
355
360
|
|
356
361
|
headers = build_headers(url, key, :blob, :verb => 'PUT')
|
357
362
|
|
358
|
-
response = ArmrestService.
|
363
|
+
response = ArmrestService.send(
|
364
|
+
:rest_put,
|
359
365
|
:url => url,
|
360
366
|
:payload => '',
|
361
367
|
:headers => headers,
|
@@ -432,7 +438,8 @@ module Azure
|
|
432
438
|
|
433
439
|
headers = build_headers(url, key, :blob, additional_headers)
|
434
440
|
|
435
|
-
ArmrestService.
|
441
|
+
ArmrestService.send(
|
442
|
+
:rest_get,
|
436
443
|
:url => url,
|
437
444
|
:headers => headers,
|
438
445
|
:proxy => proxy,
|
@@ -486,7 +493,8 @@ module Azure
|
|
486
493
|
url = File.join(properties.primary_endpoints.blob, *args) + "?#{query}"
|
487
494
|
headers = build_headers(url, key, 'blob')
|
488
495
|
|
489
|
-
ArmrestService.
|
496
|
+
ArmrestService.send(
|
497
|
+
:rest_get,
|
490
498
|
:url => url,
|
491
499
|
:headers => headers,
|
492
500
|
:proxy => proxy,
|
@@ -508,7 +516,8 @@ module Azure
|
|
508
516
|
url << "?#{query}"
|
509
517
|
end
|
510
518
|
|
511
|
-
ArmrestService.
|
519
|
+
ArmrestService.send(
|
520
|
+
:rest_get,
|
512
521
|
:url => url,
|
513
522
|
:headers => headers,
|
514
523
|
:proxy => proxy,
|