softlayer_api 2.2.2 → 3.0.b1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,34 +1,18 @@
1
- #
1
+ #--
2
2
  # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
3
  #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
- #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
22
6
 
23
7
  require 'configparser'
24
8
 
25
9
  module SoftLayer
26
10
 
27
- # The SoftLayer Config class is responsible for providing the key information
11
+ # The SoftLayer Config class is responsible for providing the key information
28
12
  # the library needs to communicate with the network SoftLayer API. Those three crucial
29
13
  # pieces of information are the Username, the API Key, and the endpoint_url. This information
30
14
  # is collected in a hash with the keys `:username`, `:api_key`, and `:endpoint_url` repsectively.
31
- #
15
+ #
32
16
  # The routine used to retrieve this information from a Config object is Config.client_settings
33
17
  #
34
18
  # There are several locations that the Config class looks for this information:
@@ -58,7 +42,7 @@ module SoftLayer
58
42
  #
59
43
  # = Environment Variables
60
44
  #
61
- # The config class will search the environment variables SL_USERNAME and SL_API_KEY for
45
+ # The config class will search the environment variables SL_USERNAME and SL_API_KEY for
62
46
  # the username and API key respectively. The endpoint_url may not be set thorugh
63
47
  # environment variables.
64
48
  #
@@ -0,0 +1,53 @@
1
+ #--
2
+ # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
+ #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
6
+
7
+ module SoftLayer
8
+ ##
9
+ # A Data Center in the SoftLayer network
10
+ #
11
+ # This class corresponds to the SoftLayer_Location++ data type:
12
+ #
13
+ # http://sldn.softlayer.com/reference/datatypes/SoftLayer_Location
14
+ #
15
+ # Although in this context it is used to represent a data center and
16
+ # not the more general class of locations that that data type can
17
+ # represent.
18
+
19
+ class Datacenter < SoftLayer::ModelBase
20
+ sl_attr :name
21
+ sl_attr :long_name, "longName"
22
+
23
+ ##
24
+ # Return the datacenter with the given name ('sng01' or 'dal05')
25
+ def self.datacenter_named(name, client = nil)
26
+ datacenters(client).find{ | datacenter | datacenter.name == name.to_s.downcase }
27
+ end
28
+
29
+ ##
30
+ # Return a list of all the datacenters
31
+ #
32
+ # If the client parameter is not provided, the routine
33
+ # will try to use Client::defult_client. If no client
34
+ # can be found, the routine will raise an exception
35
+ #
36
+ # This routine will only retrieve the list of datacenters from
37
+ # the network once and keep it in memory unless you
38
+ # pass in force_reload as true.
39
+ #
40
+ @@data_centers = nil
41
+ def self.datacenters(client = nil, force_reload = false)
42
+ softlayer_client = client || Client.default_client
43
+ raise "Datacenter.datacenters requires a client to call the network API" if !softlayer_client
44
+
45
+ if(!@@data_centers || force_reload)
46
+ datacenters_data = softlayer_client[:Location].getDatacenters
47
+ @@data_centers = datacenters_data.collect { | datacenter_data | self.new(softlayer_client, datacenter_data) }
48
+ end
49
+
50
+ @@data_centers
51
+ end
52
+ end
53
+ end
@@ -1,24 +1,8 @@
1
- #
1
+ #--
2
2
  # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
3
  #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
- #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
22
6
 
23
7
  module SoftLayer
24
8
 
