azure-armrest 0.0.1 → 0.0.2

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: 8d37ff1ea6844a2ba05ee94cb4cb8a660f7a4de0
4
- data.tar.gz: 5c517080720615271223342ac26279df490b753e
3
+ metadata.gz: 1dd317c12a178191adb7d5f66fde6ed1c5739c96
4
+ data.tar.gz: 70f5fb9db2f070fb5b1d3cd9b9f917a444eb4bfd
5
5
  SHA512:
6
- metadata.gz: ddf50c2759ae7f41cb610cc7ac14aa1459b01b1ee0ef9761183cfb9ebab182f086a87828d3bc751ad8dbfa2123c0e9bdcf019e227d5db22ec9e5b6851056c93b
7
- data.tar.gz: 98ef0ba9a5a445c3828407071ba4287910cb5b6d888590f384051b4318b0bc317818d9f3a2e9654be916a553de3f56420587af663b9fc1dd1c4b026d4cedb00c
6
+ metadata.gz: 1ad7c72b3d648dc98ef1e6a1913e544b10d27cedb66b04dce4abf587b1fb7a7dbf98dc4ddf8f0707f184737da3960861cf19df2d1be6b03451477cca790bf50e
7
+ data.tar.gz: 7dbb101daa10eda92507ebf74511b70cde4cd1ef3292d4c662be2e372608527de2419005b732c2d372e3b03d760f6c5dff8d2583d91cc34fd5bcb1921df30d93
data/CHANGES CHANGED
@@ -1,2 +1,9 @@
1
- = 0.1.0 - ???
1
+ = 0.0.2 - 23-Sep-2015
2
+ * Revamped class names. Now use "Service" instead of "Manager".
3
+ * Several new service classes added, updated, and refactored.
4
+ * All service classes now take and store configuration information as the
5
+ first argument to ensure distinct credentials.
6
+ * Added Bill Wei as author.
7
+
8
+ = 0.0.1 - 13-Aug-2015
2
9
  * Initial release
data/README.md CHANGED
@@ -13,10 +13,11 @@ A Ruby interface for Azure using the new REST API.
13
13
  ```ruby
14
14
  require 'azure/armrest'
15
15
 
16
- # Set things on a global level. All other objects will then use the
16
+ # Create a configuration object. All service objects will then use the
17
17
  # information you set here.
18
+ # A token will be retrieved based on the information you provided
18
19
 
19
- Azure::Armrest::ArmrestManager.configure(
20
+ conf = Azure::Armrest::ArmrestService.configure(
20
21
  :client_id => 'XXXXX',
21
22
  :client_key => 'YYYYY',
22
23
  :tenant_id => 'ZZZZZ',
@@ -24,25 +25,13 @@ Azure::Armrest::ArmrestManager.configure(
24
25
  )
25
26
 
26
27
  # This will then use the configuration info set above.
27
- vmm = Azure::Armrest::VirtualMachineManager.new
28
-
29
- # Alternatively you can set the configuration information on a per-instance
30
- # basis if you need different credentials for different classes.
31
- vmm = Azure::Armrest::VirtualMachineManager.new(
32
- :client_id => 'XXXXX',
33
- :client_key => 'YYYYY',
34
- :tenant_id => 'ZZZZZ',
35
- :subscription_id => 'ABCDEFG'
36
- )
37
-
38
- # Call this before making method calls if using per-instance configuration.
39
- # This is not necessary if you set it via ArmrestManager.configure.
40
- vmm.get_token
28
+ # You can add other options specific to the service to be created
29
+ vms = Azure::Armrest::VirtualMachineService.new(conf, options)
41
30
 
42
31
  # Create a virtual machine
43
32
  vmm.create_virtual_machine(
44
33
  :name => 'some_vm',
45
- :location => 'West US',
34
+ :location => 'West US',
46
35
  :vm_size => 'Standard_A1',
47
36
  :computer_name => 'whatever',
48
37
  :admin_username => 'admin_user',
@@ -52,11 +41,6 @@ vmm.create_virtual_machine(
52
41
  )
53
42
  ```
54
43
 
55
- ## Tokens and methods
56
-
57
- You will not be able to make any method calls until you first call the
58
- get_token method.
59
-
60
44
  ## Subscriptions
61
45
 
62
46
  If you do not provide a subscription ID to the constructor, then the first
