softlayer_api 2.0.1 → 2.1.0

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.
@@ -28,7 +28,7 @@ module SoftLayer
28
28
  result = {}
29
29
  result[:username] = $SL_API_USERNAME if $SL_API_USERNAME
30
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
31
+ result[:endpoint_url] = $SL_API_BASE_URL || API_PUBLIC_ENDPOINT
32
32
  result
33
33
  end
34
34
 
@@ -39,7 +39,7 @@ module SoftLayer
39
39
  result
40
40
  end
41
41
 
42
- FILE_LOCATIONS = ['/etc/softlayer.conf', '~/.softlayer']
42
+ FILE_LOCATIONS = ['/etc/softlayer.conf', '~/.softlayer', './.softlayer']
43
43
 
44
44
  def Config.file_settings(*additional_files)
45
45
  result = {}
@@ -49,7 +49,6 @@ module SoftLayer
49
49
  search_path = search_path.map { |file_path| File.expand_path(file_path) }
50
50
 
51
51
  search_path.each do |file_path|
52
-
53
52
  if File.readable? file_path
54
53
  config = ConfigParser.new file_path
55
54
  softlayer_section = config["softlayer"]
@@ -0,0 +1,170 @@
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
+
25
+ ##
26
+ # This module is inteneded to be used by classes in the SoftLayer
27
+ # object model. It creates a small DSL for creating attributes
28
+ # that update themselves dynamically (usually by making requests
29
+ # to the SoftLayer API)
30
+ #
31
+ # +sl_dynamic_attr+ is an implementation of a memoization scheme
32
+ # The module creates a getter which is implemented in terms of a
33
+ # predicate (identifying whether or not the attribute needs to be updated) and
34
+ # an update routine
35
+ #
36
+ # When the getter is called, it checks the predicate routine to see if
37
+ # the attribute needs to be updated. If it doesn't, then the getter simply
38
+ # returns the cached value for the attribute. If the attribute does need
39
+ # to be updated, the getter calls the update routine to get a new value
40
+ # and caches that value off before returning it to the caller.
41
+ #
42
+ # Declaring a attribute adds three methods to a class and
43
+ # a corresponding instance variable in instances of the class
44
+ # All three are based on the name of the attribute:
45
+ #
46
+ # * The getter simply has the same name as the attribute
47
+ # * The predicate routine is called +should_update_<attribute name>?+
48
+ # * The updating routine is called +update_<attribute name>!+
49
+ #
50
+ # The getter can also be called with a boolean argument. If that
51
+ # argument is true, the getter will force the attribute to be updated
52
+ # without consulting the +should_update?+ predicate
53
+ #
54
+ # When a attribute is defined, the definition takes a block.
55
+ # Inside the block there is a small DSL that allows you to
56
+ # set the behavior of the +should_update?+ predicate and the +update_!+
57
+ # routine.
58
+ #
59
+ # A attribute definition might look something like this:
60
+ #
61
+ # sl_dynamic_attr :lollipop do |lollipop|
62
+ # lollipop.should_update? do
63
+ # self.lollipop_supply_is_low?
64
+ # end
65
+ #
66
+ # lollipop.to_update do
67
+ # candy_store.buy_lollipops(bakers_dozen)
68
+ # end
69
+ # end
70
+ #
71
+ module DynamicAttribute
72
+
73
+ # The DynamicAttributeDefinition inner class is to collect and
74
+ # store information about how and when a sl_dynamic_attr
75
+ # should be updated. This class is an implementation detail
76
+ # of dynamic attributes and is not intended to be useful
77
+ # outside of that context.
78
+ class DynamicAttributeDefinition
79
+ # the name of the attribute this definition is for
80
+ attr_reader :attribute_name
81
+
82
+ # The block to call in order to update the attribute. The
83
+ # return value of this block should be the new value of the
84
+ # attribute.
85
+ attr_reader :update_block
86
+
87
+ # The block to call to see if the attribute needs to be updated.
88
+ attr_reader :should_update_block
89
+
90
+ def initialize(attribute_name)
91
+ raise ArgumentError if attribute_name.nil?
92
+ raise ArgumentError if attribute_name.to_s.empty?
93
+
94
+ @attribute_name = attribute_name;
95
+ @update_block = Proc.new { nil; };
96
+ @should_update_block = Proc.new { true; }
97
+ end
98
+
99
+ # This method is used to provide behavior for the
100
+ # should_update_ predicate for the attribute
101
+ def should_update? (&block)
102
+ @should_update_block = block
103
+ end
104
+
105
+ # This method is used to provide the behavior for
106
+ # the update_! method for the attribute.
107
+ def to_update (&block)
108
+ @update_block = block
109
+ end
110
+ end
111
+
112
+ module ClassMethods
113
+ # sl_dynamic_attr declares a new dynamic softlayer attribute and accepts
114
+ # a block in which the should_update? and to_update methods for the
115
+ # attribute are established.
116
+ def sl_dynamic_attr (attribute_name, &block)
117
+ attribute_definition = DynamicAttributeDefinition.new(attribute_name)
118
+
119
+ # allow the block to update the attribute definition
120
+ yield attribute_definition if block_given?
121
+
122
+ # store off the attribute definition where we can find it later
123
+ @attribute_definitions ||= {};
124
+ @attribute_definitions[attribute_name] = attribute_definition;
125
+
126
+ # define a method called "update_<attribute_name>!" which calls the update block
127
+ # stored in the attribute definition
128
+ update_symbol = "update_#{attribute_name}!".to_sym
129
+ define_method(update_symbol, &attribute_definition.update_block)
130
+
131
+ # define a method called "should_update_<attribute_name>?" which calls the
132
+ # should update block stored in the attribute definition
133
+ should_update_symbol = "should_update_#{attribute_name}?".to_sym
134
+ define_method(should_update_symbol, &attribute_definition.should_update_block)
135
+
136
+ # define an instance method of the class this is being
137
+ # called on which will get the value of the attribute.
138
+ #
139
+ # The getter will take one argument "force_update" which
140
+ # is treated as boolean value. If true, then the getter will
141
+ # force the attribute to update (by using its "to_update") block.
142
+ #
143
+ # If the force variable is false, or not given, then the
144
+ # getter will call the "should update" block to find out if the
145
+ # attribute needs to be updated.
146
+ #
147
+ getter_name = attribute_name.to_sym
148
+ value_instance_variable = "@#{attribute_name}".to_sym
149
+
150
+ define_method(getter_name) do |*args|
151
+ force_update = args[0] || false
152
+
153
+ if force_update || __send__(should_update_symbol)
154
+ instance_variable_set(value_instance_variable, __send__(update_symbol))
155
+ end
156
+
157
+ instance_variable_get(value_instance_variable)
158
+ end
159
+ end
160
+
161
+ def sl_dynamic_attr_definition(attribute_name)
162
+ @attribute_definitions[attribute_name]
163
+ end
164
+ end
165
+
166
+ def self.included(included_in)
167
+ included_in.extend(ClassMethods)
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,141 @@
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
+ ##
25
+ # The SoftLayer Gem defines an Object Hierarchy representing entities in
26
+ # an account's SoftLayer environment. This class is the base object class
27
+ # for objects in that hierarchy
28
+ #
29
+ # The SoftLayer API represents entities as a hash of properties. This class
30
+ # stores that hash and allows the use of subscripting to access those properties
31
+ # directly.
32
+ #
33
+ # The class also has a model for making network requests that will refresh
34
+ # the stored hash so that it reflects the most up-to-date information about
35
+ # an entity from the server. Subclasses should override softlayer_properties
36
+ # to retrieve information from the server. Client code should call
37
+ # refresh_details to ask an object to update itself.
38
+ #
39
+ class ModelBase
40
+ # The client environment that this model object belongs to
41
+ attr_reader :softlayer_client
42
+
43
+ ##
44
+ # :attr_reader: id
45
+ # The unique identifier of this object within its API service
46
+
47
+ # Construct a new model object in the environment of the given client and
48
+ # with the given hash of network data (presumably returned by the SoftLayer API)
49
+ def initialize(softlayer_client, network_hash)
50
+ raise ArgumentError, "A hash is required" if nil == network_hash
51
+ raise ArgumentError, "Model objects must be created in the context of a client" if nil == softlayer_client
52
+
53
+ @softlayer_client = softlayer_client
54
+ @softlayer_hash = network_hash
55
+
56
+ raise ArgumentError, "The hash used to construct a softlayer model object must have an id" unless has_sl_property?(:id)
57
+ raise ArgumentError, "id must be non-nil and non-empty" unless self[:id]
58
+ end
59
+
60
+ ##
61
+ # The service method of a Model object should return a SoftLayer Service
62
+ # that best represents the modeled object. For example, a Ticket models
63
+ # a particular entity in the SoftLayer_Ticket service. The particular
64
+ # entity is identified by its id so the Ticket class would return
65
+ #
66
+ # softlayer_client["Ticket"].object_with_id
67
+ #
68
+ # which is a service which would allow calls to the ticket service
69
+ # through that particular object.
70
+ def service
71
+ raise "Abstract method service in ModelBase was called"
72
+ end
73
+
74
+ ##
75
+ # Asks a model object to reload itself from the SoftLayer API.
76
+ #
77
+ # Subclasses should not override this method, rather they should
78
+ # implement softlayer_properties to actually make the API request
79
+ # and return the new hash.
80
+ #
81
+ def refresh_details(object_mask = nil)
82
+ @softlayer_hash = self.softlayer_properties(object_mask)
83
+ end
84
+
85
+ ##
86
+ # Subclasses should implement this method as part of enabling the
87
+ # refresh_details fuctionality The implementation should make a request
88
+ # to the SoftLayer API and retrieve an up-to-date SoftLayer hash
89
+ # representation of this object. That hash should be the return value
90
+ # of this routine.
91
+ #
92
+ def softlayer_properties(object_mask = nil)
93
+ raise "Abstract method softlayer_properties in ModelBase was called"
94
+ end
95
+
96
+ ##
97
+ # Returns the value of of the given property as stored in the
98
+ # softlayer_hash. This gives you access to the low-level, raw
99
+ # properties that underly this model object. The need for this
100
+ # is not uncommon, but using this method should still be done
101
+ # with deliberation.
102
+ def [](softlayer_property)
103
+ self.softlayer_hash[softlayer_property.to_s]
104
+ end
105
+
106
+ ##
107
+ # Returns true if the given property can be found in the softlayer hash
108
+ def has_sl_property?(softlayer_property)
109
+ softlayer_hash && softlayer_hash.has_key?(softlayer_property.to_s)
110
+ end
111
+
112
+ ##
113
+ # allows subclasses to define attributes as sl_attr
114
+ # sl_attr are attributes that draw their value from the
115
+ # low-level hash representation of the object.
116
+ def self.sl_attr(attribute_symbol, hash_key = nil)
117
+ raise "The sl_attr expects a symbol for the attribute to define" unless attribute_symbol.kind_of?(Symbol)
118
+ raise "The hash key used to define an attribute cannot be empty" if hash_key && hash_key.empty?
119
+
120
+ define_method(attribute_symbol.to_sym) { self[hash_key ? hash_key : attribute_symbol.to_s]}
121
+ end
122
+
123
+ sl_attr :id
124
+
125
+ # When printing to the console using puts, ruby will call the
126
+ # to_ary method trying to convert an object into an array of lines
127
+ # for stdio. We override to_ary to return nil for model objects
128
+ # so they may be printed
129
+ def to_ary()
130
+ return nil;
131
+ end
132
+
133
+ protected
134
+
135
+ ##
136
+ # The softlayer_hash stores the low-level information about an
137
+ # object as it was retrieved from the SoftLayer API.
138
+ attr_reader :softlayer_hash
139
+
140
+ end # class ModelBase
141
+ end # module SoftLayer
@@ -35,9 +35,13 @@ module SoftLayer
35
35
  '!~' # Does not Contain (case sensitive)
