azure-armrest 0.3.7 → 0.3.8

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 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