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.
- checksums.yaml +4 -4
- data/CHANGELOG.textile +9 -5
- data/examples/account_servers.rb +2 -2
- data/examples/create_ticket.rb +3 -3
- data/examples/open_tickets.rb +2 -2
- data/examples/ticket_info.rb +2 -2
- data/lib/softlayer/APIParameterFilter.rb +7 -21
- data/lib/softlayer/Account.rb +28 -19
- data/lib/softlayer/BareMetalServer.rb +13 -25
- data/lib/softlayer/BareMetalServerOrder.rb +6 -23
- data/lib/softlayer/BareMetalServerOrder_Package.rb +11 -29
- data/lib/softlayer/Client.rb +17 -23
- data/lib/softlayer/Config.rb +6 -22
- data/lib/softlayer/Datacenter.rb +53 -0
- data/lib/softlayer/DynamicAttribute.rb +3 -19
- data/lib/softlayer/ImageTemplate.rb +384 -0
- data/lib/softlayer/ModelBase.rb +4 -20
- data/lib/softlayer/ObjectFilter.rb +193 -191
- data/lib/softlayer/ObjectMaskParser.rb +3 -19
- data/lib/softlayer/ObjectMaskProperty.rb +3 -19
- data/lib/softlayer/ObjectMaskToken.rb +3 -19
- data/lib/softlayer/ObjectMaskTokenizer.rb +3 -19
- data/lib/softlayer/ProductItemCategory.rb +6 -23
- data/lib/softlayer/ProductPackage.rb +11 -32
- data/lib/softlayer/Server.rb +22 -19
- data/lib/softlayer/Ticket.rb +6 -45
- data/lib/softlayer/VirtualServer.rb +24 -101
- data/lib/softlayer/VirtualServerOrder.rb +14 -31
- data/lib/softlayer/VirtualServerUpgradeOrder.rb +142 -0
- data/lib/softlayer/base.rb +11 -30
- data/lib/softlayer/object_mask_helpers.rb +3 -19
- data/lib/softlayer_api.rb +12 -22
- metadata +7 -4
data/lib/softlayer/ModelBase.rb
CHANGED
@@ -1,24 +1,8 @@
|
|
1
|
-
|
1
|
+
#--
|
2
2
|
# Copyright (c) 2014 SoftLayer Technologies, Inc. All rights reserved.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
|
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
|
-
#
|
5
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
152
|
+
def self.ends_with(value)
|
153
|
+
filter_criteria('$=', value)
|
85
154
|
end
|
86
155
|
|
87
|
-
#
|
88
|
-
|
89
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
189
|
+
def self.does_not_contain(value)
|
190
|
+
filter_criteria('!~', value)
|
129
191
|
end
|
130
|
-
end
|
131
192
|
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
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
|
-
#
|
218
|
-
#
|
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
|
222
|
-
#
|
223
|
-
#
|
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.
|
238
|
-
|
239
|
-
query.strip!
|
230
|
+
def self.matches_query(query_string)
|
231
|
+
query = query_string.to_s.strip
|
240
232
|
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
251
|
-
|
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
|
-
|
266
|
-
|
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
|
-
|
269
|
-
end # query_to_filter_operation
|
253
|
+
private
|
270
254
|
|
271
|
-
|
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
|