@@ -0,0 +1,384 @@
1
+ #--
2
+ # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
+ #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
6
+
7
+
8
+ module SoftLayer
9
+ ##
10
+ # A Virtual Server Image Template.
11
+ #
12
+ # This class rougly corresponds to the unwieldily named
13
+ # +SoftLayer_Virtual_Guest_Block_Device_Template_Group+
14
+ # service:
15
+ #
16
+ # http://sldn.softlayer.com/reference/services/SoftLayer_Virtual_Guest_Block_Device_Template_Group
17
+ #
18
+ #
19
+ class ImageTemplate < SoftLayer::ModelBase
20
+ ##
21
+ # :attr_reader:
22
+ # The 'friendly name' given to the template when it was created
23
+ sl_attr :name
24
+
25
+ ##
26
+ # :attr_reader:
27
+ # The notes, if any, that are attached to the template. Can be nil.
28
+ sl_attr :notes, "note"
29
+
30
+ ##
31
+ # :attr_reader:
32
+ # The universally unique identifier (if any) for the template. Can be nil.
33
+ sl_attr :global_id, 'globalIdentifier'
34
+
35
+ # Change the name of the template
36
+ def rename!(new_name)
37
+ self.service.editObject({ "name" => new_name.to_s})
38
+ end
39
+
40
+ ##
41
+ # true if the image template is a flex image
42
+ # Note that the publicFlag property comes back as an integer (0 or 1)
43
+ def public?
44
+ self["publicFlag"] != 0
45
+ end
46
+
47
+ ##
48
+ # true if the image template is a flex image
49
+ # Note that the flexImageFlag property comes back as a boolean
50
+ def flex_image?
51
+ !!self["flexImageFlag"]
52
+ end
53
+
54
+ ##
55
+ # Changes the notes on an template to be the given strings
56
+ def notes=(new_notes)
57
+ # it is not a typo that this sets the "note" property. The
58
+ # property in the network api is "note", the model exposes it as
59
+ # 'notes' for self-consistency
60
+ self.service.editObject({ "note" => new_notes.to_s})
61
+ end
62
+
63
+ ##
64
+ # Returns an array of the tags set on the image
65
+ def tags
66
+ return self["tagReferences"].collect{ |tag_reference| tag_reference["tag"]["name"] }
67
+ end
68
+
69
+ ##
70
+ # Sets the tags on the template. Note: a pre-existing tag will be
71
+ # removed from the template if it does not appear in the array given.
72
+ # The list of tags must be comprehensive.
73
+ def tags=(tags_array)
74
+ as_strings = tags_array.collect { |tag| tag.to_s }
75
+ self.service.setTags(as_strings.join(','))
76
+ end
77
+
78
+ ##
79
+ # Returns the an array containing the datacenters where this image is available.
80
+ def datacenters
81
+ self["datacenters"].collect{ |datacenter_data| SoftLayer::Datacenter.datacenter_named(datacenter_data["name"])}
82
+ end
83
+
84
+ ##
85
+ # Accepts an array of datacenters (instances of SoftLayer::Datacenter) where this
86
+ # image should be made available. The call will kick off one or more transactions
87
+ # to make the image available in the given datacenters. These transactions can take
88
+ # some time to complete.
89
+ #
90
+ # Note that the template will be REMOVED from any datacenter that does not
91
+ # appear in this array! The list given must be comprehensive.
92
+ #
93
+ # The available_datacenters call returns a list of the values that are valid
94
+ # whithin this array.
95
+ def datacenters=(datacenters_array)
96
+ datacenter_data = datacenters_array.collect do |datacenter|
97
+ { "id" => datacenter.id }
98
+ end
99
+
100
+ self.service.setAvailableLocations(datacenter_data.compact)
101
+ end
102
+
103
+ ##
104
+ # Returns an array of the datacenters that this image can be stored in.
105
+ # This is the set of datacenters that you may choose from, when putting
106
+ # together a list you will send to the datacenters= setter.
107
+ #
108
+ def available_datacenters
109
+ datacenters_data = self.service.getStorageLocations()
110
+ datacenters_data.collect { |datacenter_data| SoftLayer::Datacenter.datacenter_named(datacenter_data["name"]) }
111
+ end
112
+
113
+
114
+ ##
115
+ # Returns a list of the accounts (identified by account ID numbers)
116
+ # that this image is shared with
117
+ def shared_with_accounts
118
+ accounts_data = self.service.getAccountReferences
119
+ accounts_data.collect { |account_data| account_data["accountId"] }
120
+ end
121
+
122
+ ##
123
+ # Change the set of accounts that this image is shared with.
124
+ # The parameter is an array of account ID's.
125
+ #
126
+ # Note that this routine will "unshare" with any accounts
127
+ # not included in the list passed in so the list should
128
+ # be comprehensive
129
+ #
130
+ def shared_with_accounts= (account_id_list)
131
+ already_sharing_with = self.shared_with_accounts
132
+
133
+ accounts_to_add = account_id_list.select { |account_id| !already_sharing_with.include?(account_id) }
134
+
135
+ # Note, using the network API, it is possible to "unshare" an image template
136
+ # with the account that owns it, however, this leads to a rather odd state
137
+ # where the image has allocated resources (that the account may be charged for)
138
+ # but no way to delete those resources. For that reason this model
139
+ # always includes the account ID that owns the image in the list of
140
+ # accounts the image will be shared with.
141
+ my_account_id = self['accountId']
142
+ accounts_to_add.push(my_account_id) if !already_sharing_with.include?(my_account_id) && !accounts_to_add.include?(my_account_id)
143
+
144
+ accounts_to_remove = already_sharing_with.select { |account_id| (account_id != my_account_id) && !account_id_list.include?(account_id) }
145
+
146
+ accounts_to_add.each {|account_id| self.service.permitSharingAccess account_id }
147
+ accounts_to_remove.each {|account_id| self.service.denySharingAccess account_id }
148
+ end
149
+
150
+ ##
151
+ # Creates a transaction to delete the image template and
152
+ # all the disk images associated with it.
153
+ #
154
+ # This is a final action and cannot be undone.
155
+ # the transaction will proceed immediately.
156
+ #
157
+ # Call it with extreme care!
158
+ def delete!
159
+ self.service.deleteObject
160
+ end
161
+
162
+ ##
163
+ # Repeatedly poll the netwokr API until transactions related to this image
164
+ # template are finished
165
+ #
166
+ # A template is not 'ready' until all the transactions on the template
167
+ # itself, and all its children are complete.
168
+ #
169
+ # At each trial, the routine will yield to a block if one is given
170
+ # The block is passed one parameter, a boolean flag indicating
171
+ # whether or not the image template is 'ready'.
172
+ #
173
+ def wait_until_ready(max_trials, seconds_between_tries = 2)
174
+ # pessimistically assume the server is not ready
175
+ num_trials = 0
176
+ begin
177
+ self.refresh_details()
178
+
179
+ parent_ready = !(has_sl_property? :transactionId) || (self[:transactionId] == "")
180
+ children_ready = (nil == self["children"].find { |child| child["transactionId"] != "" })
181
+
182
+ ready = parent_ready && children_ready
183
+ yield ready if block_given?
184
+
185
+ num_trials = num_trials + 1
186
+ sleep(seconds_between_tries) if !ready && (num_trials <= max_trials)
187
+ end until ready || (num_trials >= max_trials)
188
+
189
+ ready
190
+ end
191
+
192
+ # ModelBase protocol methods
193
+ def service
194
+ softlayer_client['Virtual_Guest_Block_Device_Template_Group'].object_with_id(self.id)
195
+ end
196
+
197
+ def softlayer_properties(object_mask = nil)
198
+ self.service.object_mask(self.class.default_object_mask).getObject
199
+ end
200
+
201
+ ##
202
+ # Retrieve a list of the private image templates from the account.
203
+ #
204
+ # The options parameter should contain:
205
+ #
206
+ # <b>+:client+</b> - The client used to connect to the API
207
+ #
208
+ # If no client is given, then the routine will try to use Client.default_client.
209
+ # If no client can be found the routine will raise an error.
210
+ #
211
+ # Additional options that may be provided:
212
+ # * <b>+:name+</b> (string) - Return templates with the given name
213
+ # * <b>+:global_id+</b> (string) - Return templates with the given global identfier
214
+ def self.find_private_templates(options_hash = {})
215
+ softlayer_client = options_hash[:client] || Client.default_client
216
+ raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
217
+
218
+ if(options_hash.has_key? :object_filter)
219
+ object_filter = options_hash[:object_filter]
220
+ raise "Expected an instance of SoftLayer::ObjectFilter" unless object_filter.kind_of?(SoftLayer::ObjectFilter)
221
+ else
222
+ object_filter = ObjectFilter.new()
223
+ end
224
+
225
+ option_to_filter_path = {
226
+ :name => "privateBlockDeviceTemplateGroups.name",
227
+ :global_id => "privateBlockDeviceTemplateGroups.globalIdentifier",
228
+ }
229
+
230
+ # For each of the options in the option_to_filter_path map, if the options hash includes
231
+ # that particular option, add a clause to the object filter that filters for the matching
232
+ # value
233
+ option_to_filter_path.each do |option, filter_path|
234
+ object_filter.modify { |filter| filter.accept(filter_path).when_it is(options_hash[option])} if options_hash[option]
235
+ end
236
+
237
+ # Tags get a much more complex object filter operation so we handle them separately
238
+ if options_hash.has_key?(:tags)
239
+ object_filter.set_criteria_for_key_path("privateBlockDeviceTemplateGroups.tagReferences.tag.name", {
240
+ 'operation' => 'in',
241
+ 'options' => [{
242
+ 'name' => 'data',
243
+ 'value' => options_hash[:tags].collect{ |tag_value| tag_value.to_s }
244
+ }]
245
+ } );
246
+ end
247
+
248
+ account_service = softlayer_client['Account']
249
+ account_service = account_service.object_filter(object_filter) unless object_filter.empty?
250
+ account_service = account_service.object_mask(default_object_mask)
251
+
252
+ if options_hash.has_key? :object_mask
253
+ account_service = account_service.object_mask(options_hash[:object_mask])
254
+ end
255
+
256
+ if options_hash.has_key?(:result_limit)
257
+ offset = options[:result_limit][:offset]
258
+ limit = options[:result_limit][:limit]
259
+
260
+ account_service = account_service.result_limit(offset, limit)
261
+ end
262
+
263
+ templates_data = account_service.getPrivateBlockDeviceTemplateGroups
264
+ templates_data.collect { |template_data| new(softlayer_client, template_data) }
265
+ end
266
+
267
+ ##
268
+ # Retrieve a list of public image templates
269
+ #
270
+ # The options parameter should contain:
271
+ #
272
+ # <b>+:client+</b> - The client used to connect to the API
273
+ #
274
+ # If no client is given, then the routine will try to use Client.default_client
275
+ # If no client can be found the routine will raise an error.
276
+ #
277
+ # Additional options that may be provided:
278
+ # * <b>+:name+</b> (string) - Return templates with the given name
279
+ # * <b>+:global_id+</b> (string) - Return templates with the given global identfier
280
+ def self.find_public_templates(options_hash = {})
281
+ softlayer_client = options_hash[:client] || Client.default_client
282
+ raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
283
+
284
+ if(options_hash.has_key? :object_filter)
285
+ object_filter = options_hash[:object_filter]
286
+ raise "Expected an instance of SoftLayer::ObjectFilter" unless object_filter.kind_of?(SoftLayer::ObjectFilter)
287
+ else
288
+ object_filter = ObjectFilter.new()
289
+ end
290
+
291
+ option_to_filter_path = {
292
+ :name => "publicImages.name",
293
+ :global_id => "publicImages.globalIdentifier",
294
+ }
295
+
296
+ # For each of the options in the option_to_filter_path map, if the options hash includes
297
+ # that particular option, add a clause to the object filter that filters for the matching
298
+ # value
299
+ option_to_filter_path.each do |option, filter_path|
300
+ object_filter.modify { |filter| filter.accept(filter_path).when_it is(options_hash[option])} if options_hash[option]
301
+ end
302
+
303
+ # Tags get a much more complex object filter operation so we handle them separately
304
+ if options_hash.has_key?(:tags)
305
+ object_filter.set_criteria_for_key_path("publicImages.tagReferences.tag.name", {
306
+ 'operation' => 'in',
307
+ 'options' => [{
308
+ 'name' => 'data',
309
+ 'value' => options_hash[:tags].collect{ |tag_value| tag_value.to_s }
310
+ }]
311
+ } );
312
+ end
313
+
314
+ template_service = softlayer_client['Virtual_Guest_Block_Device_Template_Group']
315
+ template_service = template_service.object_filter(object_filter) unless object_filter.empty?
316
+ template_service = template_service.object_mask(default_object_mask)
317
+
318
+ if options_hash.has_key? :object_mask
319
+ template_service = template_service.object_mask(options_hash[:object_mask])
320
+ end
321
+
322
+ if options_hash.has_key?(:result_limit)
323
+ offset = options[:result_limit][:offset]
324
+ limit = options[:result_limit][:limit]
325
+
326
+ template_service = template_service.result_limit(offset, limit)
327
+ end
328
+
329
+ templates_data = template_service.getPublicImages
330
+ templates_data.collect { |template_data| new(softlayer_client, template_data) }
331
+ end
332
+
333
+ ##
334
+ # Retrive the Image Template with the given ID
335
+ # (Note! This is the service ID, not the globalIdentifier!)
336
+ #
337
+ # The options parameter should contain:
338
+ #
339
+ # <b>+:client+</b> - The client used to connect to the API
340
+ #
341
+ # If no client is given, then the routine will try to use Client.default_client
342
+ # If no client can be found the routine will raise an error.
343
+ #
344
+ # The options may include the following keys
345
+ # * <b>+:object_mask+</b> (string) - A object mask of properties, in addition to the default properties, that you wish to retrieve for the template
346
+ def self.template_with_id(id, options_hash = {})
347
+ softlayer_client = options_hash[:client] || Client.default_client
348
+ raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
349
+
350
+ service = softlayer_client['Virtual_Guest_Block_Device_Template_Group'].object_with_id(id)
351
+ service.object_mask(default_object_mask)
352
+
353
+ if options_hash.has_key? :object_mask
354
+ service = service.object_mask(options_hash[:object_mask])
355
+ end
356
+
357
+ template_data = service.getObject
358
+ new(softlayer_client, template_data)
359
+ end
360
+
361
+ ##
362
+ # Retrieve the image template with the given global ID
363
+ #
364
+ # The options parameter should contain:
365
+ #
366
+ # <b>+:client+</b> - The client used to connect to the API
367
+ #
368
+ # If no client is given, then the routine will try to use Client.default_client
369
+ # If no client can be found the routine will raise an error.
370
+ #
371
+ # The options may include the following keys
372
+ # * <b>+:object_mask+</b> (string) - A object mask of properties, in addition to the default properties, that you wish to retrieve for the template
373
+ def self.template_with_global_id(global_id, options_hash = {})
374
+ templates = find_public_templates(options_hash.merge(:global_id => global_id))
375
+ templates.empty? ? nil : templates[0]
376
+ end
377
+
378
+ protected
379
+
380
+ def self.default_object_mask
381
+ return "mask[id,accountId,name,note,globalIdentifier,datacenters,blockDevices,tagReferences,publicFlag,flexImageFlag,transactionId,children.transactionId]"
382
+ end
383
+ end
384
+ end