softlayer_api 1.0.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,321 @@
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
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+ #
21
+
22
+ require 'xmlrpc/client'
23
+
24
+ # The XML-RPC spec calls for the "faultCode" in faults to be an integer
25
+ # but the SoftLayer XML-RPC API can return strings as the "faultCode"
26
+ #
27
+ # We monkey patch the module method XMLRPC::FaultException::Convert::fault
28
+ # so that it does pretty much what the default does without checking
29
+ # to ensure that the faultCode is an integer
30
+ module XMLRPC::Convert
31
+ def self.fault(hash)
32
+ if hash.kind_of? Hash and hash.size == 2 and
33
+ hash.has_key? "faultCode" and hash.has_key? "faultString" and
34
+ (hash["faultCode"].kind_of?(Integer) || hash["faultCode"].kind_of?(String)) and hash["faultString"].kind_of? String
35
+
36
+ XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
37
+ else
38
+ super
39
+ end
40
+ end
41
+ end
42
+
43
+ # The XMLRPC client uses a fixed user agent string, but we want to
44
+ # supply our own, so we add a method to XMLRPC::Client that lets
45
+ # us change it.
46
+ class XMLRPC::Client
47
+ def self.set_user_agent(new_agent)
48
+ remove_const(:USER_AGENT) if const_defined?(:USER_AGENT)
49
+ const_set(:USER_AGENT, new_agent)
50
+ end
51
+ end
52
+
53
+ module SoftLayer
54
+
55
+ # = SoftLayer API Service
56
+ #
57
+ # Instances of this class are the runtime representation of
58
+ # services in the SoftLayer API. They handle communication with
59
+ # the SoftLayer servers.
60
+ #
61
+ # You typically should not need to create services directly.
62
+ # instead, you should be creating a client and then using it to
63
+ # obtain individual services. For example:
64
+ #
65
+ # client = SoftLayer::Client.new(:username => "Joe", :api_key=>"feeddeadbeefbadfood...")
66
+ # account_service = client.service_named("Account") # returns the SoftLayer_Account service
67
+ # account_service = client['Account'] # Exactly the same as above
68
+ #
69
+ # For backward compatibility, a service can be constructed by passing
70
+ # client initialization options, however if you do so you will need to
71
+ # prepend the "SoftLayer_" on the front of the service name. For Example:
72
+ #
73
+ # account_service = SoftLayer::Service("SoftLayer_Account",
74
+ # :username=>"<your user name here>"
75
+ # :api_key=>"<your api key here>")
76
+ #
77
+ # A service communicates with the SoftLayer API through the the XML-RPC
78
+ # interface using Ruby's built in classes
79
+ #
80
+ # Once you have a service, you can invoke methods in the service like this:
81
+ #
82
+ # account_service.getOpenTickets
83
+ # => {... lots of information here representing the list of open tickets ...}
84
+ #
85
+ class Service
86
+ # The name of the service that this object calls. Cannot be emtpy or nil.
87
+ attr_reader :service_name
88
+ attr_reader :client
89
+
90
+ def initialize(service_name, options = {})
91
+ raise ArgumentError,"Please provide a service name" if service_name.nil? || service_name.empty?
92
+
93
+ # remember the service name
94
+ @service_name = service_name;
95
+
96
+ # Collect the keys relevant to client creation and pass them on to construct
97
+ # the client if one is needed.
98
+ client_keys = [:username, :api_key, :endpoint_url]
99
+ client_options = options.inject({}) do |new_hash, pair|
100
+ if client_keys.include? pair[0]
101
+ new_hash[pair[0]] = pair[1]
102
+ end
103
+
104
+ new_hash
105
+ end
106
+
107
+ # if the options hash already has a client
108
+ # go ahead and use it
109
+ if options.has_key? :client
110
+ if !client_options.empty?
111
+ raise RuntimeError, "Attempting to construct a service both with a client, and with client initialization options. Only one or the other should be provided."
112
+ end
113
+
114
+ @client = options[:client]
115
+ else
116
+ # Accepting client initialization options here
117
+ # is a backward-compatibility feature.
118
+
119
+ if $DEBUG
120
+ $stderr.puts %q{
121
+ Creating services with Client initialization options is deprecated and may be removed
122
+ in a future release. Please change your code to create a client and obtain a service
123
+ using either client.service_named('<service_name_here>') or client['<service_name_here>']}
124
+ end
125
+
126
+ @client = SoftLayer::Client.new(client_options)
127
+ end
128
+
129
+ # this has proven to be very helpful during debugging. It helps prevent infinite recursion
130
+ # when you don't get a method call just right
131
+ @method_missing_call_depth = 0 if $DEBUG
132
+ end
133
+
134
+ # Returns a related service with the given service name. The related service
135
+ # will use the same client as this service
136
+ def related_service_named(service_name)
137
+ @client.service_named(service_name)
138
+ end
139
+
140
+ # Use this as part of a method call chain to identify a particular
141
+ # object as the target of the request. The parameter is the SoftLayer
142
+ # object identifier you are interested in. For example, this call
143
+ # would return the ticket whose ID is 35212
144
+ #
145
+ # ticket_service.object_with_id(35212).getObject
146
+ #
147
+ def object_with_id(object_of_interest)
148
+ proxy = APIParameterFilter.new(self)
149
+ return proxy.object_with_id(object_of_interest)
150
+ end
151
+
152
+ # Use this as part of a method call chain to add an object mask to
153
+ # the request. The arguments to object mask should be well formed
154
+ # Extended Object Mask strings:
155
+ #
156
+ # ticket_service.object_mask("mask[ticket.createDate, ticket.modifyDate]", "mask(SoftLayer_Some_Type).aProperty").getObject
157
+ #
158
+ # The object_mask becomes part of the request sent to the server
159
+ #
160
+ def object_mask(*args)
161
+ proxy = APIParameterFilter.new(self)
162
+ return proxy.object_mask(*args)
163
+ end
164
+
165
+ # Use this as part of a method call chain to reduce the number
166
+ # of results returned from the server. For example, if the server has a list
167
+ # 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
169
+ # result_limit(5,5), then result_limit(10,5) etc.
170
+ def result_limit(offset, limit)
171
+ proxy = APIParameterFilter.new(self)
172
+ return proxy.result_limit(offset, limit)
173
+ end
174
+
175
+ # Add an object filter to the request.
176
+ def object_filter(filter)
177
+ proxy = APIParameterFilter.new(self)
178
+ return proxy.object_filter(filter)
179
+ end
180
+
181
+ # This is the primary mechanism by which requests are made. If you call
182
+ # the service with a method it doesn't understand, it will send a call to
183
+ # the endpoint for a method of the same name.
184
+ def method_missing(method_name, *args, &block)
185
+ # During development, if you end up with a stray name in some
186
+ # code, you can end up in an infinite recursive loop as method_missing
187
+ # tries to resolve that name (believe me... it happens).
188
+ # This mechanism looks for what it considers to be an unreasonable call
189
+ # depth and kills the loop quickly.
190
+ if($DEBUG)
191
+ @method_missing_call_depth += 1
192
+ if @method_missing_call_depth > 3 # 3 is somewhat arbitrary... really the call depth should only ever be 1
193
+ @method_missing_call_depth = 0
194
+ raise "stop infinite recursion #{method_name}, #{args.inspect}"
195
+ end
196
+ end
197
+
198
+ # if we're in debug mode, we put out a little helpful information
199
+ puts "SoftLayer::Service#method_missing called #{method_name}, #{args.inspect}" if $DEBUG
200
+
201
+ if(!block && method_name.to_s.match(/[[:alnum:]]+/))
202
+ result = call_softlayer_api_with_params(method_name, nil, args);
203
+ else
204
+ result = super
205
+ end
206
+
207
+ if($DEBUG)
208
+ @method_missing_call_depth -= 1
209
+ end
210
+
211
+ return result
212
+ end
213
+
214
+ # Issue an HTTP request to call the given method from the SoftLayer API with
215
+ # the parameters and arguments given.
216
+ #
217
+ # Parameters are information _about_ the call, the object mask or the
218
+ # particular object in the SoftLayer API you are calling.
219
+ #
220
+ # Arguments are the arguments to the SoftLayer method that you wish to
221
+ # invoke.
222
+ #
223
+ # This is intended to be used in the internal
224
+ # processing of method_missing and need not be called directly.
225
+ def call_softlayer_api_with_params(method_name, parameters, args)
226
+ additional_headers = {};
227
+
228
+ # The client knows about authentication, so ask him for the auth headers
229
+ authentication_headers = self.client.authentication_headers
230
+ additional_headers.merge!(authentication_headers)
231
+
232
+ if parameters && parameters.server_object_filter
233
+ additional_headers.merge!("#{@service_name}ObjectFilter" => parameters.server_object_filter)
234
+ end
235
+
236
+ # Object masks go into the headers too.
237
+ if parameters && parameters.server_object_mask && parameters.server_object_mask.count != 0
238
+ 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?
242
+ end
243
+
244
+ # Result limits go into the headers
245
+ if (parameters && parameters.server_result_limit)
246
+ additional_headers.merge!("resultLimit" => { "limit" => parameters.server_result_limit, "offset" => (parameters.server_result_offset || 0) })
247
+ end
248
+
249
+ # Add an object id to the headers.
250
+ if parameters && parameters.server_object_id
251
+ additional_headers.merge!("#{@service_name}InitParameters" => { "id" => parameters.server_object_id })
252
+ end
253
+
254
+ # This is a workaround for a potential problem that arises from mis-using the
255
+ # API. If you call SoftLayer_Virtual_Guest and you call the getObject method
256
+ # but pass a virtual guest as a parameter, what happens is the getObject method
257
+ # is called through an HTTP POST verb and the API creates a new VirtualServer that
258
+ # is a copy of the one you passed in.
259
+ #
260
+ # The counter-intuitive creation of a new Virtual Server is unexpected and, even worse,
261
+ # is something you can be billed for. To prevent that, we ignore the request
262
+ # body on a "getObject" call and print out a warning.
263
+ if (method_name == :getObject) && (nil != args) && (!args.empty?) then
264
+ $stderr.puts "Warning - The getObject method takes no parameters. The parameters you have provided will be ignored."
265
+ args = nil
266
+ end
267
+
268
+ # Collect all the different header pieces into a single hash that
269
+ # will become the first argument to the call.
270
+ call_headers = {
271
+ "headers" => additional_headers
272
+ }
273
+
274
+ begin
275
+ call_value = xmlrpc_client.call(method_name.to_s, call_headers, *args)
276
+ rescue XMLRPC::FaultException => e
277
+ puts "A XMLRPC Fault was returned #{e}" if $DEBUG
278
+ raise
279
+ end
280
+
281
+ return call_value
282
+ end
283
+
284
+ # If this is not defined for Service, then when you print a service object
285
+ # the code will try to convert it to an array and end up calling method_missing
286
+ #
287
+ # We define this here to prevent odd calls to the Softlayer API
288
+ def to_ary
289
+ nil
290
+ end
291
+
292
+ private
293
+
294
+ def xmlrpc_client()
295
+ if !@xmlrpc_client
296
+ @xmlrpc_client = XMLRPC::Client.new2(URI.join(@client.endpoint_url,@service_name).to_s)
297
+
298
+ # this is a workaround for a bug in later versions of the XML-RPC client in Ruby Core.
299
+ # see https://bugs.ruby-lang.org/issues/8182
300
+ @xmlrpc_client.http_header_extra = {
301
+ "Accept-Encoding" => "identity",
302
+ "User-Agent" => @client.user_agent
303
+ }
304
+
305
+ if $DEBUG
306
+ if !@xmlrpc_client.respond_to?(:http)
307
+ class << @xmlrpc_client
308
+ def http
309
+ return @http
310
+ end
311
+ end
312
+ end
313
+
314
+ @xmlrpc_client.http.set_debug_output($stderr)
315
+ end # $DEBUG
316
+ end
317
+
318
+ @xmlrpc_client
319
+ end
320
+ end # Service class
321
+ end # module SoftLayer
@@ -20,25 +20,24 @@
20
20
  # THE SOFTWARE.
