softlayer_api 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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