azure-armrest 0.3.7 → 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d9f6d582f68c38666f0904db56732b7cdad8f90
4
- data.tar.gz: e4f320a935c370248d70f24b8365b68fcee4f4eb
3
+ metadata.gz: cfe77ac7705382e48afa5deeab108cefa90d07ae
4
+ data.tar.gz: a6c7a2ab379f076af861fb21fcc674a573aee67e
5
5
  SHA512:
6
- metadata.gz: 77434cab50f1c0834bf422a5b66f48d9ed9a641ac29ffbac671d83100e0c65b6a94fc0ad13efa6d802d29532c5133a90b4d080b55efcb01389bc3bc1603faa76
7
- data.tar.gz: c2807304de56ded6362984e23c899fd92c6c59315261ebf33aa11d75df27c6082e8f0c57a47559823333d0de1da3150b6d56c9e10793b01a69c38666a93fc85d
6
+ metadata.gz: 97bfb012ae0a449c8c95ed107c68e5849dfcb5359a2e7a7d9a566bc12b4406722fa0c88e6678cf8834b0abdba81fbd07804d628ff4025a12940f0428d788e2e4
7
+ data.tar.gz: ebb632b5bfcf35007a99d6e46f0f65a85dea6fc0796d998d0b6c6620db01a6f99dc82795767db3c4792b226f07d6a81d539d0c53bc42f037d430057e2712e7e4
data/CHANGES CHANGED
@@ -1,3 +1,17 @@
1
+ = 0.3.8 - 7-Oct-2-16
2
+ * Added more robust exception wrapping, now mostly based on http error code.
3
+ * Refactored the internal fetch_providers method so that it uses our own
4
+ service class method call, which is cached.
5
+ * Added the TemplateDeploymentService#get_template method.
6
+ * The :list and :list_all methods in the ResourceGroupBasedService class now
7
+ automatically perform pagination.
8
+ * Replaced our custom inspect with the one provided by pretty_print.
9
+ * Added the :response_code and :response_headers accessors to the BaseModel
10
+ class. All individual objects and collections now set these.
11
+ * Modified our :poll method to check the :response_code, and to try the
12
+ :location attribute if :azure_asyncoperation isn't found.
13
+ * Updated the :create documentation for the StorageAccountService class.
14
+
1
15
  = 0.3.7 - 15-Sep-2016
2
16
  * Modified the private image listing code in the StorageAccountService class
3
17
  to skip over storage accounts when we cannot get a key.
@@ -3,6 +3,7 @@ require 'json'
3
3
  require 'thread'
4
4
  require 'addressable'
5
5
  require 'parallel'
6
+ require 'cache_method'
6
7
 
7
8
  # The Azure module serves as a namespace.
8
9
  module Azure
@@ -5,6 +5,7 @@ module Azure
5
5
  class ArmrestCollection < Array
6
6
  attr_accessor :continuation_token
7
7
  attr_accessor :response_headers
8
+ attr_accessor :response_code
8
9
 
9
10
  alias skip_token continuation_token
10
11
  alias skip_token= continuation_token=
@@ -18,6 +19,7 @@ module Azure
18
19
  json_response = JSON.parse(response)
19
20
  array = new(json_response['value'].map { |hash| klass.new(hash) })
20
21
 
22
+ array.response_code = response.code
21
23
  array.response_headers = response.headers
22
24
  array.continuation_token = parse_skip_token(json_response)
23
25
 
@@ -166,7 +166,8 @@ module Azure
166
166
  # such as create or delete.
167
167
  #
168
168
  def poll(response)
169
- url = response.respond_to?(:azure_asyncoperation) ? response.azure_asyncoperation : response.to_s
169
+ return 'Succeeded' if [200, 201].include?(response.response_code)
170
+ url = response.try(:azure_asyncoperation) || response.try(:location)
170
171
  JSON.parse(rest_get(url))['status']
171
172
  end
172
173
 
@@ -198,11 +199,9 @@ module Azure
198
199
  class << self
199
200
  private
200
201
 
