softlayer_api 2.2.2 → 3.0.b1

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.
@@ -1,24 +1,8 @@
1
- #
1
+ #--
2
2
  # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
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
- #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
22
6
 
23
7
  module SoftLayer
24
8
  ##
@@ -131,7 +115,7 @@ module SoftLayer
131
115
  def softlayer_properties(object_mask = nil)
132
116
  raise "Abstract method softlayer_properties in ModelBase was called"
133
117
  end
134
-
118
+
135
119
  ##
136
120
  # The softlayer_hash stores the low-level information about an
137
121
  # object as it was retrieved from the SoftLayer API.
@@ -1,26 +1,102 @@
1
- #
1
+ #--
2
2
  # Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
3
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
- #
4
+ # For licensing information see the LICENSE.md file in the project root.
5
+ #++
22
6
 
23
7
  module SoftLayer
8
+ ##
9
+ # An ObjectFilter is a tool that, when passed to the SoftLayer API
10
+ # allows the API server to filter, or limit the result set for a call.
11
+ #
12
+ # Constructing ObjectFilters is an art that is currently somewhat
13
+ # arcane. This class tries to simplify filtering for the fundamental
14
+ # cases, while still allowing for more complex ObjectFilters to be
15
+ # created.
16
+ #
17
+ # To construct an object filter you begin with an instance of the
18
+ # class. At construction time, or in a "modify" call you can change
19
+ # the filter criteria using a fancy DSL syntax.
20
+ #
21
+ # For example, to filter virtual servers so that you only get ones
22
+ # whose domains end with "layer.com" you might use:
23
+ #
24
+ # object_filter = ObjectFilter.new do |filter|
25
+ # filter.accept(virtualGuests.domain).when_it ends_with("layer.com")
26
+ # end
27
+ #
28
+ # The set of criteria that can be included after "when_it" are defined
29
+ # by routines in the ObjectFilterDefinitionContext module.
30
+ class ObjectFilter
31
+ def initialize(&construction_block)
32
+ @filter_hash = {}
33
+ self.modify(&construction_block)
34
+ self
35
+ end
36
+
37
+ def empty?
38
+ @filter_hash.empty?
39
+ end
40
+
41
+ def modify(&construction_block)
42
+ ObjectFilterDefinitionContext.module_exec(self, &construction_block) if construction_block
43
+ end
44
+
45
+ def accept(key_path)
46
+ CriteriaAcceptor.new(self, key_path)
47
+ end
48
+
49
+ def to_h
50
+ return @filter_hash.dup
51
+ end
52
+
53
+ def criteria_for_key_path(key_path)
54
+ raise "The key path cannot be empty when searching for criteria" if key_path.nil? || key_path.empty?
55
+
56
+ current_level = @filter_hash
57
+ keys = key_path.split('.')
58
+
59
+ while current_level && keys.count > 1
60
+ current_level = current_level[keys.shift]
61
+ end
62
+
63
+ if current_level
64
+ current_level[keys[0]]
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ def set_criteria_for_key_path(key_path, criteria)
71
+ current_level = @filter_hash
72
+ keys = key_path.split('.')
73
+
74
+ current_key = keys.shift
75
+ while current_level && !keys.empty?
76
+ if !current_level.has_key? current_key
77
+ current_level[current_key] = {}
78
+ end
79
+ current_level = current_level[current_key]
80
+ current_key = keys.shift
81
+ end
82
+
83
+ current_level[current_key] = criteria
84
+ end
85
+
86
+ class CriteriaAcceptor
87
+ def initialize(filter, key_path)
88
+ @filter = filter
89
+ @key_path = key_path
90
+ end
91
+
92
+ def when_it(criteria)
93
+ @filter.set_criteria_for_key_path(@key_path, criteria)
94
+ end
95
+ end
96
+ end # ObjectFilter
97
+
98
+ ##
99
+ # :nodoc:
24
100
  OBJECT_FILTER_OPERATORS = [
25
101
  '*=', # Contains (ignoring case)
26
102
  '^=', # Begins with (ignoring case)
@@ -35,192 +111,109 @@ module SoftLayer
35
111
  '!~' # Does not Contain (case sensitive)
36
112
  ]
37
113
 