21
21
  #
22
22
 
23
- # The SoftLayer Module
24
- #
23
+ require 'rubygems'
24
+
25
25
  # This module is used to provide a namespace for SoftLayer code. It also declares a number of
26
26
  # global variables:
27
27
  # - <tt>$SL_API_USERNAME</tt> - The default username passed by clients to the server for authentication.
28
28
  # Set this if you want to use the same username for all clients and don't want to have to specify it when the client is created
29
29
  # - <tt>$SL_API_KEY</tt> - The default API key passed by clients to the server for authentication.
30
30
  # Set this if you want to use the same api for all clients and don't want to have to specify it when the client is created
31
- # - <tt>$SL_API_BASE_URL</tt>- The default URL used to access the SoftLayer API. This defaults to the value of SoftLayer::API_PUBLIC_ENDPOINT
31
+ # - <tt>$SL_API_BASE_URL</tt>- The default URL used to access the SoftLayer API. This defaults to the value of <tt>SoftLayer::API_PUBLIC_ENDPOINT</tt>
32
32
  #
33
-
34
33
  module SoftLayer
35
- VERSION = "1.0.8" # version history at the bottom of the file.
34
+ VERSION = "2.0.0" # version history in the CHANGELOG.textile file at the root of the source
36
35
 
37
36
  # The base URL of the SoftLayer API's REST-like endpoints available to the public internet.