201
- def rest_execute(options, http_method = :get)
202
- options = options.merge(
203
- :method => http_method,
204
- :url => Addressable::URI.escape(options[:url])
205
- )
202
+ def rest_execute(options, http_method = :get, encode = true)
203
+ url = encode ? Addressable::URI.encode(options[:url]) : options[:url]
204
+ options = options.merge(:method => http_method, :url => url)
206
205
  RestClient::Request.execute(options)
207
206
  rescue RestClient::Exception => e
208
207
  raise_api_exception(e)
@@ -232,34 +231,30 @@ module Azure
232
231
  rest_execute(options, :head)
233
232
  end
234
233
 
235
- def raise_api_exception(e)
234
+ def raise_api_exception(err)
236
235
  begin
237
- response = JSON.parse(e.http_body)
238
- code = response['error']['code']
239
- message = response['error']['message']
236
+ response = JSON.parse(err.http_body)
237
+ code = response['error']['code']
238
+ message = response['error']['message']
240
239
  rescue
241
- message = e.http_body
240
+ code = err.try(:http_code) || err.try(:code)
241
+ message = err.try(:http_body) || err.try(:message)
242
242
  end
243
- message = e.http_body unless message
244
-
245
- exception_type = case e
246
- when RestClient::NotFound
247
- ResourceNotFoundException
248
- when RestClient::BadRequest
249
- BadRequestException
250
- when RestClient::GatewayTimeout
251
- GatewayTimeoutException
252
- when RestClient::BadGateway
253
- BadGatewayException
254
- when RestClient::Unauthorized, RestClient::Forbidden
255
- UnauthorizedException
256
- when RestClient::TooManyRequests
257
- TooManyRequestsException
258
- else
259
- ApiException
260
- end
261
-
262
- raise exception_type.new(code, message, e)
243
+
244
+ exception_type = Azure::Armrest::EXCEPTION_MAP[err.http_code]
245
+
246
+ # If this is an exception that doesn't map directly to an HTTP code
247
+ # then parse it the exception class name and re-raise it as our own.
248
+ if exception_type.nil?
249
+ begin
250
+ klass = "Azure::Armrest::" + err.class.to_s.split("::").last + "Exception"
251
+ exception_type = const_get(klass)
252
+ rescue NameError
253
+ exception_type = Azure::Armrest::ApiException
254
+ end
255
+ end
256
+
257
+ raise exception_type.new(code, message, err)
263
258
  end
264
259
  end
265
260
 
@@ -267,7 +262,7 @@ module Azure
267
262
 
268
263
  # REST verb methods
269
264
 