38
- # A class whose instances represent an Object Filter operator and the value it is applied to.
39
- class ObjectFilterOperation
40
-
41
- # The operator, should be a member of the SoftLayer::OBJECT_FILTER_OPERATORS array
42
- attr_reader :operator
43
-
44
- # The operand of the operator
45
- attr_reader :value
46
-
47
- def initialize(operator, value)
48
- raise ArgumentException, "An unknown operator was given" if !OBJECT_FILTER_OPERATORS.include?(operator.strip)
49
- raise ArgumentException, "Expected a value" if value.nil? || (value.respond_to?(:empty?) && value.empty?)
50
-
51
- @operator = operator.strip
52
- @value = value.strip
114
+ ##
115
+ # The ObjectFilterDefinitionContext defines a bunch of methods
116
+ # that allow the property conditions of an object filter to
117
+ # be defined in a "pretty" way. Each method returns a block
118
+ # (a lambda, a proc) that, when called and pased the tail property
119
+ # of a property chain will generate a fragment of an object filter
120
+ # asking that that property match the given conditions.
121
+ #
122
+ # This class, as a whole, is largely an implementation detail
123
+ # of object filter definitions and there is probably not
124
+ # a good reason to call into it directly.
125
+ module ObjectFilterDefinitionContext
126
+ # Matches when the value in the field is exactly equal to the
127
+ # given value. This is a case-sensitive match
128
+ def self.is(value)
129
+ { 'operation' => value }
53
130
  end
54
131
 
55
- def to_h
56
- result = ObjectFilter.new
57
- result['operation'] = "#{operator} #{value}"
58
-
59
- result
132
+ # Matches is the value in the field does not exactly equal
133
+ # the value passed in.
134
+ def self.is_not(value)
135
+ filter_criteria('!=', value)
60
136
  end
61
- end
62
137
 
63
- # This class defines the routines that are valid within the block provided to a call to
64
- # ObjectFilter.build. This allows you to create object filters like:
65
- #
66
- # object_filter = SoftLayer::ObjectFilter.build("hardware.memory") { is_greater_than(2) }
67
- #
68
- class ObjectFilterBlockHandler
69
138
  # Matches when the value is found within the field
70
139
  # the search is not case sensitive
71
- def contains(value)
72
- ObjectFilterOperation.new('*=', value)
140
+ def self.contains(value)
141
+ filter_criteria('*=', value)
73
142
  end
74
143
 
75
144
  # Matches when the value is found at the beginning of the
76
145
  # field. This search is not case sensitive
77
- def begins_with(value)
78
- ObjectFilterOperation.new('^=', value)
146
+ def self.begins_with(value)
147
+ filter_criteria('^=', value)
79
148
  end
80
149
 
81
150
  # Matches when the value is found at the end of the
82
151
  # field. This search is not case sensitive
83
- def ends_with(value)
84
- ObjectFilterOperation.new('$=', value)
152
+ def self.ends_with(value)
153
+ filter_criteria('$=', value)
85
154
  end
86
155
 
87
- # Matches when the value in the field is exactly equal to the
88
- # given value. This is a case-sensitive match
89
- def is(value)
90
- ObjectFilterOperation.new('_=', value)
91
- end
92
-
93
- # Matches is the value in the field does not exactly equal
94
- # the value passed in.
95
- def is_not(value)
96
- ObjectFilterOperation.new('!=', value)
156
+ # Maches the given value in a case-insensitive way
157
+ def self.matches_ignoring_case(value)
158
+ filter_criteria('_=', value)
97
159
  end
98
160
 
99
161
  # Matches when the value in the field is greater than the given value
100
- def is_greater_than(value)
101
- ObjectFilterOperation.new('>', value)
162
+ def self.is_greater_than(value)
163
+ filter_criteria('>', value)
102
164
  end
103
165
 
104
166
  # Matches when the value in the field is less than the given value
105
- def is_less_than(value)
106
- ObjectFilterOperation.new('<', value)
167
+ def self.is_less_than(value)
168
+ filter_criteria('<', value)
107
169
  end
108
170
 
109
171
  # Matches when the value in the field is greater than or equal to the given value
110
- def is_greater_or_equal_to(value)
111
- ObjectFilterOperation.new('>=', value)
172
+ def self.is_greater_or_equal_to(value)
173
+ filter_criteria('>=', value)
112
174
  end
