softlayer_api 2.2.2 → 3.0.b1

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