38
- API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/rest/v3/'
37
+ API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3/'
39
38
 
40
39
  # The base URL of the SoftLayer API's REST-like endpoints available through SoftLayer's private network
41
- API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/rest/v3/'
40
+ API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3/'
42
41
 
43
42
  #
44
43
  # These globals can be used to simplify client creation
@@ -56,3 +55,8 @@ module SoftLayer
56
55
  # The base URL used for the SoftLayer API's
57
56
  $SL_API_BASE_URL = SoftLayer::API_PUBLIC_ENDPOINT
58
57
  end # module SoftLayer
58
+
59
+ #
60
+ # History:
61
+ #
62
+ # The history has been moved to the CHANGELOG.textile file in the source directory
@@ -20,28 +20,75 @@
20
20
  # THE SOFTWARE.
21
21
  #
22
22
 
23
+ # Ruby Hash Class
23
24
  class Hash
24
- def to_sl_object_mask(base = "")
25
- return base if(self.empty?)
25
+ def __valid_root_property_key?(key_string)
26
+ return key_string == "mask" || (0 == (key_string =~ /\Amask\([a-z][a-z0-9_]*\)\z/i))
27
+ end
28
+
29
+ def to_sl_object_mask()
30
+ raise RuntimeError, "An object mask must contain properties" if empty?
31
+ raise RuntimeError, "An object mask must start with root properties" if keys().find { |key| !__valid_root_property_key?(key) }
26
32
 