113
175
 
114
176
  # Matches when the value in the field is less than or equal to the given value
115
- def is_less_or_equal_to(value)
116
- ObjectFilterOperation.new('<=', value)
177
+ def self.is_less_or_equal_to(value)
178
+ filter_criteria('<=', value)
117
179
  end
118
180
 
119
181
  # Matches when the value is found within the field
120
182
  # the search _is_ case sensitive
121
- def contains_exactly(value)
122
- ObjectFilterOperation.new('~', value)
183
+ def self.contains_exactly(value)
184
+ filter_criteria('~', value)
123
185
  end
124
186
 
125
187
  # Matches when the value is not found within the field
126
188
  # the search _is_ case sensitive
127
- def does_not_contain(value)
128
- ObjectFilterOperation.new('!~', value)
189
+ def self.does_not_contain(value)
190
+ filter_criteria('!~', value)
129
191
  end
130
- end
131
192
 
132
- #
133
- # An ObjectFilter is a tool that, when passed to the SoftLayer API
134
- # allows the API server to filter, or limit the result set for a call.
135
- #
136
- # Constructing ObjectFilters is an art that is currently somewhat
137
- # arcane. This class tries to simplify filtering for the fundamental
138
- # cases, while still allowing for more complex ObjectFilters to be
139
- # created.
140
- #
141
- # The ObjectFilter class is implemented as a hash that, when asked to provide
142
- # an value for an unknown key, will create a sub element
143
- # at that key which is, itself, an object filter. This allows you to build
144
- # up object filters by chaining [] dereference operations.
145
- #
146
- # Starting empty object filter when you ask for +object_filter["foo"]+
147
- # either the value at that hash location will be returned, or a new +foo+ key
148
- # will be *added* to the object. The value of that key will be an +ObjectFilter+
149
- # and that +ObjectFilter+ will be returned.
150
- #
151
- # By way of an example of chaining together +[]+ calls:
152
- # object_filter["foo"]["bar"]["baz"] = 3
153
- # yields an object filter like this:
154
- # {"foo" => { "bar" => {"baz" => 3}}}
155
- #
156
- class ObjectFilter < Hash
157
- # The default initialize for a hash is overridden
158
- # so that object filters create sub-filters when asked
159
- # for missing keys.
160
- def initialize
161
- super do |hash, key|
162
- hash[key] = ObjectFilter.new
163
- end
193
+ # Matches when the property's value is null
194
+ def self.is_null
195
+ { 'operation' => 'is null' }
164
196
  end
165
197
 
166
- # Builds an object filter with the given key path, a dot separated list of property keys.
167
- # The filter itself can be provided as a query string (in the query parameter)
168
- # or by providing a block that calls routines in the ObjectFilterBlockHandler class.
169
- def self.build(key_path, query = nil, &block)
170
- raise ArgumentError, "The key path to build cannot be empty" if !key_path
171
-
172
- # Split the keypath into its constituent parts and notify the user
173
- # if there are no parts
174
- keys = key_path.split('.')
175
- raise ArgumentError, "The key path to build cannot be empty" if keys.empty?
176
-
177
- # This will be the result of the build
178
- result = ObjectFilter.new
179
-
180
- # chase down the key path to the last-but-one key
181
- current_level = result
182
- while keys.count > 1
183
- current_level = current_level[keys.shift]
184
- end
185
-
186
- # if there is a block, then the query will come from
187
- # calling the block. We warn in debug mode if you override a
188
- # query that was passed directly with the value from a block.
189
- if block
190
- $stderr.puts "The query from the block passed to ObjectFilter:build will override the query passed as a parameter" if $DEBUG && query
191
- block_handler = ObjectFilterBlockHandler.new
192
- query = block_handler.instance_eval(&block)
193
- end
194
-
195
- # If we have a query, we assign its value to the last key
196
- # otherwise, we build an emtpy filter at the bottom
197
- if query
198
- case
199
- when query.kind_of?(Numeric)
200
- current_level[keys.shift] = { 'operation' => query }
201
- when query.kind_of?(SoftLayer::ObjectFilterOperation)
202
- current_level[keys.shift] = query.to_h
203
- when query.kind_of?(String)
204
- current_level[keys.shift] = query_to_filter_operation(query)
205
- when query.kind_of?(Hash)
206
- current_level[keys.shift] = query
207
- else
208
- current_level[keys.shift]
209
- end
210
- else
211
- current_level[keys.shift]
212
- end
198
+ # Matches when the property's value is not null
199
+ def self.is_not_null()
200
+ { 'operation' => 'not null' }
201
+ end
213
202
 