36
36
  ]
37
37
 
38
- # A class whose instances represent an Object Filter operation.
38
+ # A class whose instances represent an Object Filter operator and the value it is applied to.
39
39
  class ObjectFilterOperation
40
+
41
+ # The operator, should be a member of the SoftLayer::OBJECT_FILTER_OPERATORS array
40
42
  attr_reader :operator
43
+
44
+ # The operand of the operator
41
45
  attr_reader :value
42
46
 
43
47
  def initialize(operator, value)
@@ -49,71 +53,105 @@ module SoftLayer
49
53
  end
50
54
 
51
55
  def to_h
52
- { 'operation' => "#{operator} #{value}"}
56
+ result = ObjectFilter.new
57
+ result['operation'] = "#{operator} #{value}"
58
+
59
+ result
53
60
  end
54
61
  end
55
62
 
56
- # Routines that are valid within the block provided to a call to
57
- # ObjectFilter.build.
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
+ #
58
68
  class ObjectFilterBlockHandler
59
- # contains wihout considering case
69
+ # Matches when the value is found within the field
70
+ # the search is not case sensitive
60
71
  def contains(value)
61
72
  ObjectFilterOperation.new('*=', value)
62
73
  end
63
74
 
64
- # case insensitive begins with
75
+ # Matches when the value is found at the beginning of the
76
+ # field. This search is not case sensitive
65
77
  def begins_with(value)
