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,131 @@
1
+
2
+ module SoftLayer
3
+ # An <tt>APIParameterFilter</tt> is an intermediary object that understands how
4
+ # to accept the other API parameter filters and carry their values to
5
+ # <tt>method_missing</tt> in <tt>Service</tt>. Instances of this class are created
6
+ # internally by the <tt>Service</tt> in its handling of a method call and you
7
+ # should not have to create instances of this class directly.
8
+ #
9
+ # Instead, to use an API filter, you add a filter method to the call
10
+ # chain when you call a method through a <tt>SoftLayer::Service</tt>
11
+ #
12
+ # For example, given a <tt>SoftLayer::Service</tt> instance called <tt>account_service</tt>
13
+ # you could take advantage of the API filter that identifies a particular
14
+ # object known to that service using the <tt>object_with_id</tt> method :
15
+ #
16
+ # account_service.object_with_id(91234).getSomeAttribute
17
+ #
18
+ # The invocation of <tt>object_with_id</tt> will cause an instance of this
19
+ # class to be created with the service as its target.
20
+ #
21
+ class APIParameterFilter
22
+ attr_reader :target
23
+ attr_reader :parameters
24
+
25
+ def initialize(target, starting_parameters = nil)
26
+ @target = target
27
+ @parameters = starting_parameters || {}
28
+ end
29
+
30
+ # Adds an API filter that narrows the scope of a call to an object with
31
+ # a particular ID. For example, if you want to get the ticket
32
+ # with an ID of 12345 from the ticket service you might use
33
+ #
34
+ # ticket_service.object_with_id(12345).getObject
35
+ def object_with_id(value)
36
+ # we create a new object in case the user wants to store off the
37
+ # filter chain and reuse it later
38
+ APIParameterFilter.new(self.target, @parameters.merge({ :server_object_id => value }))
39
+ end
40
+
41
+ # Use this as part of a method call chain to add an object mask to
42
+ # the request. The arguments to object mask should be well formed
43
+ # Extended Object Mask strings:
44
+ #
45
+ # ticket_service.object_mask(
46
+ # "mask[createDate, modifyDate]",
47
+ # "mask(SoftLayer_Some_Type).aProperty").getObject
48
+ #
49
+ # The object_mask becomes part of the request sent to the server
50
+ #
51
+ def object_mask(*args)
52
+ raise ArgumentError, "object_mask expects well-formatted root object mask strings" if args.empty? || (1 == args.count && !args[0])
53
+ raise ArgumentError, "object_mask expects well-formatted root object mask strings" if args.find { |arg| !(arg.kind_of?(String)) }
54
+
55
+ object_mask = (@parameters[:object_mask] || []) + args
56
+
57
+ # we create a new object in case the user wants to store off the
58
+ # filter chain and reuse it later
59
+ APIParameterFilter.new(self.target, @parameters.merge({ :object_mask => object_mask }));
60
+ end
61
+
62
+ # Adds a result limit which helps you page through a long list of entities
63
+ #
64
+ # The offset is the index of the first item you wish to have returned
65
+ # The limit describes how many items you wish the call to return.
66
+ #
67
+ # For example, if you wanted to get five open tickets from the account
68
+ # starting with the tenth item in the open tickets list you might call
69
+ #
70
+ # account_service.result_limit(10, 5).getOpenTickets
71
+ def result_limit(offset, limit)
72
+ # we create a new object in case the user wants to store off the
73
+ # filter chain and reuse it later
74
+ APIParameterFilter.new(self.target, @parameters.merge({ :result_offset => offset, :result_limit => limit }))
75
+ end
76
+
77
+ # Adds an object_filter to the result. An Object Filter allows you
78
+ # to specify criteria which are used to filter the results returned
79
+ # by the server.
80
+ def object_filter(filter)
81
+ raise ArgumentError, "Object mask expects mask properties" if filter.nil?
82
+
83
+ # we create a new object in case the user wants to store off the
84
+ # filter chain and reuse it later
85
+ APIParameterFilter.new(self.target, @parameters.merge({:object_filter => filter}));
86
+ end
87
+
88
+ # A utility method that returns the server object ID (if any) stored
89
+ # in this parameter set.
90
+ def server_object_id
91
+ self.parameters[:server_object_id]
92
+ end
93
+
94
+ # a utility method that returns the object mask (if any) stored
95
+ # in this parameter set.
96
+ def server_object_mask
97
+ self.parameters[:object_mask]
98
+ end
99
+
100
+ # a utility method that returns the starting index of the result limit (if any) stored
101
+ # in this parameter set.
102
+ def server_result_limit
103
+ self.parameters[:result_limit]
104
+ end
105
+
106
+ # a utility method that returns the starting index of the result limit offset (if any) stored
107
+ # in this parameter set.
108
+ def server_result_offset
109
+ self.parameters[:result_offset]
110
+ end
111
+
112
+ def server_object_filter
113
+ self.parameters[:object_filter]
114
+ end
115
+
116
+ # This allows the filters to be used at the end of a long chain of calls that ends
117
+ # at a service.
118
+ def method_missing(method_name, *args, &block)
119
+ puts "SoftLayer::APIParameterFilter#method_missing called #{method_name}, #{args.inspect}" if $DEBUG
120
+
121
+ if(!block && method_name.to_s.match(/[[:alnum:]]+/))
122
+ result = @target.call_softlayer_api_with_params(method_name, self, args)
123
+ else
124
+ result = super
125
+ end
126
+
127
+ result
128
+ end
129
+ end
130
+
131
+ end # module SoftLayer
@@ -0,0 +1,100 @@
1
+ module SoftLayer
2
+ # Initialize an instance of the Client class. You pass in the service name
3
+ # and optionally hash arguments specifying how the client should access the
4
+ # SoftLayer API.
5
+ #
6
+ # The following symbols can be used as hash arguments to pass options to the constructor:
7
+ # - <tt>:username</tt> - a non-empty string providing the username to use for requests to the service
8
+ # - <tt>:api_key</tt> - a non-empty string providing the api key to use for requests to the service
9
+ # - <tt>:endpoint_url</tt> - a non-empty string providing the endpoint URL to use for requests to the service
10
+ #
11
+ # If any of the options above are missing then the constructor will try to use the corresponding
12
+ # global variable declared in the SoftLayer Module:
13
+ # - <tt>$SL_API_USERNAME</tt>
14
+ # - <tt>$SL_API_KEY</tt>
15
+ # - <tt>$SL_API_BASE_URL</tt>
16
+ #
17
+ class Client
18
+ # A username passed as authentication for each request. Cannot be emtpy or nil.
19
+ attr_reader :username
20
+
21
+ # An API key passed as part of the authentication of each request. Cannot be emtpy or nil.
22
+ attr_reader :api_key
23
+
24
+ # The base URL for requests that are passed to the server. Cannot be emtpy or nil.
25
+ attr_reader :endpoint_url
26
+
27
+ # A string passsed as the value for the User-Agent header when requests are sent to SoftLayer API.
28
+ attr_accessor :user_agent
29
+
30
+ def initialize(options = {})
31
+ @services = { }
32
+
33
+ settings = Config.client_settings(options)
34
+
35
+ # pick up the username from the options, the global, or assume no username
36
+ @username = settings[:username] || ""
37
+
38
+ # do a similar thing for the api key
39
+ @api_key = settings[:api_key] || ""
40
+
41
+ # and the endpoint url
42
+ @endpoint_url = settings[:endpoint_url] || API_PUBLIC_ENDPOINT
43
+
44
+ @user_agent = settings[:user_agent] || "softlayer_api gem/#{SoftLayer::VERSION} (Ruby #{RUBY_PLATFORM}/#{RUBY_VERSION})"
45
+
46
+ raise "A SoftLayer Client requires a username" if !@username || @username.empty?
47
+ raise "A SoftLayer Client requires an api_key" if !@api_key || @api_key.empty?
48
+ raise "A SoftLayer Clietn requires an enpoint URL" if !@endpoint_url || @endpoint_url.empty?
49
+ end
50
+
51
+ # return a hash of the authentication headers for the client
52
+ def authentication_headers
53
+ {
54
+ "authenticate" => {
55
+ "username" => @username,
56
+ "apiKey" => @api_key
57
+ }
58
+ }
59
+ end
60
+
61
+ # Returns a service with the given name.
62
+ #
63
+ # If a service has already been created by this client that same service
64
+ # will be returned each time it is called for by name. Otherwise the system
65
+ # will try to construct a new service object and return that.
66
+ #
67
+ #
68
+ # If the service has to be created then the service_options will be passed
69
+ # along to the creative function. However, when returning a previously created
70
+ # Service, the service_options will be ignored.
71
+ #
72
+ # If the service_name provided does not start with 'SoftLayer__' that prefix
73
+ # will be added
74
+ def service_named(service_name, service_options = {})
75
+ raise ArgumentError,"Please provide a service name" if service_name.nil? || service_name.empty?
76
+
77
+ # strip whitespace from service_name and
78
+ # ensure that it start with "SoftLayer_".
79
+ #
80
+ # if it does not, then add it
81
+ service_name.strip!
82
+ if not service_name =~ /\ASoftLayer_/
83
+ service_name = "SoftLayer_#{service_name}"
84
+ end
85
+
86
+ # if we've already created this service, just return it
87
+ # otherwise create a new service
88
+ service_key = service_name.to_sym
89
+ if !@services.has_key?(service_key)
90
+ @services[service_key] = SoftLayer::Service.new(service_name, {:client => self}.merge(service_options))
91
+ end
92
+
93
+ @services[service_key]
94
+ end
95
+
96
+ def [](service_name)
97
+ service_named(service_name)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,77 @@
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
+ require 'configparser'
24
+
25
+ module SoftLayer
26
+ class Config
27
+ def Config.globals_settings
28
+ result = {}
29
+ result[:username] = $SL_API_USERNAME if $SL_API_USERNAME
30
+ result[:api_key] = $SL_API_KEY if $SL_API_KEY
31
+ result[:endpoint_url] = $SL_API_BASE_URL ? $SL_API_BASE_URL : API_PUBLIC_ENDPOINT
32
+ result
33
+ end
34
+
35
+ def Config.environment_settings
36
+ result = {}
37
+ result[:username] = ENV["SL_USERNAME"] if ENV["SL_USERNAME"]
38
+ result[:api_key] = ENV["SL_API_KEY"] if ENV["SL_API_KEY"]
39
+ result
40
+ end
41
+
42
+ FILE_LOCATIONS = ['/etc/softlayer.conf', '~/.softlayer']
43
+
44
+ def Config.file_settings(*additional_files)
45
+ result = {}
46
+
47
+ search_path = FILE_LOCATIONS
48
+ search_path = search_path + additional_files if additional_files
49
+ search_path = search_path.map { |file_path| File.expand_path(file_path) }
50
+
51
+ search_path.each do |file_path|
52
+
53
+ if File.readable? file_path
54
+ config = ConfigParser.new file_path
55
+ softlayer_section = config["softlayer"]
56
+
57
+ if softlayer_section
58
+ result[:username] = softlayer_section['username'] if softlayer_section['username']
59
+ result[:endpoint_url] = softlayer_section['endpoint_url'] if softlayer_section['endpoint_url']
60
+ result[:api_key] = softlayer_section['api_key'] if softlayer_section['api_key']
61
+ end
62
+ end
63
+ end
64
+
65
+ result
66
+ end
67
+
68
+ def Config.client_settings(provided_settings = {})
69
+ settings = file_settings
70
+ settings.merge! environment_settings
71
+ settings.merge! globals_settings
72
+ settings.merge! provided_settings
73
+
74
+ settings
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,232 @@
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
+ OBJECT_FILTER_OPERATORS = [
25
+ '*=', # Contains (ignoring case)
26
+ '^=', # Begins with (ignoring case)
27
+ '$=', # Ends with (ignoring_case)
28
+ '_=', # Matches (ignoring case)
29
+ '!=', # Is not Equal To (case sensitive)
30
+ '<=', # Less than or Equal To (case sensitive)
31
+ '>=', # Greater than or Equal To (case sensitive)
32
+ '<', # Less Than (case sensitive)
33
+ '>', # Greater Than (case sensitive)
34
+ '~', # Contains (case sensitive)
35
+ '!~' # Does not Contain (case sensitive)
36
+ ]
37
+
38
+ # A class whose instances represent an Object Filter operation.
39
+ class ObjectFilterOperation
40
+ attr_reader :operator
41
+ attr_reader :value
42
+
43
+ def initialize(operator, value)
44
+ raise ArgumentException, "An unknown operator was given" if !OBJECT_FILTER_OPERATORS.include?(operator.strip)
45
+ raise ArgumentException, "Expected a value" if value.nil? || (value.respond_to?(:empty?) && value.empty?)
46
+
47
+ @operator = operator.strip
48
+ @value = value.strip
49
+ end
50
+
51
+ def to_h
52
+ { 'operation' => "#{operator} #{value}"}
53
+ end
54
+ end
55
+
56
+ # Routines that are valid within the block provided to a call to
57
+ # ObjectFilter.build.
58
+ class ObjectFilterBlockHandler
59
+ # contains wihout considering case
60
+ def contains(value)
61
+ ObjectFilterOperation.new('*=', value)
62
+ end
63
+
64
+ # case insensitive begins with
65
+ def begins_with(value)
66
+ ObjectFilterOperation.new('^=', value)
67
+ end
68
+
69
+ # case insensitive ends with
70
+ def ends_with(value)
71
+ ObjectFilterOperation.new('$=', value)
72
+ end
73
+
74
+ # matches exactly (ignoring case)
75
+ def is(value)
76
+ ObjectFilterOperation.new('_=', value)
77
+ end
78
+
79
+ def is_not(value)
80
+ ObjectFilterOperation.new('!=', value)
81
+ end
82
+
83
+ def is_greater_than(value)
84
+ ObjectFilterOperation.new('>', value)
85
+ end
86
+
87
+ def is_less_than(value)
88
+ ObjectFilterOperation.new('<', value)
89
+ end
90
+
91
+ def is_greater_or_equal_to(value)
92
+ ObjectFilterOperation.new('>=', value)
93
+ end
94
+
95
+ def is_less_or_equal_to(value)
96
+ ObjectFilterOperation.new('<=', value)
97
+ end
98
+
99
+ def contains_exactly(value)
100
+ ObjectFilterOperation.new('~', value)
101
+ end
102
+
103
+ def does_not_contain(value)
104
+ ObjectFilterOperation.new('!~', value)
105
+ end
106
+ end
107
+
108
+ # An ObjectFilter is a hash that, when asked to provide
109
+ # an value for an unknown key, will create a sub element
110
+ # at that key that is itself an object filter. So if you
111
+ # start with an empty object filter and ask for <tt>object_filter["foo"]</tt>
112
+ # then foo will be +added+ to the object and the value of that
113
+ # key will be an Object Filter <tt>{ "foo" => {} }</tt>
114
+ #
115
+ # This allows you to create object filters by chaining [] calls:
116
+ # object_filter["foo"]["bar"]["baz"] = 3 yields {"foo" => { "bar" => {"baz" => 3}}}
117
+ #
118
+ class ObjectFilter < Hash
119
+ # The default initialize for a hash is overridden
120
+ # so that object filters create sub-filters when asked
121
+ # for missing keys.
122
+ def initialize
123
+ super do |hash, key|
124
+ hash[key] = ObjectFilter.new
125
+ end
126
+ end
127
+
128
+ # Builds an object filter with the given key path, a dot separated list of property keys.
129
+ # The filter itself can be provided as a query string (in the query parameter)
130
+ # or by providing a block that calls routines in the ObjectFilterBlockHandler class.
131
+ def self.build(key_path, query = nil, &block)
132
+ raise ArgumentError, "The key path to build cannot be empty" if !key_path
133
+
134
+ # Split the keypath into its constituent parts and notify the user
135
+ # if there are no parts
136
+ keys = key_path.split('.')
137
+ raise ArgumentError, "The key path to build cannot be empty" if keys.empty?
138
+
139
+ # This will be the result of the build
140
+ result = ObjectFilter.new
141
+
142
+ # chase down the key path to the last-but-one key
143
+ current_level = result
144
+ while keys.count > 1
145
+ current_level = current_level[keys.shift]
146
+ end
147
+
148
+ # if there is a block, then the query will come from
149
+ # calling the block. We warn in debug mode if you override a
150
+ # query that was passed directly with the value from a block.
151
+ if block
152
+ $stderr.puts "The query from the block passed to ObjectFilter:build will override the query passed as a parameter" if $DEBUG && query
153
+ block_handler = ObjectFilterBlockHandler.new
154
+ query = block_handler.instance_eval(&block)
155
+ end
156
+
157
+ # If we have a query, we assign it's value to the last key
158
+ # otherwise, we build an emtpy filter at the bottom
159
+ if query
160
+ case
161
+ when query.kind_of?(Numeric)
162
+ current_level[keys.shift] = { 'operation' => query }
163
+ when query.kind_of?(SoftLayer::ObjectFilterOperation)
164
+ current_level[keys.shift] = query.to_h
165
+ when query.kind_of?(String)
166
+ current_level[keys.shift] = query_to_filter_operation(query)
167
+ when query.kind_of?(Hash)
168
+ current_level[keys.shift] = query
169
+ else
170
+ current_level[keys.shift]
171
+ end
172
+ else
173
+ current_level[keys.shift]
174
+ end
175
+
176
+ result
177
+ end
178
+
179
+ # This method tries to simplify creating a correct object filter structure
180
+ # by allowing the caller to provide a string in a simple query language.
181
+ # It then translates that string into an Object Filter operation structure
182
+ #
183
+ # Object Filter comparisons are done using operators. Some operators make
184
+ # case sensitive comparisons and some do not. The general form of an Object
185
+ # Filter operation is an operator follwed by the value used in the comparison.
186
+ # e.g.
187
+ # "*= smaug"
188
+ #
189
+ # The query language also accepts some aliases using asterisks
190
+ # in a regular-expression-like way. Those aliases look like:
191
+ #
192
+ # 'value' Exact value match (translates to '_= value')
193
+ # 'value*' Begins with value (translates to '^= value')
194
+ # '*value' Ends with value (translates to '$= value')
195
+ # '*value*' Contains value (translates to '*= value')
196
+ #
197
+ def self.query_to_filter_operation(query)
198
+ if query.kind_of? String then
199
+ query.strip!
200
+
201
+ begin
202
+ return { 'operation' => Integer(query) }
203
+ rescue
204
+ end
205
+
206
+ operator = OBJECT_FILTER_OPERATORS.find do | operator_string |
207
+ query[0 ... operator_string.length] == operator_string
208
+ end
209
+
210
+ if operator then
211
+ operation = "#{operator} #{query[operator.length..-1].strip}"
212
+ else
213
+ case query
214
+ when /\A\*(.*)\*\Z/
215
+ operation = "*= #{$1}"
216
+ when /\A\*(.*)/
217
+ operation = "$= #{$1}"
218
+ when /\A(.*)\*\Z/
219
+ operation = "^= #{$1}"
220
+ else
221
+ operation = "_= #{query}"
222
+ end #case
223
+ end #if
224
+ else
225
+ operation = query.to_i
226
+ end # query is string
227
+
228
+ { 'operation' => operation }
229
+ end # query_to_filter_operation
230
+
231
+ end # ObjectFilter
232
+ end # SoftLayer