From: marios <[email protected]>
Signed-off-by: marios <[email protected]> --- server/Gemfile | 3 +- server/deltacloud.rb | 2 + server/lib/deltacloud/base_driver/base_driver.rb | 14 ++- server/lib/deltacloud/base_driver/features.rb | 8 +- server/lib/deltacloud/drivers/ec2/ec2_driver.rb | 149 +++++++++++++++++++++- server/lib/deltacloud/models/bucket.rb | 2 +- server/lib/deltacloud/models/firewall.rb | 22 +++ server/lib/deltacloud/models/firewall_rule.rb | 23 ++++ server/public/javascripts/application.js | 63 +++++++-- server/server.rb | 112 ++++++++++++++++ server/views/firewalls/index.html.haml | 25 ++++ server/views/firewalls/index.xml.haml | 23 ++++ server/views/firewalls/new.html.haml | 11 ++ server/views/firewalls/new_rule.html.haml | 26 ++++ server/views/firewalls/show.html.haml | 44 +++++++ server/views/firewalls/show.xml.haml | 21 +++ 16 files changed, 522 insertions(+), 26 deletions(-) create mode 100644 server/lib/deltacloud/models/firewall.rb create mode 100644 server/lib/deltacloud/models/firewall_rule.rb create mode 100644 server/views/firewalls/index.html.haml create mode 100644 server/views/firewalls/index.xml.haml create mode 100644 server/views/firewalls/new.html.haml create mode 100644 server/views/firewalls/new_rule.html.haml create mode 100644 server/views/firewalls/show.html.haml create mode 100644 server/views/firewalls/show.xml.haml diff --git a/server/Gemfile b/server/Gemfile index 3ab3d5c..ee5f805 100644 --- a/server/Gemfile +++ b/server/Gemfile @@ -15,7 +15,8 @@ group :azure do end group :ec2 do - gem 'aws' + gem 'aws', ">= 2.5.4" + end group :gogrid do diff --git a/server/deltacloud.rb b/server/deltacloud.rb index 7caf34f..5628e31 100644 --- a/server/deltacloud.rb +++ b/server/deltacloud.rb @@ -36,6 +36,8 @@ require 'deltacloud/models/storage_volume' require 'deltacloud/models/bucket' require 'deltacloud/models/blob' require 'deltacloud/models/load_balancer' +require 'deltacloud/models/firewall' +require 'deltacloud/models/firewall_rule' require 'deltacloud/validation' require 'deltacloud/helpers' diff --git a/server/lib/deltacloud/base_driver/base_driver.rb b/server/lib/deltacloud/base_driver/base_driver.rb index 2c780d9..7f8bd65 100644 --- a/server/lib/deltacloud/base_driver/base_driver.rb +++ b/server/lib/deltacloud/base_driver/base_driver.rb @@ -149,7 +149,12 @@ module Deltacloud # def keys(credentials, opts) # def create_key(credentials, opts) # def destroy_key(credentials, opts) - + # + # def firewalls(credentials, opts) + # def create_firewall(credentials, opts) + # def delete_firewall(credentials, opts) + # def create_firewall_rule(credentials, opts) + # def delete_firewall_rule(credentials, opts) def realm(credentials, opts) realms = realms(credentials, opts).first if has_capability?(:realms) end @@ -183,8 +188,12 @@ module Deltacloud keys(credentials, opts).first if has_capability?(:keys) end + def firewall(credentials, opts={}) + firewalls(credentials, opts).first if has_capability?(:firewalls) + end + MEMBER_SHOW_METHODS = - [ :realm, :image, :instance, :storage_volume, :bucket, :blob, :key ] + [ :realm, :image, :instance, :storage_volume, :bucket, :blob, :key, :firewall ] def has_capability?(capability) if MEMBER_SHOW_METHODS.include?(capability.to_sym) @@ -194,7 +203,6 @@ module Deltacloud end end - def filter_on(collection, attribute, opts) return collection if opts.nil? return collection if opts[attribute].nil? diff --git a/server/lib/deltacloud/base_driver/features.rb b/server/lib/deltacloud/base_driver/features.rb index b5bc3ee..4a2f1f8 100644 --- a/server/lib/deltacloud/base_driver/features.rb +++ b/server/lib/deltacloud/base_driver/features.rb @@ -187,11 +187,11 @@ module Deltacloud end end - declare_feature :instances, :security_group do - description "Put instance in one or more security groups on launch" + declare_feature :instances, :firewall do + description "Put instance in one or more firewalls (security groups) on launch" operation :create do - param :security_group, :array, :optional, [], - "Array of security group names" + param :firewalls, :array, :optional, nil, "Array of firewall ID strings" + "Array of firewall (security group) id" end end diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb index 4edd989..fbf39f1 100644 --- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb +++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb @@ -33,12 +33,13 @@ module Deltacloud class EC2Driver < Deltacloud::BaseDriver def supported_collections - DEFAULT_COLLECTIONS + [ :keys, :buckets, :load_balancers, :addresses ] + + DEFAULT_COLLECTIONS + [ :keys, :buckets, :load_balancers, :addresses, :firewalls ] end feature :instances, :user_data feature :instances, :authentication_key - feature :instances, :security_group + feature :instances, :firewall feature :instances, :instance_count feature :images, :owner_id feature :buckets, :bucket_location @@ -201,7 +202,7 @@ module Deltacloud instance_options.merge!(:key_name => opts[:keyname]) if opts[:keyname] instance_options.merge!(:availability_zone => opts[:realm_id]) if opts[:realm_id] instance_options.merge!(:instance_type => opts[:hwp_id]) if opts[:hwp_id] && opts[:hwp_id].length > 0 - instance_options.merge!(:group_ids => opts[:security_group]) if opts[:security_group] + instance_options.merge!(:group_ids => opts[:firewalls]) if opts[:firewalls] instance_options.merge!( :min_count => opts[:instance_count], :max_count => opts[:instance_count] @@ -571,6 +572,73 @@ module Deltacloud end end +#-- +#FIREWALLS - ec2 security groups +#-- + def firewalls(credentials, opts={}) + ec2 = new_client(credentials) + the_firewalls = [] + groups = [] + safely do + if opts[:id] + groups = ec2.describe_security_groups([opts[:id]]) + else + groups = ec2.describe_security_groups() + end + end + groups.each do |security_group| + the_firewalls << convert_security_group(security_group) + end + the_firewalls + end + +#-- +#Create firewall +#-- + def create_firewall(credentials, opts={}) + ec2 = new_client(credentials) + safely do + ec2.create_security_group(opts["name"], opts["description"]) + end + Firewall.new( { :id=>opts["name"], :name=>opts["name"], + :description => opts["description"], :owner_id => "", :rules => [] } ) + end + +#-- +#Delete firewall +#-- + def delete_firewall(credentials, opts={}) + ec2 = new_client(credentials) + safely do + ec2.delete_security_group(opts["id"]) + end + end +#-- +#Create firewall rule +#-- + def create_firewall_rule(credentials, opts={}) + ec2 = new_client(credentials) + groups = [] + opts['groups'].each do |k,v| + groups << {"group_name" => k, "owner" =>v} + end + safely do + ec2.manage_security_group_ingress(opts['id'], opts['from_port'], opts['to_port'], opts['protocol'], + "authorize", opts['addresses'], groups) + end + end +#-- +#Delete firewall rule +#-- + def delete_firewall_rule(credentials, opts={}) + ec2 = new_client(credentials) + firewall = opts[:id] + protocol, from_port, to_port, addresses, groups = firewall_rule_params(opts[:rule_id]) + safely do + ec2.manage_security_group_ingress(firewall, from_port, to_port, protocol, "revoke", addresses, groups) + end + end + def valid_credentials?(credentials) retval = true begin @@ -764,6 +832,81 @@ module Deltacloud balancer end + #generate uid from firewall rule parameters (amazon doesn't do this for us + def firewall_rule_id(user_id, protocol, from_port, to_port, sources) + sources_string = "" + sources.each do |source| + sources_string<<"@" + source.each_pair do |key,value| + sources_string<< "#{value}," + end + sources_string.chomp!(",") + end + #sources_string is @group,297467797945,test@address,ipv4,10.1.1.1,24 etc + id_string = "#{user_id}~#{protocol}~#{from_port}~#{to_port}~#{sources_string}" + end + + #extract params from uid + def firewall_rule_params(id) + #user_id~protocol~from_port~to_port~sources_string + params = id.split("~") + protocol = params[1] + from_port = params[2] + to_port = params[3] + sources = params[4].split("@") + sources.shift #first match is "" + addresses = [] + groups = [] + #@group,297467797945,test@address,ipv4,10.1.1.1,24@address,ipv4,192.168.1.1,24 + sources.each do |source| + current = source.split(",") + type = current[0] + case type + when 'group' + #group,297467797945,test + owner = current[1] + name = current[2] + groups << {'group_name' => name, 'owner' => owner} + when 'address' + #address,ipv4,10.1.1.1,24 + address = current[2] + address<<"/#{current[3]}" + addresses << address + end + end + return protocol, from_port, to_port, addresses, groups + end + + #Convert ec2 security group to server/lib/deltacloud/models/firewall + def convert_security_group(security_group) + rules = [] + security_group[:aws_perms].each do |perm| + sources = [] + perm[:groups].each do |group| + sources << {:type => "group", :name => group[:group_name], :owner => group[:owner]} + end + perm[:ip_ranges].each do |ip| + sources << {:type => "address", :family=>"ipv4", + :address=>ip[:cidr_ip].split("/").first, + :prefix=>ip[:cidr_ip].split("/").last} + end + rule_id = firewall_rule_id(security_group[:aws_owner], perm[:protocol], + perm[:from_port] , perm[:to_port], sources) + rules << FirewallRule.new({:id => rule_id, + :allow_protocol => perm[:protocol], + :port_from => perm[:from_port], + :port_to => perm[:to_port], + :direction => 'ingress', + :sources => sources}) + end + Firewall.new( { :id => security_group[:aws_group_name], + :name => security_group[:aws_group_name], + :description => security_group[:aws_description], + :owner_id => security_group[:aws_owner], + :rules => rules + } ) + end + def convert_state(ec2_state) case ec2_state when "terminated" diff --git a/server/lib/deltacloud/models/bucket.rb b/server/lib/deltacloud/models/bucket.rb index e6b2b34..bef36ac 100644 --- a/server/lib/deltacloud/models/bucket.rb +++ b/server/lib/deltacloud/models/bucket.rb @@ -24,7 +24,7 @@ class Bucket < BaseModel def to_hash h = self.to_hash_original - h[:blob_list] = self.blob_list.collect { |blob| { :id => blob, + h[:blob_list] = self.blob_list.collect { |blob| { :id => blob, :href => "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/buckets/#{self.id}/#{blob.id}"}} return h end diff --git a/server/lib/deltacloud/models/firewall.rb b/server/lib/deltacloud/models/firewall.rb new file mode 100644 index 0000000..dc0ae3d --- /dev/null +++ b/server/lib/deltacloud/models/firewall.rb @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +class Firewall < BaseModel + attr_accessor :name + attr_accessor :description + attr_accessor :owner_id + attr_accessor :rules +end \ No newline at end of file diff --git a/server/lib/deltacloud/models/firewall_rule.rb b/server/lib/deltacloud/models/firewall_rule.rb new file mode 100644 index 0000000..3959eb6 --- /dev/null +++ b/server/lib/deltacloud/models/firewall_rule.rb @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +class FirewallRule < BaseModel + attr_accessor :allow_protocol # tcp/udp/icmp + attr_accessor :port_from + attr_accessor :port_to + attr_accessor :sources + attr_accessor :direction #ingress egress +end diff --git a/server/public/javascripts/application.js b/server/public/javascripts/application.js index 95c9bc2..703879c 100644 --- a/server/public/javascripts/application.js +++ b/server/public/javascripts/application.js @@ -19,10 +19,10 @@ $(document).ready(function() { function more_fields() { - //increment the hidden input that captures how many meta_data are passed - var meta_params = document.getElementsByName('meta_params') + //increment the hidden input that captures how many meta_data are passed + var meta_params = document.getElementsByName('meta_params') current_number_params = eval(meta_params[0].value)+1 - meta_params[0].value = current_number_params + meta_params[0].value = current_number_params var new_meta = document.getElementById('metadata_holder').cloneNode(true); new_meta.id = 'metadata_holder' + current_number_params; new_meta.style.display = 'block'; @@ -39,15 +39,50 @@ function more_fields() function less_fields() { var meta_params = document.getElementsByName('meta_params') - current_val = eval(meta_params[0].value) - if (current_val == 0) - { - return; - } - else - { - var theDiv = document.getElementById('metadata_holder'+current_val) - theDiv.parentNode.removeChild(theDiv) - meta_params[0].value = eval(current_val)-1 - } + current_val = eval(meta_params[0].value) + if (current_val == 0) + { + return; + } + else + { + var theDiv = document.getElementById('metadata_holder'+current_val) + theDiv.parentNode.removeChild(theDiv) + meta_params[0].value = eval(current_val)-1 + } +} + +var addresses = 0; +var groups = 0; +function make_fields(type) +{ + form = document.getElementById("new_rule_form") + button = document.getElementById("submit_button") + if(type == "address") + { + name = "ip_address" + eval(++addresses) + create_rule_source_field(name, "Address " + eval(addresses) + " [use CIDR notation 0.0.0.0/0]", form, button) + } + else if(type == "group") + { + name = "group" + eval(++groups) + create_rule_source_field(name, "Name of group " + eval(groups), form, button) + name = "group" + eval(groups) + "owner" + create_rule_source_field(name, "Group " + eval(groups) + " owner (required)", form, button) + } +} + +function create_rule_source_field(name, label, form, button) +{ + element = document.createElement("INPUT") + element.type = "input" + element.size = 35 + element.name = name + text = document.createTextNode(label) + form.insertBefore(element, button) + form.insertBefore(text, element) + form.insertBefore(document.createElement('BR'), element) + form.insertBefore(document.createElement('BR'), button) + form.insertBefore(document.createElement('BR'), button) + form.insertBefore(document.createElement('BR'), button) } diff --git a/server/server.rb b/server/server.rb index ba87c67..ae23263 100644 --- a/server/server.rb +++ b/server/server.rb @@ -948,3 +948,115 @@ collection :addresses do end end + +#html for creating a new firewall +get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/firewalls/new" do + respond_to do |format| + format.html { haml :"firewalls/new" } + end +end + +#html for creating a new firewall rule +get "#{Sinatra::UrlForHelper::DEFAULT_URI_PREFIX}/firewalls/:firewall/new_rule" do + @firewall_name = params[:firewall] + respond_to do |format| + format.html {haml :"firewalls/new_rule" } + end +end + +#FIREWALLS +collection :firewalls do + description "Allow user to define firewall rules for an instance (ec2 security groups) eg expose ssh access [port 22, tcp]." + operation :index do + description 'List all firewalls' + with_capability :firewalls + control { filter_all(:firewalls) } + end + + operation :show do + description 'Show details for a specific firewall - list all rules' + with_capability :firewall + param :id, :string, :required + control { show(:firewall) } + end + + operation :create do + description 'Create a new firewall' + with_capability :create_firewall + param :name, :string, :required + param :description, :string, :required + control do + @firewall = driver.create_firewall(credentials, params ) + respond_to do |format| + format.xml do + response.status = 201 # Created + haml :"firewalls/show" + end + format.html {haml :"firewalls/show"} + format.json {convert_to_json(:firewall, @firewall)} + end + end + end + + operation :destroy do + description 'Delete a specified firewall - error if firewall has rules' + with_capability :delete_firewall + param :id, :string, :required + control do + driver.delete_firewall(credentials, params) + respond_to do |format| + format.xml { 204 } + format.json { 204 } + format.html { redirect(firewalls_url) } + end + end + end + +#create a new firewall rule - POST /api/firewalls/:firewall/rules + operation :rules, :method => :post, :member => true do + description 'Create a new firewall rule for the specified firewall' + param :firewall, :required, :string, [], "Name of firewall in which to apply this rule" + param :protocol, :required, :string, ['tcp','udp','icmp'], "Transport layer protocol for the rule" + param :from_port, :required, :string, [], "Start of port range for the rule" + param :to_port, :required, :string, [], "End of port range for the rule" + with_capability :create_firewall_rule + control do + #source IPs from params + addresses = params.inject([]){|result,current| result << current.last unless current.grep(/^ip[-_]address/i).empty?; result} + #source groups from params + groups = {} + max_groups = params.select{|k,v| k=~/^group/}.size/2 + for i in (1..max_groups) do + groups.merge!({params["group#{i}"]=>params["group#{i}owner"]}) + end + params.merge!( {'addresses' => addresses} ) ; params.merge!( {'groups' => groups} ) + driver.create_firewall_rule(credentials, params) + @firewall = driver.firewall(credentials, {:id => params[:firewall]}) + respond_to do |format| + format.html {haml :"firewalls/show"} + format.xml do + response.status = 201 #created + haml :"firewall/show" + end + format.json {convert_to_json(:firewall, @firewall)} + end + end + end + +#delete a firewall rule DELETE /api/firewalls/:firewall/rule - with param rule_id + operation :rule, :method => :delete, :member => true do + description 'Delete the specified firewall rule from the given firewall' + param :firewall, :required, :string + param :rule_id, :required, :string + with_capability :delete_firewall_rule + control do + driver.delete_firewall_rule(credentials, params) + respond_to do |format| + format.html {redirect firewall_url(params[:id])} + format.xml {204} + format.json {204} + end + end + end + +end #firewalls diff --git a/server/views/firewalls/index.html.haml b/server/views/firewalls/index.html.haml new file mode 100644 index 0000000..3312a32 --- /dev/null +++ b/server/views/firewalls/index.html.haml @@ -0,0 +1,25 @@ +%h1 Firewalls +%br +%p + =link_to 'Create new firewall', "/api/firewalls/new" +%table.display + %thead + %tr + %th Id + %th Name + %th Description + %th Owner ID + %th Rules + %tbody + - @firewalls.each do |firewall| + %tr + %td + = link_to firewall.id, firewall_url(firewall.id) + %td + = firewall.name + %td + = firewall.description + %td + = firewall.owner_id + %td + = link_to 'view rules', firewall_url(firewall.id) diff --git a/server/views/firewalls/index.xml.haml b/server/views/firewalls/index.xml.haml new file mode 100644 index 0000000..f027785 --- /dev/null +++ b/server/views/firewalls/index.xml.haml @@ -0,0 +1,23 @@ +!!! XML +%firewalls + - @firewalls.each do |firewall| + %firewall{:href => firewall_url(firewall.id), :id => firewall.id} + - firewall.attributes.select{ |attr| attr != :id && attr!= :rules}.each do |attribute| + - haml_tag("#{attribute}".tr('-', '_'), :<) do + - if [:name, :description].include?(attribute) + =cdata do + - haml_concat firewall.send(attribute) + - else + - haml_concat firewall.send(attribute) + %rules + - firewall.rules.each do |rule| + %rule{:id => rule.id} + - rule.attributes.select{|attr| attr != :sources && attr != :id}.each do |rule_attrib| + - haml_tag("#{rule_attrib}".tr('-', '_'), :<) do + - haml_concat rule.send(rule_attrib) + %sources + - rule.sources.each do |source| + - if source[:type] == "group" + %source{:name => source[:name], :type=> source[:type], :owner=> source[:owner]} + - else + %source{:prefix => source[:prefix], :address=> source[:address], :family=>source[:family], :type => source[:type]} \ No newline at end of file diff --git a/server/views/firewalls/new.html.haml b/server/views/firewalls/new.html.haml new file mode 100644 index 0000000..4a230a6 --- /dev/null +++ b/server/views/firewalls/new.html.haml @@ -0,0 +1,11 @@ +%h1 New Firewall + +%form{:action => firewalls_url, :method => :post} + %label + Firewall Name + %input{:name => 'name', :size => 25}/ + %br + %label + Firewall Description + %input{:name => 'description', :size => 100}/ + %input{:type => :submit, :name => "commit", :value=>"create"} \ No newline at end of file diff --git a/server/views/firewalls/new_rule.html.haml b/server/views/firewalls/new_rule.html.haml new file mode 100644 index 0000000..b25206a --- /dev/null +++ b/server/views/firewalls/new_rule.html.haml @@ -0,0 +1,26 @@ +%h1 New Firewall Rule + +%form{ :action => "#{firewall_url(@firewall_name)}/rules", :id => "new_rule_form", :method => :post, :enctype => 'multipart/form-data'} + %label + Protocol: + %br + %input{ :name => 'protocol', :size => 10}/ + %br + %br + %label + From port: + %br + %input{ :name => 'from_port', :size => 10}/ + %br + %br + To port: + %br + %input{ :name => 'to_port', :size => 10}/ + %br + %br + %a{ :href => "javascript:;", :onclick => "make_fields('address');"} Add source IP address + %br + %a{ :href => "javascript:;", :onclick => "make_fields('group');"} Add source group + %br + %br + %input{ :type => :submit, :id => "submit_button", :name => "commit", :value => "create"}/ diff --git a/server/views/firewalls/show.html.haml b/server/views/firewalls/show.html.haml new file mode 100644 index 0000000..b77aaa4 --- /dev/null +++ b/server/views/firewalls/show.html.haml @@ -0,0 +1,44 @@ +%h1 Firewall +%h2 + = @firewall.id +%dl + %di + %dt Name + %dd + = @firewall.name + %dt Owner + %dd + = @firewall.owner_id + %dt Description + %dd + = @firewall.description + +%h2 + Rules + %br + %p + =link_to 'Create a new rule', "/api/firewalls/#{@firewall.name}/new_rule" +%dl + - @firewall.rules.each do |rule| + %di + Rule + - rule.attributes.select{|attr| attr != :sources}.each do |attrib| + %dt #{attrib} + %dd + = rule.send(attrib) + %dt sources + %dd + - rule.sources.each do |source| + - if source[:type] == "group" + type: #{source[:type]}, name: #{source[:name]}, owner: #{source[:owner]} + %br + - else + type: #{source[:type]}, family: #{source[:family]}, address: #{source[:address]}, prefix: #{source[:prefix]} + %br + %dd + %form{ :action => "#{firewall_url(@firewall.name)}/rule", :method => :post} + %input{:type => "hidden", :name => "_method", :value => "delete"} + %input{:type => "hidden", :name => "rule_id", :value => rule.id} + %input{:type => :submit, :value => "Delete Rule"} + %dd + = link_to_action 'Delete Firewall', destroy_firewall_url(@firewall.name), :delete diff --git a/server/views/firewalls/show.xml.haml b/server/views/firewalls/show.xml.haml new file mode 100644 index 0000000..9d1fc48 --- /dev/null +++ b/server/views/firewalls/show.xml.haml @@ -0,0 +1,21 @@ +!!! XML +%firewall{:href => firewall_url(@firewall.id), :id => @firewall.id} + - @firewall.attributes.select{ |attr| attr != :id && attr!= :rules}.each do |attribute| + - haml_tag("#{attribute}".tr('-', '_'), :<) do + - if [:name, :description].include?(attribute) + =cdata do + - haml_concat @firewall.send(attribute) + - else + - haml_concat @firewall.send(attribute) + %rules + - @firewall.rules.each do |rule| + %rule{:id => rule.id} + - rule.attributes.select{|attr| attr != :sources && attr != :id}.each do |rule_attrib| + - haml_tag("#{rule_attrib}".tr('-', '_'), :<) do + - haml_concat rule.send(rule_attrib) + %sources + - rule.sources.each do |source| + - if source[:type] == "group" + %source{:name => source[:name], :type=> source[:type], :owner=>source[:owner]} + - else + %source{:prefix => source[:prefix], :address=> source[:address], :family=>source[:family], :type => source[:type]} \ No newline at end of file -- 1.7.3.4
