softlayer_api 2.0.1 → 2.1.0

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.
@@ -0,0 +1,196 @@
1
+ #
2
+ # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
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
+ #
22
+
23
+ module SoftLayer
24
+ ##
25
+ # Each SoftLayer ProductPackage provides information about ordering a product
26
+ # or service from SoftLayer.
27
+ #
28
+ # === Product Item Categories
29
+ # A important companion to Product Packages are ProductItemCategories.
30
+ # Each ProductItemCategory represents a set of options that you may choose from when
31
+ # configuring an attribute of the product or service you are ordering.
32
+ #
33
+ # ProductItemCategories are identified by +categoryCode+. Examples of category codes
34
+ # include 'os', 'ram', and 'port_speed'.
35
+ #
36
+ # For example, in a package for ordering a server, the 'os' ProductItemCategory contains
37
+ # the available choices for operating systems that may be provisioned on the server.
38
+ #
39
+ # When you construct an order based on that package, you will make one selection from
40
+ # the 'os' category and put it into the order.
41
+ #
42
+ # === Package Configuration
43
+ # A package also has a Configuration. A Configuration specifies which
44
+ # Categories are valid in an order and, more importantly, which Categories
45
+ # are **required** in any order that uses the ProductPackage.
46
+ #
47
+ # When constructing an order, you **must** provide an option for each of the Categories
48
+ # that the Configuration marks as required (and you must supply a value even if the
49
+ # Category only has one choice)
50
+ #
51
+ class ProductPackage < ModelBase
52
+ include ::SoftLayer::DynamicAttribute
53
+
54
+ ##
55
+ # A friendly, readable name for the package
56
+ sl_attr :name
57
+
58
+ ##
59
+ # The list of locations where this product package is available.
60
+ sl_attr :availableLocations
61
+
62
+ ##
63
+ # The set of product categories needed to make an order for this product package.
64
+ #
65
+ sl_dynamic_attr :configuration do |resource|
66
+ resource.should_update? do
67
+ # only retrieved once per instance
68
+ @configuration == nil
69
+ end
70
+
71
+ resource.to_update do
72
+ #
73
+ # We call +SoftLayer_Product_Package+ to get the configuration for this package.
74
+ #
75
+ # Unfortunately, even though this call includes +SoftLayer_Product_Item_Category+ entities, it does not have the context
76
+ # needed to find the active price items for that category.
77
+ #
78
+ # Instead, we make a second call, this time to +SoftLayer_Product_Package::getCategories+. That method incorporates a complex
79
+ # filtering mechanism on the server side to give us a list of the categories, groups, and prices that are valid for the current
80
+ # account at the current time. We construct the ProductItemCategory objects from the results we get back.
81
+ #
82
+ configuration_data = softlayer_client['Product_Package'].object_with_id(self.id).object_mask("mask[isRequired,itemCategory.categoryCode]").getConfiguration()
83
+
84
+ # We sort of invert the information and create a map from category codes to a boolean representing
85
+ # whether or not they are required.
86
+ required_by_category_code = configuration_data.inject({}) do |required_by_category_code, config_category|
87
+ required_by_category_code[config_category['itemCategory']['categoryCode']] = (config_category['isRequired'] != 0)
88
+ required_by_category_code
89
+ end
90
+
91
+ # This call to getCategories is the one that does lots of fancy back-end filtering for us
92
+ categories_data = softlayer_client['Product_Package'].object_with_id(self.id).getCategories()
93
+
94
+ # Run though the categories and for each one that's in our config, create a SoftLayer::ProductItemCategory object.
95
+ # Conveniently the +keys+ of the required_by_category_code gives us a list of the category codes in the configuration
96
+ config_categories = required_by_category_code.keys
97
+ categories_data.collect do |category_data|
98
+ if config_categories.include? category_data['categoryCode']
99
+ SoftLayer::ProductItemCategory.new(softlayer_client, category_data, required_by_category_code[category_data['categoryCode']])
100
+ else
101
+ nil
102
+ end
103
+ end.compact
104
+ end
105
+ end
106
+
107
+ ##
108
+ # Returns an array of the required categories in this package
109
+ def required_categories
110
+ configuration.select { |category| category.required? }
111
+ end
112
+
113
+ ##
114
+ # Returns the product category with the given category code (or nil if one cannot be found)
115
+ def category(category_code)
116
+ configuration.find { |category| category.categoryCode == category_code }
117
+ end
118
+
119
+ def datacenter_options
120
+ availableLocations.collect { |location_data| location_data["location"]["name"] }
121
+ end
122
+
123
+ ##
124
+ # Given a datacenter name that was returned by datacenter_options, use information
125
+ # in the package to retrieve a location id.
126
+ def location_id_for_datacenter_name(datacenter_name)
127
+ location_data = availableLocations.find { |location_data| location_data["location"]["name"] == datacenter_name }
128
+ location_data["locationId"]
129
+ end
130
+
131
+ def service
132
+ softlayer_client['Product_Package'].object_with_id(self.id)
133
+ end
134
+
135
+ ##
136
+ # Requests a list (array) of ProductPackages whose key names match the
137
+ # one passed in.
138
+ #
139
+ def self.packages_with_key_name(key_name, client = nil)
140
+ softlayer_client = client || Client.default_client
141
+ raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
142
+
143
+ filter = SoftLayer::ObjectFilter.build('type.keyName') { is(key_name) }
144
+ filtered_service = softlayer_client['Product_Package'].object_filter(filter).object_mask(self.default_object_mask('mask'))
145
+ packages_data = filtered_service.getAllObjects
146
+ packages_data.collect { |package_data| ProductPackage.new(softlayer_client, package_data) }
147
+ end
148
+
149
+ ##
150
+ # Requests a list (array) of ProductPackages whose key names match the
151
+ # one passed in.
152
+ #
153
+ def self.package_with_id(package_id, client = nil)
154
+ softlayer_client = client || Client.default_client
155
+ raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
156
+
157
+ package_data = softlayer_client['Product_Package'].object_with_id(package_id).object_mask(self.default_object_mask('mask')).getObject
158
+ ProductPackage.new(softlayer_client, package_data)
159
+ end
160
+
161
+ ##
162
+ # Returns the ProductPackage of the package used to order virtual servers
163
+ # At the time of this writing, the code assumes this package is unique
164
+ #
165
+ # 'VIRTUAL_SERVER_INSTANCE' is a "well known" constant for this purpose
166
+ def self.virtual_server_package(client = nil)
167
+ packages_with_key_name('VIRTUAL_SERVER_INSTANCE', client).first
168
+ end
169
+
170
+ ##
171
+ # Returns the ProductPackage of the package used to order Bare Metal Servers
172
+ # with simplified configuration options.
173
+ #
174
+ # At the time of this writing, the code assumes this package is unique
175
+ #
176
+ # 'BARE_METAL_CORE' is a "well known" constant for this purpose
177
+ def self.bare_metal_instance_package(client = nil)
178
+ packages_with_key_name('BARE_METAL_CORE', client).first
179
+ end
180
+
181
+ ##
182
+ # Returns an array of ProductPackages, each of which can be used
183
+ # as the foundation to order a bare metal server.
184
+ #
185
+ # 'BARE_METAL_CPU' is a "well known" constant for this purpose
186
+ def self.bare_metal_server_packages(client = nil)
187
+ packages_with_key_name('BARE_METAL_CPU', client)
188
+ end
189
+
190
+ protected
191
+
192
+ def self.default_object_mask(root)
193
+ "#{root}[id,name,description,availableLocations.location]"
194
+ end
195
+ end
196
+ end # SoftLayer
@@ -0,0 +1,245 @@
1
+ #
2
+ # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
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
+ #
22
+
23
+ module SoftLayer
24
+ # Server is the base class for VirtualServer and BareMetalServer.
25
+ # It implements some functionality common to both those classes.
26
+ #
27
+ # Server is an abstract class and you should not create them
28
+ # directly.
29
+ #
30
+ # While VirtualServer and BareMetalServer each have analogs
31
+ # in the SoftLayer API, those analogs do not share a direct
32
+ # ancestry. As a result there is no SoftLayer API analog
33
+ # to this class.
34
+ class Server < SoftLayer::ModelBase
35
+
36
+ ##
37
+ # :attr_reader:
38
+ # The host name SoftLayer has stored for the server
39
+ sl_attr :hostname
40
+
41
+ ##
42
+ # :attr_reader:
43
+ # The domain name SoftLayer has stored for the server
44
+ sl_attr :domain
45
+
46
+ ##
47
+ # :attr_reader:
48
+ # A convenience attribute that combines the hostname and domain name
49
+ sl_attr :fullyQualifiedDomainName
50
+
51
+ ##
52
+ # :attr_reader:
53
+ # The data center where the server is located
54
+ sl_attr :datacenter
55
+
56
+ ##
57
+ # :attr_reader:
58
+ # The IP address of the primary public interface for the server
59
+ sl_attr :primary_public_ip, "primaryIpAddress"
60
+
61
+ ##
62
+ # :attr_reader:
63
+ # The IP address of the primary private interface for the server
64
+ sl_attr :primary_private_ip, "primaryBackendIpAddress"
65
+
66
+ ##
67
+ # :attr_reader:
68
+ # Notes about these server (for use by the customer)
69
+ sl_attr :notes
70
+
71
+ ##
72
+ # Construct a server from the given client using the network data found in +network_hash+
73
+ #
74
+ # Most users should not have to call this method directly. Instead you should access the
75
+ # servers property of an Account object, or use methods like BareMetalServer#find_servers
76
+ # or VirtualServer#find_servers
77
+ #
78
+ def initialize(softlayer_client, network_hash)
79
+ if self.class == Server
80
+ raise RuntimeError, "The Server class is an abstract base class and should not be instantiated directly"
81
+ else
82
+ super
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Make an API request to SoftLayer and return the latest properties hash
88
+ # for this object.
89
+ def softlayer_properties(object_mask = nil)
90
+ my_service = self.service
91
+
92
+ if(object_mask)
93
+ my_service = my_service.object_mask(object_mask)
94
+ else
95
+ my_service = my_service.object_mask(self.class.default_object_mask.to_sl_object_mask)
96
+ end
97
+
98
+ my_service.getObject()
99
+ end
100
+
101
+ ##
102
+ # Change the notes of the server
103
+ # raises ArgumentError if you pass nil as the notes
104
+ def notes=(new_notes)
105
+ raise ArgumentError.new("The new notes cannot be nil") unless new_notes
106
+
107
+ edit_template = {
108
+ "notes" => new_notes
109
+ }
110
+
111
+ self.service.editObject(edit_template)
112
+ self.refresh_details()
113
+ end
114
+
115
+ ##
116
+ # Change the user metadata for the server.
117
+ #
118
+ def user_metadata=(new_metadata)
119
+ raise ArgumentError.new("Cannot set user metadata to nil") unless new_metadata
120
+ self.service.setUserMetadata([new_metadata])
121
+ self.refresh_details()
122
+ end
123
+
124
+ ##
125
+ # Change the hostname of this server
126
+ # Raises an ArgumentError if the new hostname is nil or empty
127
+ #
128
+ def set_hostname!(new_hostname)
129
+ raise ArgumentError.new("The new hostname cannot be nil") unless new_hostname
130
+ raise ArgumentError.new("The new hostname cannot be empty") if new_hostname.empty?
131
+
132
+ edit_template = {
133
+ "hostname" => new_hostname
134
+ }
135
+
136
+ self.service.editObject(edit_template)
137
+ self.refresh_details()
138
+ end
139
+
140
+ ##
141
+ # Change the domain of this server
142
+ #
143
+ # Raises an ArgumentError if the new domain is nil or empty
144
+ # no further validation is done on the domain name
145
+ #
146
+ def set_domain!(new_domain)
147
+ raise ArgumentError.new("The new hostname cannot be nil") unless new_domain
148
+ raise ArgumentError.new("The new hostname cannot be empty") if new_domain.empty?
149
+
150
+ edit_template = {
151
+ "domain" => new_domain
152
+ }
153
+
154
+ self.service.editObject(edit_template)
155
+ self.refresh_details()
156
+ end
157
+
158
+ ##
159
+ # Change the current port speed of the server
160
+ #
161
+ # +new_speed+ is expressed Mbps and should be 0, 10, 100, or 1000.
162
+ # Ports have a maximum speed that will limit the actual speed set
163
+ # on the port.
164
+ #
165
+ # Set +public+ to +false+ in order to change the speed of the
166
+ # primary private network interface.
167
+ #
168
+ def change_port_speed(new_speed, public = true)
169
+ if public
170
+ self.service.setPublicNetworkInterfaceSpeed(new_speed)
171
+ else
172
+ self.service.setPrivateNetworkInterfaceSpeed(new_speed)
173
+ end
174
+
175
+ self.refresh_details()
176
+ self
177
+ end
178
+
179
+ ##
180
+ # Begins an OS reload on this server.
181
+ #
182
+ # The OS reload can wipe out the data on your server so this method uses a
183
+ # confirmation mechanism built into the underlying SoftLayer API. If you
184
+ # call this method once without a token, it will not actually start the
185
+ # reload. Instead it will return a token to you. That token is good for
186
+ # 10 minutes. If you call this method again and pass that token **then**
187
+ # the OS reload will actually begin.
188
+ #
189
+ # If you wish to force the OS Reload and bypass the token safety mechanism
190
+ # pass the token 'FORCE' as the first parameter. If you do so
191
+ # the reload will proceed immediately.
192
+ #
193
+ def reload_os!(token = '', provisioning_script_uri = nil, ssh_keys = nil)
194
+ configuration = {}
195
+
196
+ configuration['customProvisionScriptUri'] = provisioning_script_uri if provisioning_script_uri
197
+ configuration['sshKeyIds'] = ssh_keys if ssh_keys
198
+
199
+ self.service.reloadOperatingSystem(token, configuration)
200
+ end
201
+
202
+ def to_s
203
+ result = super
204
+ if respond_to?(:hostname) then
205
+ result.sub!('>', ", #{hostname}>")
206
+ end
207
+ result
208
+ end
209
+
210
+ protected
211
+
212
+ # returns the default object mask for all servers
213
+ # structured as a hash so that the classes BareMetalServer
214
+ # and VirtualServer can use hash construction techniques to
215
+ # specialize the mask for their use.
216
+ def self.default_object_mask
217
+ { "mask" => [
218
+ 'id',
219
+ 'globalIdentifier',
220
+ 'notes',
221
+ 'hostname',
222
+ 'domain',
223
+ 'fullyQualifiedDomainName',
224
+ 'datacenter',
225
+ 'primaryIpAddress',
226
+ 'primaryBackendIpAddress',
227
+ { 'operatingSystem' => {
228
+ 'softwareLicense.softwareDescription' => ['manufacturer', 'name', 'version','referenceCode'],
229
+ 'passwords' => ['username','password']
230
+ }
231
+ },
232
+ 'privateNetworkOnlyFlag',
233
+ 'userData',
234
+ 'networkComponents.primarySubnet[id, netmask, broadcastAddress, networkIdentifier, gateway]',
235
+ 'billingItem[id,recurringFee]',
236
+ 'hourlyBillingFlag',
237
+ 'tagReferences[id,tag[name,id]]',
238
+ 'networkVlans[id,vlanNumber,networkSpace]',
239
+ 'postInstallScriptUri'
240
+ ]
241
+ }
242
+ end
243
+
244
+ end # class Server
245
+ end # module SoftLayer
@@ -1,4 +1,3 @@
1
- #
2
1
  # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