@@ -75,4 +59,5 @@ The gem is available as open source under the terms of the [Apache License 2.0](
75
59
 
76
60
  * Daniel Berger
77
61
  * Bronagh Sorota
62
+ * Bill Wei
78
63
 
data/Rakefile CHANGED
@@ -10,9 +10,9 @@ namespace :spec do
10
10
  t.pattern = ['spec/armrest_module_spec.rb']
11
11
  end
12
12
 
13
- desc 'Run tests for the Armrest::ArmrestManager base class'
14
- RSpec::Core::RakeTask.new(:manager) do |t|
15
- t.pattern = ['spec/armrest_manager_spec.rb']
13
+ desc 'Run tests for the Armrest::ArmrestService base class'
14
+ RSpec::Core::RakeTask.new(:service) do |t|
15
+ t.pattern = ['spec/armrest_service_spec.rb']
16
16
  end
17
17
  end
18
18
  end
@@ -4,30 +4,28 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'azure/armrest/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = 'azure-armrest'
8
- spec.version = Azure::Armrest::VERSION
9
- spec.authors = ['Daniel J. Berger', 'Bronagh Sorota', 'Greg Blomquist']
10
- spec.email = ['dberger@redhat.com', 'bsorota@redhat.com', 'gblomqui@redhat.com']
7
+ spec.name = 'azure-armrest'
8
+ spec.version = Azure::Armrest::VERSION
9
+ spec.authors = ['Daniel J. Berger', 'Bronagh Sorota', 'Greg Blomquist', 'Bill Wei']
10
+ spec.email = ['dberger@redhat.com', 'bsorota@redhat.com', 'gblomqui@redhat.com', 'billwei@redhat.com']
11
+ spec.summary = 'An interface for ARM/JSON Azure REST API'
12
+ spec.homepage = 'http://github.com/ManageIQ/azure-armrest'
13
+ spec.license = 'Apache 2.0'
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
11
15
 
12
- spec.summary = 'An interface for ARM/JSON Azure REST API'
13
- spec.description = <<-EOF
16
+ spec.description = <<-EOF
14
17
  This is a Ruby interface for Azure using the newer REST API. This is
15
18
  different than the current azure gem, which uses the older (XML) interface
16
19
  behind the scenes.
17
20
  EOF
18
- spec.homepage = 'http://github.com/ManageIQ/azure-armrest'
19
- spec.license = 'Apache 2.0'
20
21
 
21
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
- spec.bindir = "exe"
23
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
- spec.require_paths = ["lib"]
22
+ spec.add_dependency 'json'
23
+ spec.add_dependency 'rest-client'
24
+ spec.add_dependency 'cache_method', "~> 0.2.7"
25
25
 
26
26
  spec.add_development_dependency "bundler"
27
- spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rake"
28
28
  spec.add_development_dependency "rspec", "~> 3.0"
29
29
  spec.add_development_dependency "codeclimate-test-reporter"
30
-
31
- spec.add_dependency 'json'
32
- spec.add_dependency 'rest-client'
30
+ spec.add_development_dependency "timecop", "~> 0.7"
33
31
  end
@@ -1,6 +1,7 @@
1
1
  require 'rest-client'
2
2
  require 'json'
3
3
  require 'thread'
4
+ require 'uri'
4
5
 
5
6
  # The Azure module serves as a namespace.
6
7
  module Azure
@@ -21,12 +22,16 @@ module Azure
21
22
  end
22
23
 
23
24
  require 'azure/armrest/version'
24
- require 'azure/armrest/armrest_manager'
25
- require 'azure/armrest/storage_account_manager'
26
- require 'azure/armrest/availability_set_manager'
27
- require 'azure/armrest/virtual_machine_manager'
28
- require 'azure/armrest/virtual_machine_image_manager'
29
- require 'azure/armrest/virtual_machine_extension_manager'
30
- require 'azure/armrest/virtual_network_manager'
31
- require 'azure/armrest/subnet_manager'
32
- require 'azure/armrest/event_manager'
25
+ require 'azure/armrest/armrest_service'
26
+ require 'azure/armrest/storage_account_service'
27
+ require 'azure/armrest/availability_set_service'
28
+ require 'azure/armrest/virtual_machine_service'
29
+ require 'azure/armrest/virtual_machine_image_service'
30
+ require 'azure/armrest/virtual_machine_extension_service'
31
+ require 'azure/armrest/virtual_network_service'
32
+ require 'azure/armrest/subnet_service'
33
+ require 'azure/armrest/event_service'
34
+ require 'azure/armrest/template_deployment_service'
35
+ require 'azure/armrest/resource_service'
36
+ require 'azure/armrest/resource_group_service'
37
+ require 'azure/armrest/resource_provider_service'
@@ -0,0 +1,385 @@
1
+ module Azure
2
+ module Armrest
3
+ # Abstract base class for the other service classes.
4
+ class ArmrestService
5
+ ArmrestConfiguration = Struct.new(
6
+ :client_id,
7
+ :client_key,
8
+ :tenant_id,
9
+ :subscription_id,
10
+ :resource_group,
11
+ :api_version,
12
+ :grant_type,
13
+ :content_type,
14
+ :accept,
15
+ :token,
16
+ :token_expiration # token expiration local system date
17
+ ) do
18
+ @@tokens = Hash.new([])
19
+
20
+ def as_cache_key
21
+ "#{grant_type}_#{tenant_id}_#{client_id}_#{client_key}"
22
+ end
23
+
24
+ def token
25
+ self[:token], self[:token_expiration] = @@tokens[as_cache_key] if self[:token].nil?
26
+
27
+ if self[:token].nil? || Time.now > (self[:token_expiration] || Time.new(0))
28
+ self[:token], self[:token_expiration] = fetch_token
29
+ end
30
+ self[:token]
31
+ end
32
+
33
+ def fetch_token
34
+ token_url = Azure::Armrest::AUTHORITY + tenant_id + "/oauth2/token"
35
+
36
+ response = JSON.parse(RestClient.post(
37
+ token_url,
38
+ :grant_type => grant_type,
39
+ :client_id => client_id,
40
+ :client_secret => client_key,
41
+ :resource => Azure::Armrest::RESOURCE
42
+ ))
43
+ token = 'Bearer ' + response['access_token']
44
+ @@tokens[as_cache_key] = [token, Time.now + response['expires_in'].to_i]
45
+ end
46
+
47
+ private :fetch_token
48
+ end
49
+
50
+ # Configuration to access azure APIs
51
+ attr_accessor :armrest_configuration
52
+
53
+ # Base url used for REST calls.
54
+ attr_accessor :base_url
55
+
56
+ @@providers_hash = {} # Set in constructor
57
+
58
+ @@tokens = {} # token caches
59
+
60
+ @@subscriptions = {} # subscription caches
61
+
62
+ # Create a configuration object based on input options.
63
+ # This object can be used to create service objects.
64
+ #
65
+ # Possible options are:
66
+ #
67
+ # - client_id
68
+ # - client_key
69
+ # - tenant_id
70
+ # - subscription_id
71
+ # - resource_group
72
+ # - api_version
73
+ # - grant_type
74
+ # - content_type
75
+ # - accept
76
+ # - token,
77
+ # - token_expiration
78
+ #
79
+ # Of these, you should include a client_id, client_key and tenant_id.
80
+ # The resource_group can be specified here, but many methods allow you
81
+ # to specify a resource group if you prefer flexibility.
82
+ #
83
+ # If no subscription_id is provided then this method will attempt to find
84
+ # a list of associated subscriptions and use the first one it finds as
85
+ # the default. If no associated subscriptions are found, an ArgumentError
86
+ # is raised.
87
+ #
88
+ # The other options (grant_type, content_type, accept, token,
89
+ # token_expirationand api_version) should generally NOT be set by you
90
+ # except in specific circumstances. Setting them explicitly will likely
91
+ # cause breakage. Token and token_expiration must be set in pair.
92
+ # Token_expiration is of local system time.
93
+ # The api_version will typically be overridden on a per-provider/resource
94
+ # basis within subclasses anyway.
95
+ #
96
+ # You may need to associate your application with a subscription using
97
+ # the new portal or the New-AzureRoleAssignment powershell command.
98
+ #
99
+ def self.configure(options)
100
+ configuration = ArmrestConfiguration.new
101
+
102
+ options.each do |k,v|
103
+ configuration[k] = v
104
+ end
105
+
106
+ unless configuration.client_id && configuration.client_key
107
+ raise ArgumentError, "client_id and client_key must be specified"
108
+ end
109
+
110
+ configuration.api_version ||= '2015-01-01'
111
+ configuration.grant_type ||= 'client_credentials'
112
+ configuration.content_type ||= 'application/json'
113
+ configuration.accept ||= 'application/json'
114
+ configuration.subscription_id ||= fetch_subscription_id(configuration)
115
+
116
+ configuration
117
+ end
118
+
119
+ def self.fetch_subscription_id(config)
120
+ return @@subscriptions[config.as_cache_key] if @@subscriptions.has_key?(config.as_cache_key)
121
+
122
+ url = File.join(Azure::Armrest::RESOURCE, "subscriptions?api-version=#{config.api_version}")
123
+
124
+ response = RestClient.get(
125
+ url,
126
+ :content_type => config.content_type,
127
+ :authorization => config.token
128
+ )
129
+
130
+ hash = JSON.parse(response)["value"].first
131
+
132
+ raise ArgumentError, "No associated subscription found" if hash.empty?
133
+
134
+ id = hash.fetch("subscriptionId")
135
+ @@subscriptions[config.as_cache_key] = id
136
+ end
137
+
138
+ private_class_method :fetch_subscription_id
139
+
140
+ # Do not instantiate directly. This is an abstract base class from which
141
+ # all other service classes should subclass, and call super within their
142
+ # own constructors.
143
+ #
144
+ def initialize(armrest_configuration, _options)
145
+ self.armrest_configuration = armrest_configuration
146
+
147
+ # Base URL used for REST calls. Modify within method calls as needed.
148
+ @base_url = Azure::Armrest::RESOURCE
149
+
150
+ set_providers_info
151
+ end
152
+
153
+ # Returns a list of the available resource providers.
154
+ #
155
+ def providers
156
+ url = url_with_api_version(armrest_configuration.api_version, @base_url, 'providers')
157
+ resp = rest_get(url)
158
+ JSON.parse(resp.body)["value"]
159
+ end
160
+
161
+ # Returns information about the specific provider +namespace+.
162
+ #
163
+ def provider_info(provider)
164
+ url = url_with_api_version(armrest_configuration.api_version, @base_url, 'providers', provider)
165
+ response = rest_get(url)
166
+ JSON.parse(response.body)
167
+ end
168
+
169
+ alias geo_locations provider_info
170
+
171
+ # Returns a list of all locations for all resource types of the given
172
+ # +provider+. If you do not specify a provider, then the locations for
173
+ # all providers will be returned.
174
+ #
175
+ # If you need individual details on a per-provider basis, use the
176
+ # provider_info method instead.
177
+ #--
178
+ #
179
+ def locations(provider = nil)
180
+ hash = provider.nil? ? @@providers_hash : {provider => @@providers_hash[provider.downcase]}
181
+
182
+ hash.collect do |_provider, provider_data|
183
+ provider_data.collect { |_resource, resource_data| resource_data['locations'] }
184
+ end.flatten.uniq
185
+ end
186
+
187
+ # Returns a list of subscriptions for the tenant.
188
+ #
189
+ def subscriptions
190
+ url = url_with_api_version(armrest_configuration.api_version, @base_url, 'subscriptions')
191
+ resp = rest_get(url)
192
+ JSON.parse(resp.body)["value"]
193
+ end
194
+
195
+ # Return information for the specified subscription ID, or the
196
+ # subscription ID that was provided in the constructor if none is
197
+ # specified.
198
+ #
199
+ def subscription_info(subscription_id = armrest_configuration.subscription_id)
200
+ url = url_with_api_version(
201
+ armrest_configuration.api_version,
202
+ @base_url,
203
+ 'subscriptions',
204
+ armrest_configuration.subscription_id
205
+ )
206
+
207
+ resp = rest_get(url)
208
+ JSON.parse(resp.body)
209
+ end
210
+
211
+ # Returns a list of resources for the current subscription. If a
212
+ # +resource_group+ is provided, only list resources for that
213
+ # resource group.
214
+ #
215
+ def resources(resource_group = nil)
216
+ url_comps = [@base_url, 'subscriptions', armrest_configuration.subscription_id]
217
+ url_comps += ['resourcegroups', resource_group] if resource_group
218
+ url_comps << 'resources'
219
+
220
+ url = url_with_api_version(armrest_configuration.api_version, url_comps)
221
+ response = rest_get(url)
222
+
223
+ JSON.parse(response.body)["value"]
224
+ end
225
+
226
+ # Returns a list of resource groups for the current subscription.
227
+ #
228
+ def resource_groups
229
+ url = url_with_api_version(
230
+ armrest_configuration.api_version,
231
+ @base_url,
232
+ 'subscriptions',
233
+ armrest_configuration.subscription_id,
234
+ 'resourcegroups'
235
+ )
236
+ response = rest_get(url)
237
+ JSON.parse(response.body)["value"]
238
+ end
239
+
240
+ # Returns information on the specified +resource_group+ for the current
241
+ # subscription, or the resource group specified in the constructor if
242
+ # none is provided.
243
+ #
244
+ def resource_group_info(resource_group)
245
+ url = url_with_api_version(
246
+ armrest_configuration.api_version,
247
+ @base_url,
248
+ 'subscriptions',
249
+ armrest_configuration.subscription_id,
250
+ 'resourcegroups',
251
+ resource_group
252
+ )
253
+
254
+ resp = rest_get(url)
255
+ JSON.parse(resp.body)
256
+ end
257
+
258
+ # Returns a list of tags for the current subscription.
259
+ #
260
+ def tags
261
+ url = url_with_api_version(
262
+ armrest_configuration.api_version,
263
+ @base_url,
264
+ 'subscriptions',
265
+ armrest_configuration.subscription_id,
266
+ 'tagNames'
267
+ )
268
+ resp = rest_get(url)
269
+ JSON.parse(resp.body)["value"]
270
+ end
271
+
272
+ # Returns a list of tenants that can be accessed.
273
+ #
274
+ def tenants
275
+ url = url_with_api_version(armrest_configuration.api_version, @base_url, 'tenants')
276
+ resp = rest_get(url)
277
+ JSON.parse(resp.body)
278
+ end
279
+
280
+ private
281
+
282
+ # REST verb methods
283
+
284
+ def rest_get(url)
285
+ RestClient.get(
286
+ url,
287
+ :accept => armrest_configuration.accept,
288
+ :content_type => armrest_configuration.content_type,
289
+ :authorization => armrest_configuration.token,
290
+ )
291
+ end
292
+
293
+ def rest_put(url, body = '')
294
+ RestClient.put(
295
+ url,
296
+ body,
297
+ :accept => armrest_configuration.accept,
298
+ :content_type => armrest_configuration.content_type,
299
+ :authorization => armrest_configuration.token,
300
+ )
301
+ end
302
+
303
+ def rest_post(url, body = '')
304
+ RestClient.post(
305
+ url,
306
+ body,
307
+ :accept => armrest_configuration.accept,
308
+ :content_type => armrest_configuration.content_type,
309
+ :authorization => armrest_configuration.token,
310
+ )
311
+ end
312
+
313
+ def rest_patch(url, body = '')
314
+ RestClient.patch(
315
+ url,
316
+ body,
317
+ :accept => armrest_configuration.accept,
318
+ :content_type => armrest_configuration.content_type,
319
+ :authorization => armrest_configuration.token,
320
+ )
321
+ end
322
+
323
+ def rest_delete(url)
324
+ RestClient.delete(
325
+ url,
326
+ :accept => armrest_configuration.accept,
327
+ :content_type => armrest_configuration.content_type,
328
+ :authorization => armrest_configuration.token,
329
+ )
330
+ end
331
+
332
+ # Take an array of URI elements and join the together with the API version.
333
+ def url_with_api_version(api_version, *paths)
334
+ File.join(*paths) << "?api-version=#{api_version}"
335
+ end
336
+
337
+ # Build a one-time lookup table for each provider & resource. This
338
+ # lets subclasses set api-version strings properly for each method
339
+ # depending on whichever provider they're using.
340
+ #
341
+ # e.g. @@providers_hash['Microsoft.Compute']['virtualMachines']['api_version']
342
+ #
343
+ # Note that for methods that don't depend on a resource type should use
344
+ # armrest_configuration.api_version instead or set it explicitly as needed.
345
+ #
346
+ def set_providers_info
347
+ return unless @@providers_hash.empty?
348
+
349
+ providers.each do |info|
350
+ provider_info = {}
351
+ info['resourceTypes'].each do |resource|
352
+ provider_info[resource['resourceType']] = {
353
+ 'api_version' => resource['apiVersions'].first,
354
+ 'locations' => resource['locations'] - [''] # Ignore empty elements
355
+ }
356
+ end
357
+ @@providers_hash[info['namespace'].downcase] = provider_info
358
+ end
359
+ end
360
+
361
+ # Each Azure API call may require different api_version.
362
+ # The api_version in armrest_configuration is used for common methods provided
363
+ # by ArmrestService
364
+ #
365
+ # The options hash for each service's constructor can contain key-value pair
366
+ # api_version => version
367
+ # This version will be used for the service specific API calls
368
+ #
369
+ # Otherwise the service specific api_version is looked up from @@providers_hash
370
+ #
371
+ # Finally api_version in armrest_configuration is used if service specific version
372
+ # cannot be determined
373
+ def set_service_api_version(options, service)
374
+ @api_version =
375
+ if options.has_key?('api_version')
376
+ options['api_version']
377
+ elsif @@providers_hash.has_key?(@provider.downcase)
378
+ @@providers_hash[@provider.downcase][service]['api_version']
379
+ else
380
+ armrest_configuration.api_version
381
+ end
382
+ end
383
+ end # ArmrestService
384
+ end # Armrest
385
+ end # Azure