icontrol 0.3.2 → 0.3.3

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.
data/.yardopts ADDED
@@ -0,0 +1,9 @@
1
+ --no-private
2
+ --plugin examples-from-rspec
3
+ -m rdoc
4
+ lib/**/*rb
5
+ spec/**/*.rb
6
+ -
7
+ README.rdoc
8
+ LICENSE
9
+
data/README.rdoc CHANGED
@@ -17,19 +17,35 @@ In order to configure you just have to set up the username, password and the ser
17
17
 
18
18
  = Using the library
19
19
 
20
+ Bassically the library is the same as the one documented in the f5 devcentral but using ruby docs (http://devcentral.f5.com/wiki/default.aspx/iControl/APIReference.html). The main difference is that while the icontrol api does allow the bulk operation over a number of elements at the same time, in this case we limit it to just one at a time. So, for example, if you one to set the default pool for a virtual server, in the f5 doc, it says you have to pass 2 parameters
21
+ set_default_pool_name(
22
+ in String [] virtual_servers,
23
+ in String [] default_pools
24
+ );
25
+ So you can change several default pools at a time. On the contrary this library adopts a more object oriented design and only allows to work with one object at a time, i. e. and talking ruby, you don't have a set_default_pool_name class method but an instance one, so you have to first retreive the virtual server and later set its default pool. Something like this
26
+ IControl::LocalLB::VirtualServer.find("my_virtual_server).set_default_pool_name(:default_pool => "the default pool name")
27
+ if you want to do it in every virtual server then you do something like this.
28
+ IControl::LocalLB::VirtualServer.find(:all).each { |vs| vs.set_default_pool_name(:default_pool => "the default pool name") }
29
+ I think you get the point. Note that now the parameter name is :default_pool and not :default_pools, because of the semantics change.
30
+
20
31
  == Virtual Servers
21
32
 
22
33
  You can retreive, create, delete and modify virtual servers, for more information see IControl::LocalLB::VirtualServer. As an example of you what you can do:
23
34
 
24
35
  === Creating a virtual Server
25
- new_virtual_server = IControl::LocalLB::VirtualServer.create(:name => "foo_virtual_server",
26
- :address => "192.168.1.1",
27
- :port => "80",
28
- :protocol => IControl::Common::ProtocolType::PROTOCOL_TCP,
29
- :wildmask => "255.255.255.255",
30
- :type => IControl::LocalLB::VirtualServer::Type::RESOURCE_TYPE_POOL,
31
- :default_pool => foo_pool,
32
- :profiles => [])
36
+ IControl::LocalLB::VirtualServer.create(:definition => {
37
+ :address => "192.168.99.99",
38
+ :name => "test_virtual_server",
39
+ :port => "4",
40
+ :protocol => IControl::Common::ProtocolType::PROTOCOL_TCP
41
+ },
42
+ :wildmask => "255.255.255.255",
43
+ :resource => {
44
+ :type => IControl::LocalLB::VirtualServer::VirtualServerType::RESOURCE_TYPE_POOL,
45
+ :default_pool_name => ""
46
+ },
47
+ :profiles => [])
48
+
33
49
 
34
50
  === Obtaining an instance of a virtual server
35
51
 
@@ -39,7 +55,7 @@ You can retreive, create, delete and modify virtual servers, for more informatio
39
55
 
40
56
  === Changing its default pool
41
57
 
42
- my_virtual_server.default_pool = IControl::LocalLB::Pool.find("my_new_default_pool")
58
+ my_virtual_server.set_default_pool_name(:default_pool => "my_pool_name")
43
59
 
44
60
  === Destroying it
45
61
 
@@ -56,5 +72,5 @@ You can retreive, create, delete and modify virtual servers, for more informatio
56
72
  * Send me a pull request. Bonus points for topic branches.
57
73
 
58
74
  == Copyright
59
-
75
+ This documentation is based on the original documentation procided by F5 Networks, Inc., Seattle, Washington
60
76
  Copyright (c) 2010 Jose Fernandez (magec). See LICENSE for details.
data/lib/icontrol/base.rb CHANGED
@@ -13,6 +13,7 @@ require 'base/sequence'
13
13
  require 'base/predeclarations'
14
14
  require 'base/exception'
15
15
  require 'base/icontrol_overlay'
16
+ require 'base/xml'
16
17
 
17
18
  Savon.log = false
18
19
  HTTPI::Adapter.use = :net_http
@@ -51,6 +52,11 @@ module IControl
51
52
  include IControl::Base::Attributable
52
53
 
53
54
  class << self
55
+
56
+ attr_accessor :id_name
57
+ def set_id_name(name)
58
+ @id_name = name
59
+ end
54
60
 
55
61
  def class_name
56
62
  name.split("::").last
@@ -79,8 +85,49 @@ module IControl
79
85
  return @client
80
86
  end
81
87
 
88
+ def symbolize(data)
89
+ if data.is_a? Hash
90
+ out = data.inject({}) do |hash,(key,value)|
91
+ value = case value
92
+ when ::Hash then value["xsi:nil"] ? nil : symbolize(value)
93
+ when ::Array then value.map { |val| symbolize(val) rescue val }
94
+ when ::String then value
95
+ end
96
+
97
+ new_key = if Savon.strip_namespaces?
98
+ key.strip_namespace.snakecase.to_sym
99
+ else
100
+ key.snakecase
101
+ end
102
+ if hash[new_key] # key already exists, value should be added as an Array
103
+ hash[new_key] = [hash[new_key], value].flatten
104
+ result = hash
105
+ else
106
+ result = hash.merge new_key => value
107
+ end
108
+ result
109
+ end
110
+ if data.respond_to?(:empty_node?)
111
+ out.instance_eval{ @_empty_node = data.empty_node? }
112
+ end
113
+ return out
114
+ else
115
+ return data
116
+ end
117
+ end
118
+
119
+
82
120
  # Generic type mapping
83
121
  def map_response(response)
122
+
123
+ response = Crack::XML.parse(response).to_hash
124
+
125
+ envelope = response[response.keys.first] || {}
126
+ body_key = envelope.keys.find { |key| /.+:Body/ =~ key } rescue nil
127
+ response = body_key ? envelope[body_key] : {}
128
+
129
+ response = symbolize(response)
130
+
84
131
  response_key = response.keys.first
85
132
  if response_key
86
133
  if response[response_key].has_key? :return
@@ -155,6 +202,8 @@ module IControl
155
202
 
156
203
 
157
204
  def pluralize(string)
205
+ string = string.to_s.gsub(/ss$/,"sse")
206
+ return string if string[-1..-1] == "s"
158
207
  return "#{string}s"
159
208
  end
160
209
 
@@ -175,7 +224,7 @@ module IControl
175
224
  block.call(soap) if block
176
225
  request = soap.to_xml
177
226
  end
178
- return self.map_response(response.to_hash)
227
+ return self.map_response(response.to_xml)
179
228
  rescue Savon::HTTP::Error, Savon::SOAP::Fault => error
180
229
  IControl::Base::ExceptionFactory.raise_from_xml(error.http.body)
181
230
  end
@@ -186,6 +235,13 @@ module IControl
186
235
 
187
236
  end
188
237
 
238
+ def initialize(attributes)
239
+ @attributes = {}
240
+ id = attributes.delete(self.class.id_name) if attributes && attributes[self.class.id_name]
241
+ @attributes[:id] ||= id
242
+ super(attributes)
243
+ end
244
+
189
245
  def default_body
190
246
  { self.class.id_name.to_s => @attributes[:id] }
191
247
  end
@@ -7,38 +7,17 @@ module IControl
7
7
  def self.included(klass)
8
8
  klass.class_eval do
9
9
  include InstanceMethods
10
- class << self;
11
- attr_accessor :id_name
12
- def set_id_name(name)
13
- @id_name = name
14
- end
15
- end
16
10
  end
17
11
  end
18
12
 
19
13
  module InstanceMethods # :nodoc:
20
-
21
- def id
22
- @attributes[:id]
23
- end
24
14
 
25
15
  def type
26
16
  return @attributes[:type] || super
27
17
  end
28
18
 
29
19
  def initialize(attributes)
30
- id = attributes.delete(self.class.id_name) if attributes && attributes[self.class.id_name]
31
20
  @attributes = attributes || {}
32
- @attributes[:id] ||= id
33
-
34
- # Now we define an alias for the id_name
35
- id_name = self.class.id_name
36
-
37
- (class << self; self; end).instance_eval do
38
- define_method(id_name) do
39
- @attributes[:id]
40
- end
41
- end if id_name
42
21
  end
43
22
 
44
23
  def method_missing(method_name,*args,&block)
@@ -17,6 +17,7 @@ module IControl # :nodoc:
17
17
  return String if type == "y:string"
18
18
  return LongSequence if type == "y:longSequence"
19
19
  return StringSequence if type == "y:stringSequence"
20
+ return BooleanSequence if type == "y:booleanSequence"
20
21
 
21
22
  splitted = type.split(":")
22
23
  temp_name = [splitted.shift,*splitted.shift.split(".")]
@@ -6,6 +6,7 @@ class BasicSequence
6
6
  end
7
7
  def from_soap(xml)
8
8
  object = [*xml[:item]].map { |i| i.send(@conversion_method) }
9
+ return nil if object.empty?
9
10
  return object.length == 1 ? object.first : object
10
11
  end
11
12
  end
@@ -31,6 +32,14 @@ class LongSequence < BasicSequence
31
32
  set_conversion_method :to_i
32
33
  end
33
34
 
35
+ class BooleanSequence
36
+ def self.from_soap(xml)
37
+ object = [*xml[:item]].map { |i| i == "true" }
38
+ return object.length == 1 ? object.first : object
39
+ end
40
+ end
41
+
42
+
34
43
  # @private DoubleSequence
35
44
  class DoubleSequence < BasicSequence
36
45
  set_conversion_method :to_f
@@ -30,7 +30,11 @@ module IControl # :nodoc:
30
30
  if v == Numeric
31
31
  aux[k] = xml[k].to_i
32
32
  else
33
- aux[k] = xml[k]
33
+ if xml[k].empty_node?
34
+ aux[k] = nil
35
+ else
36
+ aux[k] = xml[k]
37
+ end
34
38
  end
35
39
  end
36
40
  end if xml
@@ -0,0 +1,231 @@
1
+ require 'rexml/parsers/streamparser'
2
+ require 'rexml/parsers/baseparser'
3
+ require 'rexml/light/node'
4
+ require 'rexml/text'
5
+ require 'date'
6
+ require 'time'
7
+ require 'yaml'
8
+ require 'bigdecimal'
9
+
10
+ # This is a slighly modified version of the XMLUtilityNode from
11
+ # http://merb.devjavu.com/projects/merb/ticket/95 (has.sox@gmail.com)
12
+ # It's mainly just adding vowels, as I ht cd wth n vwls :)
13
+ # This represents the hard part of the work, all I did was change the
14
+ # underlying parser.
15
+ class REXMLUtilityNode #:nodoc:
16
+ attr_accessor :name, :attributes, :children, :type, :empty_node
17
+
18
+ def self.typecasts
19
+ @@typecasts
20
+ end
21
+
22
+ def self.typecasts=(obj)
23
+ @@typecasts = obj
24
+ end
25
+
26
+ def self.available_typecasts
27
+ @@available_typecasts
28
+ end
29
+
30
+ def self.available_typecasts=(obj)
31
+ @@available_typecasts = obj
32
+ end
33
+
34
+ self.typecasts = {}
35
+ self.typecasts["integer"] = lambda{|v| v.nil? ? nil : v.to_i}
36
+ self.typecasts["boolean"] = lambda{|v| v.nil? ? nil : (v.strip != "false")}
37
+ self.typecasts["datetime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
38
+ self.typecasts["date"] = lambda{|v| v.nil? ? nil : Date.parse(v)}
39
+ self.typecasts["dateTime"] = lambda{|v| v.nil? ? nil : Time.parse(v).utc}
40
+ self.typecasts["decimal"] = lambda{|v| v.nil? ? nil : BigDecimal(v.to_s)}
41
+ self.typecasts["double"] = lambda{|v| v.nil? ? nil : v.to_f}
42
+ self.typecasts["float"] = lambda{|v| v.nil? ? nil : v.to_f}
43
+ self.typecasts["symbol"] = lambda{|v| v.nil? ? nil : v.to_sym}
44
+ self.typecasts["string"] = lambda{|v| v.to_s}
45
+ self.typecasts["yaml"] = lambda{|v| v.nil? ? nil : YAML.load(v)}
46
+ self.typecasts["base64Binary"] = lambda{|v| v.unpack('m').first }
47
+
48
+ self.available_typecasts = self.typecasts.keys
49
+
50
+ def initialize(name, normalized_attributes = {})
51
+
52
+ # unnormalize attribute values
53
+ attributes = Hash[* normalized_attributes.map { |key, value|
54
+ [ key, unnormalize_xml_entities(value) ]
55
+ }.flatten]
56
+
57
+ @name = name.tr("-", "_")
58
+ # leave the type alone if we don't know what it is
59
+ @type = self.class.available_typecasts.include?(attributes["type"]) ? attributes.delete("type") : attributes["type"]
60
+
61
+ @nil_element = attributes.delete("nil") == "true"
62
+ @attributes = undasherize_keys(attributes)
63
+ @children = []
64
+ @text = false
65
+ @empty_node = false
66
+ end
67
+
68
+ def add_node(node)
69
+ @text = true if node.is_a? String
70
+ @children << node
71
+ end
72
+
73
+ def emptyze!(obj,value)
74
+ # Monkeypatch to allow empty_node? in the hash values
75
+ obj.class.instance_eval do
76
+ define_method(:empty_node?) { @_empty_node }
77
+ end
78
+ obj.instance_eval { @_empty_node = value }
79
+ end
80
+
81
+ def to_hash
82
+ if @type == "file"
83
+ f = StringIO.new((@children.first || '').unpack('m').first)
84
+ class << f
85
+ attr_accessor :original_filename, :content_type
86
+ end
87
+ f.original_filename = attributes['name'] || 'untitled'
88
+ f.content_type = attributes['content_type'] || 'application/octet-stream'
89
+ return {name => f}
90
+ end
91
+
92
+ if @text
93
+ t = typecast_value( unnormalize_xml_entities( inner_html ) )
94
+ t.class.send(:attr_accessor, :attributes)
95
+ t.attributes = attributes
96
+ emptyze!(t,false)
97
+ return { name => t }
98
+ else
99
+ #change repeating groups into an array
100
+ groups = @children.inject({}) { |s,e| (s[e.name] ||= []) << e; s }
101
+
102
+ out = nil
103
+ if @type == "array"
104
+ out = []
105
+ groups.each do |k, v|
106
+ if v.size == 1
107
+ out << v.first.to_hash.entries.first.last
108
+ else
109
+ out << v.map{|e| e.to_hash[k]}
110
+ end
111
+ end
112
+ out = out.flatten
113
+
114
+ else # If Hash
115
+ out = {}
116
+ groups.each do |k,v|
117
+ if v.size == 1
118
+ out.merge!(v.first)
119
+ else
120
+ out.merge!( k => v.map{|e| e.to_hash[k]})
121
+ end
122
+ end
123
+ out.merge! attributes unless attributes.empty?
124
+ out = out.empty? ? nil : out
125
+ end
126
+
127
+ if @type && out.nil?
128
+ out = typecast_value(out)
129
+ end
130
+ emptyze!(out,@empty_node)
131
+
132
+ { name => out }
133
+ end
134
+ end
135
+
136
+ # Typecasts a value based upon its type. For instance, if
137
+ # +node+ has #type == "integer",
138
+ # {{[node.typecast_value("12") #=> 12]}}
139
+ #
140
+ # @param value<String> The value that is being typecast.
141
+ #
142
+ # @details [:type options]
143
+ # "integer"::
144
+ # converts +value+ to an integer with #to_i
145
+ # "boolean"::
146
+ # checks whether +value+, after removing spaces, is the literal
147
+ # "true"
148
+ # "datetime"::
149
+ # Parses +value+ using Time.parse, and returns a UTC Time
150
+ # "date"::
151
+ # Parses +value+ using Date.parse
152
+ #
153
+ # @return <Integer, TrueClass, FalseClass, Time, Date, Object>
154
+ # The result of typecasting +value+.
155
+ #
156
+ # @note
157
+ # If +self+ does not have a "type" key, or if it's not one of the
158
+ # options specified above, the raw +value+ will be returned.
159
+ def typecast_value(value)
160
+ return value unless @type
161
+ proc = self.class.typecasts[@type]
162
+ proc.nil? ? value : proc.call(value)
163
+ end
164
+
165
+ # Take keys of the form foo-bar and convert them to foo_bar
166
+ def undasherize_keys(params)
167
+ params.keys.each do |key, value|
168
+ params[key.tr("-", "_")] = params.delete(key)
169
+ end
170
+ params
171
+ end
172
+
173
+ # Get the inner_html of the REXML node.
174
+ def inner_html
175
+ @children.join
176
+ end
177
+
178
+ # Converts the node into a readable HTML node.
179
+ #
180
+ # @return <String> The HTML node in text form.
181
+ def to_html
182
+ attributes.merge!(:type => @type ) if @type
183
+ "<#{name}#{attributes.to_xml_attributes}>#{@nil_element ? '' : inner_html}</#{name}>"
184
+ end
185
+
186
+ # @alias #to_html #to_s
187
+ def to_s
188
+ to_html
189
+ end
190
+
191
+ private
192
+
193
+ def unnormalize_xml_entities value
194
+ REXML::Text.unnormalize(value)
195
+ end
196
+ end
197
+
198
+ module Crack
199
+ class XML
200
+ def self.parse(xml)
201
+ stack = []
202
+ parser = REXML::Parsers::BaseParser.new(xml)
203
+ inner_elements = [0]
204
+
205
+ while true
206
+ event = parser.pull
207
+ case event[0]
208
+ when :end_document
209
+ break
210
+ when :end_doctype, :start_doctype
211
+ # do nothing
212
+ when :start_element
213
+ stack.push REXMLUtilityNode.new(event[1], event[2])
214
+ inner_elements[-1] += 1
215
+ inner_elements << 0
216
+ when :end_element
217
+ if stack.size > 1
218
+ temp = stack.pop
219
+ temp.empty_node = true if inner_elements.pop == 0
220
+ stack.last.add_node(temp)
221
+ else
222
+ stack.last.empty_node = true if inner_elements.pop == 0
223
+ end
224
+ when :text, :cdata
225
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0 || stack.empty?
226
+ end
227
+ end
228
+ stack.length > 0 ? stack.pop.to_hash : {}
229
+ end
230
+ end
231
+ end