270
- def rest_execute(url, body = nil, http_method = :get)
265
+ def rest_execute(url, body = nil, http_method = :get, encode = true)
271
266
  options = {
272
267
  :url => url,
273
268
  :proxy => configuration.proxy,
@@ -282,13 +277,17 @@ module Azure
282
277
 
283
278
  options[:payload] = body if body
284
279
 
285
- self.class.send(:rest_execute, options, http_method)
280
+ self.class.send(:rest_execute, options, http_method, encode)
286
281
  end
287
282
 
288
283
  def rest_get(url)
289
284
  rest_execute(url)
290
285
  end
291
286
 
287
+ def rest_get_without_encoding(url)
288
+ rest_execute(url, nil, :get, false)
289
+ end
290
+
292
291
  def rest_put(url, body = '')
293
292
  rest_execute(url, body, :put)
294
293
  end
@@ -162,7 +162,11 @@ module Azure
162
162
 
163
163
  # Return the default api version for the given provider and service
164
164
  def provider_default_api_version(provider, service)
165
- @provider_api_versions[provider.downcase][service.downcase]
165
+ if @provider_api_versions
166
+ @provider_api_versions[provider.downcase][service.downcase]
167
+ else
168
+ nil # Typically only for the fetch_providers method.
169
+ end
166
170
  end
167
171
 
168
172
  # The name of the file or handle used to log http requests.
@@ -266,22 +270,7 @@ module Azure
266
270
  end
267
271
 
268
272
  def fetch_providers
269
- uri = Addressable::URI.join(Azure::Armrest::RESOURCE, 'providers')
270
- uri.query = "api-version=#{api_version}"
271
-
272
- response = ArmrestService.send(
273
- :rest_get,
274
- :url => uri.to_s,
275
- :proxy => proxy,
276
- :ssl_version => ssl_version,
277
- :ssl_verify => ssl_verify,
278
- :headers => {
279
- :content_type => content_type,
280
- :authorization => token
281
- }
282
- )
283
-
284
- JSON.parse(response.body)['value'].map { |hash| Azure::Armrest::ResourceProvider.new(hash) }
273
+ Azure::Armrest::ResourceProviderService.new(self).list
285
274
  end
286
275
 
287
276
  def fetch_token
@@ -52,18 +52,100 @@ module Azure
52
52
  end
53
53
  end
54
54
 
55
- # A list of predefined exceptions that we wrap around RestClient exceptions.
56
-
57
- class ResourceNotFoundException < ApiException; end
58
-
55
+ # Rewrapped HTTP errors
56
+ class BadGatewayException < ApiException; end
59
57
  class BadRequestException < ApiException; end
60
-
58
+ class BandwidthLimitExceededException < ApiException; end
59
+ class BlockedByWindowsParentalControlsException < ApiException; end
60
+ class ConflictException < ApiException; end
61
+ class ExpectationFailedException < ApiException; end
62
+ class FailedDependencyException < ApiException; end
63
+ class ForbiddenException < ApiException; end
64
+ class GatewayTimeoutException < ApiException; end
65
+ class GoneException < ApiException; end
66
+ class HTTPVersionNotSupportedException < ApiException; end
67
+ class ImATeapotException < ApiException; end
68
+ class InsufficientStorageException < ApiException; end
69
+ class InternalServerErrorException < ApiException; end
70
+ class LengthRequiredException < ApiException; end
71
+ class LockedException < ApiException; end
72
+ class LoopDetectedException < ApiException; end
73
+ class MethodNotAllowedException < ApiException; end
74
+ class NetworkAuthenticationRequiredException < ApiException; end
75
+ class NotAcceptableException < ApiException; end
76
+ class NotExtendedException < ApiException; end
77
+ class NotFoundException < ApiException; end
78
+ class NotImplementedException < ApiException; end
79
+ class PayloadTooLargeException < ApiException; end
80
+ class PaymentRequiredException < ApiException; end
81
+ class PreconditionFailedException < ApiException; end
82
+ class PreconditionRequiredException < ApiException; end
83
+ class ProxyAuthenticationRequiredException < ApiException; end
84
+ class RangeNotSatisfiableException < ApiException; end
85
+ class RequestHeaderFieldsTooLargeException < ApiException; end
86
+ class RequestTimeoutException < ApiException; end
87
+ class RetryWithException < ApiException; end
88
+ class ServiceUnavailableException < ApiException; end
89
+ class TooManyConnectionsFromThisIPException < ApiException; end
90
+ class TooManyRequestsException < ApiException; end
91
+ class URITooLongException < ApiException; end
61
92
  class UnauthorizedException < ApiException; end
93
+ class UnorderedCollectionException < ApiException; end
94
+ class UnprocessableEntityException < ApiException; end
95
+ class UnsupportedMediaTypeException < ApiException; end
96
+ class UpgradeRequiredException < ApiException; end
97
+ class VariantAlsoNegotiatesException < ApiException; end
62
98
 
63
- class BadGatewayException < ApiException; end
64
-
65
- class GatewayTimeoutException < ApiException; end
99
+ # Custom errors or other wrapped exceptions
100
+ class ResourceNotFoundException < ApiException; end
101
+ class TimeoutException < RequestTimeoutException; end
102
+ class OpenTimeoutException < TimeoutException; end
103
+ class ReadTimeoutException < TimeoutException; end
66
104
 
67
- class TooManyRequestsException < ApiException; end
105
+ # Map HTTP error codes to our exception classes
106
+ EXCEPTION_MAP = {
107
+ 400 => BadRequestException,
108
+ 401 => UnauthorizedException,
109
+ 402 => PaymentRequiredException,
110
+ 403 => ForbiddenException,
111
+ 404 => NotFoundException,
112
+ 405 => MethodNotAllowedException,
113
+ 406 => NotAcceptableException,
114
+ 407 => ProxyAuthenticationRequiredException,
115
+ 408 => RequestTimeoutException,
116
+ 409 => ConflictException,
117
+ 410 => GoneException,
118
+ 411 => LengthRequiredException,
119
+ 412 => PreconditionFailedException,
120
+ 413 => PayloadTooLargeException,
121
+ 414 => URITooLongException,
122
+ 415 => UnsupportedMediaTypeException,
123
+ 416 => RangeNotSatisfiableException,
124
+ 417 => ExpectationFailedException,
125
+ 418 => ImATeapotException,
126
+ 421 => TooManyConnectionsFromThisIPException,
127
+ 422 => UnprocessableEntityException,
128
+ 423 => LockedException,
129
+ 424 => FailedDependencyException,
130
+ 425 => UnorderedCollectionException,
131
+ 426 => UpgradeRequiredException,
132
+ 428 => PreconditionRequiredException,
133
+ 429 => TooManyRequestsException,
134
+ 431 => RequestHeaderFieldsTooLargeException,
135
+ 449 => RetryWithException,
136
+ 450 => BlockedByWindowsParentalControlsException,
137
+ 500 => InternalServerErrorException,
138
+ 501 => NotImplementedException,
139
+ 502 => BadGatewayException,
140
+ 503 => ServiceUnavailableException,
141
+ 504 => GatewayTimeoutException,
142
+ 505 => HTTPVersionNotSupportedException,
143
+ 506 => VariantAlsoNegotiatesException,
144
+ 507 => InsufficientStorageException,
145
+ 508 => LoopDetectedException,
146
+ 509 => BandwidthLimitExceededException,
147
+ 510 => NotExtendedException,
148
+ 511 => NetworkAuthenticationRequiredException
149
+ }.freeze
68
150
  end
69
151
  end
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/string/inflections'
2
+ require 'pp'
2
3
 
3
4
  module Azure
4
5
  module Armrest
@@ -23,6 +24,7 @@ module Azure
23
24
  attr_hash :tags
24
25
 
25
26
  attr_accessor :response_headers
27
+ attr_accessor :response_code
26
28
 
27
29
  # Constructs and returns a new JSON wrapper class. Pass in a plain
28
30
  # JSON string and it will automatically give you accessor methods
@@ -91,19 +93,9 @@ module Azure
91
93
  @json
92
94
  end
93
95
 
94
- def inspect_method_list
95
- methods(false).reject { |m| m.to_s.end_with?('=') }
96
- end
97
- private :inspect_method_list
98
-
99
- def inspect
100
- Kernel.instance_method(:to_s).bind(self).call.chomp!('>') <<
101
- ' ' <<
102
- inspect_method_list.map { |m| "#{m}=#{send(m).inspect}" }.join(', ') <<
103
- '>'
104
- end
105
-
106
96
  def pretty_print(q)
97
+ inspect_method_list = methods(false).reject { |m| m.to_s.end_with?('=') }
98
+
107
99
  q.object_address_group(self) {
108
100
  q.seplist(inspect_method_list, lambda { q.text ',' }) {|v|
109
101
  q.breakable
@@ -117,6 +109,8 @@ module Azure
117
109
  }
118
110
  end
119
111
 
112
+ alias_method :inspect, :pretty_print_inspect
113
+
120
114
  def ==(other)
121
115
  return false unless other.kind_of?(BaseModel)
122
116
  __getobj__ == other.__getobj__
@@ -186,6 +180,7 @@ module Azure
186
180
  # Initial class definitions. Reopen these classes as needed.
187
181
 
188
182
  class AvailabilitySet < BaseModel; end
183
+ class DeploymentTemplate < BaseModel; end
189
184
  class Event < BaseModel; end
190
185
  class ImageVersion < BaseModel; end
191
186
  class Offer < BaseModel; end
@@ -196,7 +191,9 @@ module Azure
196
191
  class Sku < BaseModel; end
197
192
  class Usage < BaseModel; end
198
193
 
199
- class ResponseHeaders < BaseModel; end
194
+ class ResponseHeaders < BaseModel
195
+ undef_method :response_headers
196
+ end
200
197
 
201
198
  class StorageAccount < BaseModel; end
202
199
  class StorageAccountKey < StorageAccount
@@ -21,13 +21,18 @@ module Azure
21
21
  url = yield(url) || url if block_given?
22
22
  response = rest_put(url, options.to_json)
23
23
 
24
- obj = nil
24
+ headers = Azure::Armrest::ResponseHeaders.new(response.headers)
25
+ headers.response_code = response.code
25
26
 
26
- unless response.empty?
27
+ if response.body.empty?
28
+ obj = get(name, rgroup)
29
+ else
27
30
  obj = model_class.new(response.body)
28
- obj.response_headers = Azure::Armrest::ResponseHeaders.new(response.headers)
29
31
  end
30
32
 
33
+ obj.response_headers = headers
34
+ obj.response_code = headers.response_code
35
+
31
36
  obj
32
37
  end
33
38
 
@@ -46,7 +51,7 @@ module Azure
46
51
  url = yield(url) || url if block_given?
47
52
  response = rest_get(url)
48
53
 
49
- Azure::Armrest::ArmrestCollection.create_from_response(response, model_class)
54
+ get_all_results(response)
50
55
  end
51
56
 
52
57
  # Use a single call to get all resources for the service. You may
@@ -63,7 +68,7 @@ module Azure
63
68
  url = yield(url) || url if block_given?
64
69
 
65
70
  response = rest_get(url)
66
- results = Azure::Armrest::ArmrestCollection.create_from_response(response, model_class)
71
+ results = get_all_results(response)
67
72
 
68
73
  filter.empty? ? results : results.select { |obj| filter.all? { |k, v| obj.public_send(k) == v } }
69
74
  end
@@ -81,6 +86,7 @@ module Azure
81
86
 
82
87
  obj = model_class.new(response.body)
83
88
  obj.response_headers = Azure::Armrest::ResponseHeaders.new(response.headers)
89
+ obj.response_code = response.code
84
90
 
85
91
  obj
86
92
  end
@@ -106,11 +112,29 @@ module Azure
106
112
  raise Azure::Armrest::ResourceNotFoundException.new(response.code, msg, response)
107
113
  end
108
114
 
109
- Azure::Armrest::ResponseHeaders.new(response.headers)
115
+ headers = Azure::Armrest::ResponseHeaders.new(response.headers)
116
+ headers.response_code = response.code
117
+
118
+ headers
110
119
  end
111
120
 
112
121
  private
113
122
 
123
+ # Make additional calls and concatenate the results if a continuation URL is found.
124
+ def get_all_results(response)
125
+ results = Azure::Armrest::ArmrestCollection.create_from_response(response, model_class)
126
+ nextlink = JSON.parse(response)['nextLink']
127
+
128
+ while nextlink
129
+ response = rest_get_without_encoding(nextlink)
130
+ more = Azure::Armrest::ArmrestCollection.create_from_response(response, model_class)
131
+ results.concat(more)
132
+ nextlink = JSON.parse(response)['nextLink']
133
+ end
134
+
135
+ results
136
+ end
137
+
114
138
  def validate_resource_group(name)
115
139
  raise ArgumentError, "must specify resource group" unless name
116
140
  end
@@ -144,17 +168,22 @@ module Azure
144
168
  array = []
145
169
  mutex = Mutex.new
146
170
  headers = nil
171
+ code = nil
147
172
 
148
173
  Parallel.each(list_resource_groups, :in_threads => configuration.max_threads) do |rg|
149
174
  response = rest_get(build_url(rg.name))
150
175
  json_response = JSON.parse(response.body)['value']
151
176
  headers = Azure::Armrest::ResponseHeaders.new(response.headers)
177
+ code = response.code
152
178
  results = json_response.map { |hash| model_class.new(hash) }
153
179
  mutex.synchronize { array << results } unless results.blank?
154
180
  end
155
181
 
156
182
  array = ArmrestCollection.new(array.flatten)
157
- array.response_headers = headers # Use the last set of headers for the overall result
183
+
184
+ # Use the last set of headers and response code for the overall result.
185
+ array.response_headers = headers
186
+ array.response_code = code
158
187
 
159
188
  array
160
189
  end
@@ -1,5 +1,3 @@
1
- require 'cache_method'
2
-
3
1
  module Azure
4
2
  module Armrest
5
3
  class ResourceProviderService < ArmrestService
@@ -39,6 +37,8 @@ module Azure
39
37
  _list.map{ |hash| Azure::Armrest::ResourceProvider.new(hash) }
40
38
  end
41
39
 
40
+ # This is split out for the cache_method feature.
41
+ #
42
42
  def _list
43
43
  response = rest_get(build_url)
44
44
  JSON.parse(response)["value"]
@@ -44,7 +44,8 @@ module Azure
44
44
  # specified parameters.
45
45
  #
46
46
  # Note that the name of the storage account within the specified
47
- # must be 3-24 alphanumeric lowercase characters.
47
+ # must be 3-24 alphanumeric lowercase characters. This name must be
48
+ # unique across all subscriptions.
48
49
  #
49
50
  # The options available are as follows:
50
51
  #
@@ -68,15 +69,14 @@ module Azure
68
69
  #
69
70
  # sas = Azure::Armrest::StorageAccountService(config)
70
71
  #
71
- # sas.create(
72
- # "your_storage_account",
73
- # "your_resource_group",
74
- # {
75
- # :location => "West US",
76
- # :properties => {:accountType => "Standard_ZRS"},
77
- # :tags => {:YourCompany => true}
78
- # }
79
- # )
72
+ # options = {
73
+ # :location => "Central US",
74
+ # :tags => {:redhat => true},
75
+ # :sku => {:name => "Standard_LRS"},
76
+ # :kind => "Storage"
77
+ # }
78
+ #
79
+ # sas.create("your_storage_account", "your_resource_group", options)
80
80
  #
81
81
  def create(account_name, rgroup = configuration.resource_group, options)
82
82
  validating = options.delete(:validating)
@@ -86,10 +86,6 @@ module Azure
86
86
  url << "&validating=" << validating if validating
87
87
  end
88
88
 
89
- # An initial create call will return nil because the response body is
90
- # empty. In that case, make another call to get the object properties.
91
- acct = get(account_name, rgroup) unless acct
92
-
93
89
  acct.proxy = configuration.proxy
94
90
  acct.ssl_version = configuration.ssl_version
95
91
  acct.ssl_verify = configuration.ssl_verify
@@ -37,6 +37,18 @@ module Azure
37
37
  response = rest_get(url)
38
38
  TemplateDeploymentOperation.new(response)
39
39
  end
40
+
41
+ # Returns the json template as an object for the given deployment.
42
+ #
43
+ # If you want the plain JSON text then call .to_json on the returned object.
44
+ #
45
+ def get_template(deploy_name, resource_group = configuration.resource_group)
46
+ validate_resource_group(resource_group)
47
+ validate_resource(deploy_name)
48
+ url = build_url(resource_group, deploy_name, 'exportTemplate')
49
+ response = JSON.parse(rest_post(url))['template']
50
+ DeploymentTemplate.new(response)
51
+ end
40
52
  end
41
53
  end
42
54
  end
@@ -1,5 +1,5 @@
1
1
  module Azure
2
2
  module Armrest
3
- VERSION = '0.3.7'.freeze
3
+ VERSION = '0.3.8'.freeze
4
4
  end
5
5
  end
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.3.7
4
+ version: 0.3.8
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: 2016-09-15 00:00:00.000000000 Z
14
+ date: 2016-10-07 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: json