softlayer_api 2.2.2 → 3.0.b1

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