66
78
  ObjectFilterOperation.new('^=', value)
67
79
  end
68
80
 
69
- # case insensitive ends with
81
+ # Matches when the value is found at the end of the
82
+ # field. This search is not case sensitive
70
83
  def ends_with(value)
71
84
  ObjectFilterOperation.new('$=', value)
72
85
  end
73
86
 
74
- # matches exactly (ignoring case)
87
+ # Matches when the value in the field is exactly equal to the
88
+ # given value. This is a case-sensitive match
75
89
  def is(value)
76
90
  ObjectFilterOperation.new('_=', value)
77
91
  end
78
92
 
93
+ # Matches is the value in the field does not exactly equal
94
+ # the value passed in.
79
95
  def is_not(value)
80
96
  ObjectFilterOperation.new('!=', value)
81
97
  end
82
98
 
99
+ # Matches when the value in the field is greater than the given value
83
100
  def is_greater_than(value)
84
101
  ObjectFilterOperation.new('>', value)
85
102
  end
86
103
 
104
+ # Matches when the value in the field is less than the given value
87
105
  def is_less_than(value)
88
106
  ObjectFilterOperation.new('<', value)
89
107
  end
90
108
 
109
+ # Matches when the value in the field is greater than or equal to the given value
91
110
  def is_greater_or_equal_to(value)