2
  #
4
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -51,7 +50,6 @@ class XMLRPC::Client
51
50
  end
52
51
 
53
52
  module SoftLayer
54
-
55
53
  # = SoftLayer API Service
56
54
  #
57
55
  # Instances of this class are the runtime representation of
@@ -87,7 +85,7 @@ module SoftLayer
87
85
  attr_reader :service_name
88
86
  attr_reader :client
89
87
 
90
- def initialize(service_name, options = {})
88
+ def initialize (service_name, options = {})
91
89
  raise ArgumentError,"Please provide a service name" if service_name.nil? || service_name.empty?
92
90
 
93
91
  # remember the service name
@@ -137,6 +135,13 @@ using either client.service_named('<service_name_here>') or client['<service_nam
137
135
  @client.service_named(service_name)
138
136
  end
139
137
 
138
+ # Added here so that the interface of this class matches that
139
+ # of APIParameterFilter. In APIParameterFilter the target is
140
+ # a service. In a service, the target is itself.
141
+ def target
142
+ return self
143
+ end
144
+
140
145
  # Use this as part of a method call chain to identify a particular
141
146
  # object as the target of the request. The parameter is the SoftLayer
142
147
  # object identifier you are interested in. For example, this call
@@ -165,7 +170,7 @@ using either client.service_named('<service_name_here>') or client['<service_nam
165
170
  # Use this as part of a method call chain to reduce the number
166
171
  # of results returned from the server. For example, if the server has a list
167
172
  # of 100 entities and you only want 5 of them, you can get the first five
168
- # by using result_limit(0,5). Then for the next 5 you would use
173
+ # by using result_limit(0,5). Then for the next 5 you would use
169
174
  # result_limit(5,5), then result_limit(10,5) etc.
170
175
  def result_limit(offset, limit)
171
176
  proxy = APIParameterFilter.new(self)
@@ -234,11 +239,9 @@ using either client.service_named('<service_name_here>') or client['<service_nam
234
239
  end
235
240
 
236
241
  # Object masks go into the headers too.
237
- if parameters && parameters.server_object_mask && parameters.server_object_mask.count != 0
242
+ if parameters && parameters.server_object_mask
238
243
  object_mask = parameters.server_object_mask
239
- object_mask_string = object_mask.count == 1 ? object_mask[0] : "[#{object_mask.join(',')}]"
240
-
241
- additional_headers.merge!("SoftLayer_ObjectMask" => { "mask" => object_mask_string }) unless object_mask_string.empty?
244
+ additional_headers.merge!("SoftLayer_ObjectMask" => { "mask" => object_mask }) unless object_mask.empty?
242
245
  end
243
246
 
244
247
  # Result limits go into the headers
@@ -284,7 +287,7 @@ using either client.service_named('<service_name_here>') or client['<service_nam
284
287
  # If this is not defined for Service, then when you print a service object
285
288
  # the code will try to convert it to an array and end up calling method_missing
286
289
  #
287
- # We define this here to prevent odd calls to the Softlayer API
290
+ # We define this here to prevent odd calls to the SoftLayer API
288
291
  def to_ary
289
292
  nil
290
293
  end