27
- # ask the children to convert themselves with the key as the base
28
- masked_children = self.map { |key, value| result = value.to_sl_object_mask(key); }.flatten
33
+ key_strings = __sl_object_mask_properties_for_keys();
34
+ key_strings.count > 1 ? "[#{key_strings.join(',')}]" : "#{key_strings[0]}"
35
+ end
29
36
 
30
- # now resolve the children with respect to the base passed in.
31
- masked_children.map { |mask_item| mask_item.to_sl_object_mask(base) }
37
+ def to_sl_object_mask_property()
38
+ key_strings = __sl_object_mask_properties_for_keys();
39
+ "#{key_strings.join(',')}"
40
+ end
41
+
42
+ def __sl_object_mask_properties_for_keys
43
+ key_strings = [];
44
+
45
+ each do |key, value|
46
+ return "" if !value
47
+
48
+ string_for_key = key.to_sl_object_mask_property
49
+
50
+ if value.kind_of?(String) || value.kind_of?(Symbol) then
51
+ string_for_key = "#{string_for_key}.#{value.to_sl_object_mask_property}"
52
+ end
53
+
54
+ if value.kind_of?(Array) || value.kind_of?(Hash) then
55
+ value_string = value.to_sl_object_mask_property
56
+ if value_string && !value_string.empty?
57
+ string_for_key = "#{string_for_key}[#{value_string}]"
58
+ end
59
+ end
60
+
61
+ key_strings.push(string_for_key)
62
+ end
63
+
64
+ key_strings
32
65
  end
33
66
  end
34
67
 
68
+ # Ruby Array Class
35
69
  class Array
36
- def to_sl_object_mask(base = "")
37
- return base if self.empty?
38
- self.map { |item| item.to_sl_object_mask(base) }.flatten
70
+ # Returns a string representing the object mask content represented by the
71
+ # Array. Each value in the array is converted to its object mask eqivalent
72
+ def to_sl_object_mask_property()
73
+ return "" if self.empty?
74
+ property_content = map { |item| item.to_sl_object_mask_property() }.flatten.join(",")
75
+ "#{property_content}"
39
76
  end
40
77
  end
41
78
 
79
+ # Ruby String Class
42
80
  class String
43
- def to_sl_object_mask(base = "")
44
- return base if self.empty?
45
- base.empty? ? self : "#{base}.#{self}"
81
+ # Returns a string representing the object mask content represented by the
82
+ # String. Strings are simply represented as copies of themselves. We make
83
+ # a copy in case the original String is modified somewhere along the way
84
+ def to_sl_object_mask_property()
85
+ return self.strip
46
86
  end
47
- end
87
+ end
88
+
89
+ # Ruby Symbol Class
90
+ class Symbol
91
+ def to_sl_object_mask()
92
+ self.to_s.to_sl_object_mask()
93
+ end
94
+ end