icontrol 0.3.2 → 0.3.3

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