softlayer_api 2.0.1 → 2.1.0

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