214
- result
203
+ # This is a catch-all criteria matcher that allows for raw object filter conditions
204
+ # not covered by the more convenient methods above. The name is intentionally, annoyingly
205
+ # long and you should use this routine with solid knowledge and great care.
206
+ def self.satisfies_the_raw_condition(condition_hash)
207
+ condition_hash
215
208
  end
216
209
 
217
- # This method simplifies creating correct object filter structures
218
- # by defining a simple query language. It translates strings in that
219
- # language into an Object Filter operations
210
+ # Accepts a query string defined by a simple query language.
211
+ # It translates strings in that language into criteria blocks
220
212
  #
221
- # Object Filter comparisons are done using operators. Some operators make
222
- # case sensitive comparisons and some do not. The general form of an Object
223
- # Filter operation is an operator follwed by the value used in the comparison.
213
+ # Object Filter comparisons can be done using operators. The
214
+ # set of accepted operators is found in the OBJECT_FILTER_OPERATORS
215
+ # array. The query string can consist of an operator followed
216
+ # by a space, followed by operand
224
217
  # e.g.
225
218
  # "*= smaug"
226
219
  #
@@ -234,39 +227,48 @@ module SoftLayer
234
227
  #
235
228
  # This method corresponds to the +query_filter+ method in the SoftLayer-Python
236
229
  # API.
237
- def self.query_to_filter_operation(query)
238
- if query.kind_of? String then
239
- query.strip!
230
+ def self.matches_query(query_string)
231
+ query = query_string.to_s.strip
240
232
 
241
- begin
242
- return { 'operation' => Integer(query) }
243
- rescue
244
- end
245
-
246
- operator = OBJECT_FILTER_OPERATORS.find do | operator_string |
247
- query[0 ... operator_string.length] == operator_string
248
- end
233
+ operator = OBJECT_FILTER_OPERATORS.find do | operator_string |
234
+ query[0 ... operator_string.length] == operator_string
235
+ end
249
236
 
250
- if operator then
251
- operation = "#{operator} #{query[operator.length..-1].strip}"
252
- else
253
- case query
254
- when /\A\*(.*)\*\Z/
255
- operation = "*= #{$1}"
256
- when /\A\*(.*)/
257
- operation = "$= #{$1}"
258
- when /\A(.*)\*\Z/
259
- operation = "^= #{$1}"
260
- else
261
- operation = "_= #{query}"
262
- end #case
263
- end #if
237
+ if operator then
238
+ filter_criteria(operator, query[operator.length..-1])
264
239
  else
265
- operation = query.to_i
266
- end # query is string
240
+ case query
241
+ when /\A\*(.*)\*\Z/
242
+ contains($1)
243
+ when /\A\*(.*)/
244
+ ends_with($1)
245
+ when /\A(.*)\*\Z/
246
+ begins_with($1)
247
+ else
248
+ matches_ignoring_case(query)
249
+ end #case
250
+ end #if
251
+ end
267
252
 
268
- { 'operation' => operation }
269
- end # query_to_filter_operation
253
+ private
270
254
 
271
- end # ObjectFilter
255
+ def self.cleaned_up_operand(operand)
256
+ # try to convert the operand to an integer. If it works, return
257
+ # that integer
258
+ begin
259
+ return Integer(operand)
260
+ rescue
261
+ end
262
+
263
+ # The operand could not be converted to an integer so we try to make it a string
264
+ # and clean up the string
265
+ filter_operand = operand.to_s.strip
266
+ end
267
+
268
+ def self.filter_criteria(with_operator, operand)
269
+ filter_operand = cleaned_up_operand(operand)
270
+ filter_condition = "#{with_operator.to_s.strip} #{operand.to_s.strip}"
271
+ { 'operation' => filter_condition }
272
+ end
273
+ end
272
274
  end # SoftLayer