From 0347108ec681655efa013b311e3f729cff4fb00f Mon Sep 17 00:00:00 2001 From: Iristyle Date: Mon, 3 Nov 2014 10:31:33 -0800 Subject: [PATCH] Add securitygroup acceptance tests - Add basic Mustache template for the sake of acceptance runs - Tests ensure that security groups can be added / deleted, and that when added their name, tags, description and ingress rules match Ingress rule matching is a little naive and could be improved - Refactor acceptance test helper to further transform an array of hashes (like ingress rules) into a more generalized data structure that can be used by Mustache templates. Mustache doesn't seem to deal with nested arrays very well, but if the array is a list of hashes with a :value key, then that can be translated more easily into a template. --- .../acceptance/fixtures/securitygroup.pp.tmpl | 19 +++ spec/acceptance/securitygroup_spec.rb | 160 ++++++++++++++++++ spec/spec_helper_acceptance.rb | 37 +++- 3 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 spec/acceptance/fixtures/securitygroup.pp.tmpl create mode 100644 spec/acceptance/securitygroup_spec.rb diff --git a/spec/acceptance/fixtures/securitygroup.pp.tmpl b/spec/acceptance/fixtures/securitygroup.pp.tmpl new file mode 100644 index 00000000..28510bd6 --- /dev/null +++ b/spec/acceptance/fixtures/securitygroup.pp.tmpl @@ -0,0 +1,19 @@ +ec2_securitygroup { '{{name}}': + ensure => {{ensure}}, + description => '{{description}}', + region => '{{region}}', + ingress => [ + {{#ingress}} + { + {{#values}} + {{k}} => '{{v}}', + {{/values}} + }, + {{/ingress}} + ], + tags => { + {{#tags}} + {{k}} => '{{v}}', + {{/tags}} + } +} diff --git a/spec/acceptance/securitygroup_spec.rb b/spec/acceptance/securitygroup_spec.rb new file mode 100644 index 00000000..2a3d4b34 --- /dev/null +++ b/spec/acceptance/securitygroup_spec.rb @@ -0,0 +1,160 @@ +require 'spec_helper_acceptance' +require 'securerandom' + +describe "ec2_securitygroup" do + + before(:all) do + @default_region = 'sa-east-1' + @ec2 = Ec2Helper.new(@default_region) + @template = 'securitygroup.pp.tmpl' + end + + def find_group(name) + groups = @ec2.get_groups(name) + expect(groups.count).to eq(1) + groups.first + end + + def has_matching_tags(group, tags) + group_tags = {} + group.tags.each { |s| group_tags[s.key.to_sym] = s.value if s.key != 'Name' } + + symmetric_difference = tags.to_set ^ group_tags.to_set + expect(symmetric_difference).to be_empty + end + + def get_group_permission(ip_permissions, group, protocol) + ip_permissions.detect do |perm| + pairs = perm[:user_id_group_pairs] + pairs.any? do |pair| + pair.group_name == group && perm[:ip_protocol] == protocol + end + end + end + + # a fairly naive matching algorithm, since the shape of ip_permissions is + # quite different than the shape of our ingress rules + def has_ingress_rule(rule, ip_permissions) + if (rule.has_key? :security_group) + group_name = rule[:security_group] + # a match occurs when AWS has a TCP / UDP / ICMP perm setup for group + tcp_perm = get_group_permission(ip_permissions, group_name, 'tcp') + udp_perm = get_group_permission(ip_permissions, group_name, 'udp') + icmp_perm = get_group_permission(ip_permissions, group_name, 'icmp') + match = !tcp_perm.nil? && !udp_perm.nil? && !icmp_perm.nil? + expect(match).to eq(true), "Could not find ingress rule for #{group_name}" + else + match = ip_permissions.any? do |perm| + rule[:protocol] == perm[:ip_protocol] && + rule[:port] == perm[:from_port] && + rule[:port] == perm[:to_port] && + perm[:ip_ranges].any? { |ip| ip[:cidr_ip] == rule[:cidr] } + end + msg = "Could not find ingress rule for #{rule[:protocol]} port #{rule[:port]} and CIDR #{rule[:cidr]}" + expect(match).to eq(true), msg + end + end + + describe 'should create a new security group' do + + before(:all) do + @name = "#{PuppetManifest.env_id}-#{SecureRandom.uuid}" + @config = { + :name => @name, + :ensure => 'present', + :description => 'aws acceptance securitygroup', + :region => @default_region, + :ingress => [ + { + :security_group => @name, + },{ + :protocol => 'tcp', + :port => 22, + :cidr => '0.0.0.0/0' + } + ], + :tags => { + :department => 'engineering', + :project => 'cloud', + :created_by => 'aws-acceptance' + } + } + + PuppetManifest.new(@template, @config).apply + @group = find_group(@config[:name]) + end + + after(:all) do + new_config = @config.update({:ensure => 'absent'}) + PuppetManifest.new(@template, new_config).apply + + expect(find_group(@config[:name])).to be_nil + end + + it "with the specified name" do + expect(@group.group_name).to eq(@config[:name]) + end + + it "with the specified tags" do + has_matching_tags(@group, @config[:tags]) + end + + it "with the specified description" do + expect(@group.description).to eq(@config[:description]) + end + + it "with the specified ingress rules" do + # perform a naive match + @config[:ingress].all? { |rule| has_ingress_rule(rule, @group.ip_permissions)} + end + + end + + describe 'should create a new securitygroup' do + + before(:each) do + @name = "#{PuppetManifest.env_id}-#{SecureRandom.uuid}" + @config = { + :name => @name, + :ensure => 'present', + :description => 'aws acceptance sg', + :region => @default_region, + :ingress => [ + { + :security_group => @name, + },{ + :protocol => 'tcp', + :port => 22, + :cidr => '0.0.0.0/0' + } + ], + :tags => { + :department => 'engineering', + :project => 'cloud', + :created_by => 'aws-acceptance' + } + } + + PuppetManifest.new(@template, @config).apply + @group = find_group(@config[:name]) + end + + after(:each) do + @config[:ensure] = 'absent' + PuppetManifest.new(@template, @config).apply + end + + it 'that can have tags changed' do + pending 'changing tags not yet supported for security groups' + has_matching_tags(@group, @config[:tags]) + + tags = {:created_by => 'aws-tests', :foo => 'bar'} + @config[:tags].update(tags) + + PuppetManifest.new(@template, @config).apply + @group = find_group(@config[:name]) + has_matching_tags(@group, @config[:tags]) + end + end + +end diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb index 9af7c44e..ca26a924 100644 --- a/spec/spec_helper_acceptance.rb +++ b/spec/spec_helper_acceptance.rb @@ -5,10 +5,7 @@ class PuppetManifest < Mustache def initialize(file, config) @template_file = File.join(Dir.getwd, 'spec', 'acceptance', 'fixtures', file) config.each do |key, value| - config_value = value - if (value.class == Hash) - config_value = value.map { |k, v| { :k => k, :v => v }} - end + config_value = self.class.to_generalized_data(value) instance_variable_set("@#{key}".to_sym, config_value) self.class.send(:attr_accessor, key) end @@ -18,6 +15,38 @@ def apply system("bundle exec puppet apply -e \"#{manifest}\" --modulepath ../") end + def self.to_generalized_data(val) + case val + when Hash + to_generalized_hash_list(val) + when Array + to_generalized_array_list(val) + else + val + end + end + + # returns an array of :k =>, :v => hashes given a Hash + # { :a => 'b', :c => 'd' } -> [{:k => 'a', :v => 'b'}, {:k => 'c', :v => 'd'}] + def self.to_generalized_hash_list(hash) + hash.map { |k, v| { :k => k, :v => v }} + end + + # necessary to build like [{ :values => Array }] rather than [[]] when there + # are nested hashes, for the sake of Mustache being able to render + # otherwise, simply return the item + def self.to_generalized_array_list(arr) + arr.map do |item| + if item.class == Hash + { + :values => to_generalized_hash_list(item) + } + else + item + end + end + end + def self.env_id @env_id ||= ( ENV['BUILD_DISPLAY_NAME'] ||