92
111
  ObjectFilterOperation.new('>=', value)
93
112
  end
94
113
 
114
+ # Matches when the value in the field is less than or equal to the given value
95
115
  def is_less_or_equal_to(value)
96
116
  ObjectFilterOperation.new('<=', value)
97
117
  end
98
118
 
119
+ # Matches when the value is found within the field
120
+ # the search _is_ case sensitive
99
121
  def contains_exactly(value)
100
122
  ObjectFilterOperation.new('~', value)
101
123
  end
102
124
 
125
+ # Matches when the value is not found within the field
126
+ # the search _is_ case sensitive
103
127
  def does_not_contain(value)
104
128
  ObjectFilterOperation.new('!~', value)
105
129
  end
106
130
  end
107
131
 
108
- # An ObjectFilter is a hash that, when asked to provide
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
109
142
  # 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>
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.
114
150
  #
115
- # This allows you to create object filters by chaining [] calls:
116
- # object_filter["foo"]["bar"]["baz"] = 3 yields {"foo" => { "bar" => {"baz" => 3}}}
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}}}
117
155
  #
118
156
  class ObjectFilter < Hash
119
157
  # The default initialize for a hash is overridden
@@ -146,7 +184,7 @@ module SoftLayer
146
184
  end
147
185
 
148
186
  # if there is a block, then the query will come from
149
- # calling the block. We warn in debug mode if you override a
187
+ # calling the block. We warn in debug mode if you override a
150
188
  # query that was passed directly with the value from a block.
151
189
  if block
152
190
  $stderr.puts "The query from the block passed to ObjectFilter:build will override the query passed as a parameter" if $DEBUG && query
@@ -154,7 +192,7 @@ module SoftLayer
154
192
  query = block_handler.instance_eval(&block)
155
193
  end
156
194
 
157
- # If we have a query, we assign it's value to the last key
195
+ # If we have a query, we assign its value to the last key
158
196
  # otherwise, we build an emtpy filter at the bottom
159
197
  if query
160
198
  case
@@ -176,9 +214,9 @@ module SoftLayer
176
214
  result
177
215
  end
178
216
 
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
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
182
220
  #
183
221
  # Object Filter comparisons are done using operators. Some operators make
184
222
  # case sensitive comparisons and some do not. The general form of an Object
@@ -187,13 +225,15 @@ module SoftLayer
187
225
  # "*= smaug"
188
226
  #
189
227
  # The query language also accepts some aliases using asterisks
190
- # in a regular-expression-like way. Those aliases look like:
228
+ # in a regular-expression-like way. Those aliases look like:
191
229
  #
192
230
  # 'value' Exact value match (translates to '_= value')
193
231
  # 'value*' Begins with value (translates to '^= value')
194
232
  # '*value' Ends with value (translates to '$= value')
195
233
  # '*value*' Contains value (translates to '*= value')
196
234
  #
235
+ # This method corresponds to the +query_filter+ method in the SoftLayer-Python
236
+ # API.
197
237
  def self.query_to_filter_operation(query)
198
238
  if query.kind_of? String then
199
239
  query.strip!