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.
- checksums.yaml +4 -4
- data/README.textile +116 -66
- data/examples/{accountInformation.rb → account_info.rb} +6 -4
- data/examples/{createTicket.rb → create_ticket.rb} +6 -3
- data/examples/{openTickets.rb → open_tickets.rb} +6 -4
- data/examples/ticket_info.rb +8 -8
- data/lib/softlayer/APIParameterFilter.rb +131 -0
- data/lib/softlayer/Client.rb +100 -0
- data/lib/softlayer/Config.rb +77 -0
- data/lib/softlayer/ObjectFilter.rb +232 -0
- data/lib/softlayer/Service.rb +321 -0
- data/lib/softlayer/base.rb +11 -7
- data/lib/softlayer/object_mask_helpers.rb +60 -13
- data/lib/softlayer_api.rb +6 -1
- metadata +21 -17
- data/lib/softlayer/service.rb +0 -467
@@ -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
|
data/lib/softlayer/base.rb
CHANGED
@@ -20,25 +20,24 @@
|
|
20
20
|
# THE SOFTWARE.
|
21
21
|
#
|
22
22
|
|
23
|
-
|
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 = "
|
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/
|
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/
|
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
|
25
|
-
return
|
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
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|