chef-provisioning-aws 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,23 @@
1
- require 'chef/provisioning/aws_driver/aws_resource_with_entry'
1
+ require 'chef/provisioning/aws_driver/aws_resource'
2
2
  require 'chef/resource/aws_vpc'
3
+ require 'chef/provisioning/aws_driver/exceptions'
3
4
 
4
- class Chef::Resource::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSResourceWithEntry
5
- aws_sdk_type AWS::EC2::SecurityGroup
5
+ class Chef::Resource::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSResource
6
+ aws_sdk_type AWS::EC2::SecurityGroup,
7
+ id: :id,
8
+ option_names: [:security_group, :security_group_id, :security_group_name]
6
9
 
7
10
  attribute :name, kind_of: String, name_attribute: true
8
11
  attribute :vpc, kind_of: [ String, AwsVpc, AWS::EC2::VPC ]
9
12
  attribute :description, kind_of: String
10
13
 
14
+ # This should be a hash of tags to apply to the AWS object
15
+ # TODO this is duplicated from AWSResourceWithEntry
16
+ #
17
+ # @param aws_tags [Hash] Should be a hash of keys & values to add. Keys and values
18
+ # can be provided as symbols or strings, but will be stored in AWS as strings.
19
+ attribute :aws_tags, kind_of: Hash
20
+
11
21
  #
12
22
  # Accepts rules in the format:
13
23
  # [
@@ -49,8 +59,22 @@ class Chef::Resource::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSResou
49
59
  }
50
60
 
51
61
  def aws_object
52
- driver, id = get_driver_and_id
53
- result = driver.ec2.security_groups[id] if id
62
+ if security_group_id
63
+ result = driver.ec2.security_groups[security_group_id]
64
+ else
65
+ # Names are unique within a VPC. Try to search by name and narroy by VPC, if
66
+ # provided
67
+ if vpc
68
+ vpc_object = Chef::Resource::AwsVpc.get_aws_object(vpc, resource: self)
69
+ results = vpc_object.security_groups.filter('group-name', name).to_a
70
+ else
71
+ results = driver.ec2.security_groups.filter('group-name', name).to_a
72
+ end
73
+ if results.size >= 2
74
+ raise ::Chef::Provisioning::AWSDriver::Exceptions::MultipleSecurityGroupError.new(name, results)
75
+ end
76
+ result = results.first
77
+ end
54
78
  result && result.exists? ? result : nil
55
79
  end
56
80
  end
@@ -13,6 +13,7 @@ module AWSSupport
13
13
  require 'aws_support/matchers/create_an_aws_object'
14
14
  require 'aws_support/matchers/update_an_aws_object'
15
15
  require 'aws_support/matchers/destroy_an_aws_object'
16
+ require 'aws_support/matchers/have_aws_object_tags'
16
17
  require 'aws_support/delayed_stream'
17
18
  require 'chef/provisioning/aws_driver/resources'
18
19
  require 'aws_support/aws_resource_run_wrapper'
@@ -21,7 +22,7 @@ module AWSSupport
21
22
  require 'aws'
22
23
  require 'aws_support/deep_matcher/matchable_object'
23
24
  require 'aws_support/deep_matcher/matchable_array'
24
- DeepMatcher::MatchableObject.matchable_classes << proc { |o| o.class.name =~ /^AWS::EC2($|::)/ }
25
+ DeepMatcher::MatchableObject.matchable_classes << proc { |o| o.class.name =~ /^AWS::(EC2|ELB)($|::)/ }
25
26
  DeepMatcher::MatchableArray.matchable_classes << AWS::Core::Data::List
26
27
 
27
28
  def purge_all
@@ -189,6 +190,9 @@ module AWSSupport
189
190
  define_method("create_an_#{resource_name}") do |name, expected_values={}|
190
191
  AWSSupport::Matchers::CreateAnAWSObject.new(self, resource_class, name, expected_values)
191
192
  end
193
+ define_method("have_#{resource_name}_tags") do |name, expected_tags={}|
194
+ AWSSupport::Matchers::HaveAWSObjectTags.new(self, resource_class, name, expected_tags)
195
+ end
192
196
  define_method("destroy_an_#{resource_name}") do |name, expected_values={}|
193
197
  AWSSupport::Matchers::DestroyAnAWSObject.new(self, resource_class, name)
194
198
  end
@@ -0,0 +1,63 @@
1
+ require 'rspec/matchers'
2
+ require 'chef/provisioning'
3
+ require 'aws_support/deep_matcher'
4
+
5
+ module AWSSupport
6
+ module Matchers
7
+ class HaveAWSObjectTags
8
+ include RSpec::Matchers::Composable
9
+ include AWSSupport::DeepMatcher
10
+
11
+ def initialize(example, resource_class, name, expected_tags)
12
+ @example = example
13
+ @resource_class = resource_class
14
+ @name = name
15
+ @expected_tags = expected_tags
16
+ end
17
+
18
+ attr_reader :example
19
+ attr_reader :resource_class
20
+ attr_reader :name
21
+ attr_reader :expected_tags
22
+ def resource_name
23
+ @resource_class.resource_name
24
+ end
25
+
26
+ def match_failure_messages(recipe)
27
+ differences = []
28
+
29
+ # Check for object existence and properties
30
+ resource = resource_class.new(name, nil)
31
+ resource.driver example.driver
32
+ resource.managed_entry_store Chef::Provisioning.chef_managed_entry_store
33
+ @aws_object = resource.aws_object
34
+
35
+ # Check existence
36
+ if @aws_object.nil?
37
+ differences << "#{resource_name}[#{name}] did not exist!"
38
+ else
39
+ differences += match_hashes_failure_messages(expected_tags, aws_object_tags, "#{resource_name}[#{name}]")
40
+ end
41
+
42
+ differences
43
+ end
44
+
45
+ private
46
+
47
+ def aws_object_tags
48
+ if @aws_object.is_a? AWS::ELB::LoadBalancer
49
+ resp = @example.driver.elb.client.describe_tags load_balancer_names: [@aws_object.name]
50
+ tags = {}
51
+ resp.data[:tag_descriptions] && resp.data[:tag_descriptions].each do |td|
52
+ td[:tags].each do |t|
53
+ tags[t[:key]] = t[:value]
54
+ end
55
+ end
56
+ tags
57
+ else
58
+ @aws_object.tags.to_h
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -8,10 +8,7 @@ describe Chef::Resource::AwsEbsVolume do
8
8
 
9
9
  it "aws_ebs_volume 'test_volume' creates an ebs volume" do
10
10
  expect_recipe {
11
- aws_ebs_volume "test_volume" do
12
- availability_zone 'a'
13
- size 8
14
- end
11
+ aws_ebs_volume "test_volume"
15
12
  }.to create_an_aws_ebs_volume('test_volume',
16
13
  :size => 8
17
14
  ).and be_idempotent
@@ -25,12 +22,16 @@ describe Chef::Resource::AwsEbsVolume do
25
22
  end
26
23
  }
27
24
  it "deletes the ebs volume" do
28
- expect_recipe {
25
+ # TODO all the `with_*` and `expect_*` methods from Cheffish
26
+ # automatically converge the block - we don't want to do that,
27
+ # we want to let the `destroy_an*` matcher do that
28
+ r = recipe {
29
29
  aws_ebs_volume "test_volume" do
30
30
  action :destroy
31
31
  end
32
- }.to destroy_an_aws_ebs_volume('test_volume')
33
- .and be_idempotent
32
+ }
33
+ expect(r).to destroy_an_aws_ebs_volume('test_volume'
34
+ ).and be_idempotent
34
35
  end
35
36
  end
36
37
 
@@ -38,7 +39,6 @@ describe Chef::Resource::AwsEbsVolume do
38
39
  expect_recipe {
39
40
  aws_ebs_volume "test_volume_az" do
40
41
  availability_zone "#{driver.aws_config.region}a"
41
- size 8
42
42
  end
43
43
  }.to create_an_aws_ebs_volume('test_volume_az')
44
44
  .and be_idempotent
@@ -35,6 +35,39 @@ describe Chef::Resource::AwsRouteTable do
35
35
  ]
36
36
  ).and be_idempotent
37
37
  end
38
+
39
+ it "ignores routes whose target matches ignore_route_targets" do
40
+ eni = nil
41
+ expect_recipe {
42
+ aws_subnet 'test_subnet' do
43
+ vpc 'test_vpc'
44
+ end
45
+
46
+ eni = aws_network_interface 'test_network_interface' do
47
+ subnet 'test_subnet'
48
+ end
49
+
50
+ aws_route_table 'test_route_table' do
51
+ vpc 'test_vpc'
52
+ routes(
53
+ '0.0.0.0/0' => :internet_gateway,
54
+ '172.31.0.0/16' => eni
55
+ )
56
+ end
57
+
58
+ aws_route_table 'test_route_table' do
59
+ vpc 'test_vpc'
60
+ routes '0.0.0.0/0' => :internet_gateway
61
+ ignore_route_targets ['^eni-']
62
+ end
63
+ }.to create_an_aws_route_table('test_route_table',
64
+ routes: [
65
+ { destination_cidr_block: '10.0.0.0/24', 'target.id' => 'local', state: :active },
66
+ { destination_cidr_block: '172.31.0.0/16', 'target.id' => eni.aws_object.id, state: :blackhole },
67
+ { destination_cidr_block: '0.0.0.0/0', 'target.id' => test_vpc.aws_object.internet_gateway.id, state: :active },
68
+ ]
69
+ ).and be_idempotent
70
+ end
38
71
  end
39
72
  end
40
73
  end
@@ -1,4 +1,6 @@
1
1
  require 'spec_helper'
2
+ require 'chef/resource/aws_security_group'
3
+ require 'chef/provisioning/aws_driver/exceptions'
2
4
 
3
5
  describe Chef::Resource::AwsSecurityGroup do
4
6
  extend AWSSupport
@@ -18,6 +20,42 @@ describe Chef::Resource::AwsSecurityGroup do
18
20
  ).and be_idempotent
19
21
  end
20
22
 
23
+ it "can reference a security group by name or id" do
24
+ expect_recipe {
25
+ sg = aws_security_group 'test_sg'
26
+ sg.run_action(:create)
27
+ id = sg.aws_object.id
28
+ aws_security_group id do
29
+ inbound_rules '0.0.0.0/0' => 22
30
+ end
31
+ aws_security_group 'test_sg' do
32
+ security_group_id id
33
+ outbound_rules 22 => '0.0.0.0/0'
34
+ end
35
+ }.to create_an_aws_security_group('test_sg',
36
+ description: 'test_sg',
37
+ vpc_id: default_vpc.id,
38
+ ip_permissions_list: [
39
+ { groups: [], ip_ranges: [{cidr_ip: "0.0.0.0/0"}], ip_protocol: "tcp", from_port: 22, to_port: 22},
40
+ ],
41
+ ip_permissions_list_egress: [
42
+ {groups: [], ip_ranges: [{cidr_ip: "0.0.0.0/0"}], ip_protocol: "tcp", from_port: 22, to_port: 22 }
43
+ ]
44
+
45
+ ).and be_idempotent
46
+ end
47
+
48
+ it "raises an error trying to reference a security group by an unknown id" do
49
+ expect_converge {
50
+ aws_security_group 'sg-12345678'
51
+ }.to raise_error(RuntimeError, /Chef::Resource::AwsSecurityGroup\[sg-12345678\] does not exist!/)
52
+ expect_converge {
53
+ aws_security_group 'test_sg' do
54
+ security_group_id 'sg-12345678'
55
+ end
56
+ }.to raise_error(RuntimeError, /Chef::Resource::AwsSecurityGroup\[sg-12345678\] does not exist!/)
57
+ end
58
+
21
59
  end
22
60
 
23
61
  with_aws "in a VPC" do
@@ -53,5 +91,53 @@ describe Chef::Resource::AwsSecurityGroup do
53
91
  ).and be_idempotent
54
92
  end
55
93
  end
94
+
95
+ with_aws "when narrowing from multiple VPCs" do
96
+ aws_vpc 'test_vpc1' do
97
+ cidr_block '10.0.0.0/24'
98
+ end
99
+ aws_vpc 'test_vpc2' do
100
+ cidr_block '10.0.0.0/24'
101
+ end
102
+ aws_security_group 'test_sg' do
103
+ vpc 'test_vpc1'
104
+ end
105
+ aws_security_group 'test_sg' do
106
+ vpc 'test_vpc2'
107
+ end
108
+
109
+ # We need to manually delete these because the auto-delete
110
+ # won't specify VPC
111
+ after(:context) do
112
+ converge {
113
+ aws_security_group 'test_sg' do
114
+ vpc 'test_vpc1'
115
+ action :destroy
116
+ end
117
+ aws_security_group 'test_sg' do
118
+ vpc 'test_vpc2'
119
+ action :destroy
120
+ end
121
+ }
122
+ end
123
+
124
+ it "raises an error if it finds multiple security groups" do
125
+ expect_converge {
126
+ r = aws_security_group 'test_sg'
127
+ r.aws_object
128
+ }.to raise_error(::Chef::Provisioning::AWSDriver::Exceptions::MultipleSecurityGroupError)
129
+ end
130
+
131
+ it "correctly returns the security group when vpc is specified" do
132
+ aws_obj = nil
133
+ expect_converge {
134
+ r = aws_security_group 'test_sg' do
135
+ vpc 'test_vpc1'
136
+ end
137
+ aws_obj = r.aws_object
138
+ }.to_not raise_error
139
+ expect(aws_obj.vpc.tags['Name']).to eq('test_vpc1')
140
+ end
141
+ end
56
142
  end
57
143
  end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ describe "AWS Tagged Items" do
4
+ extend AWSSupport
5
+
6
+ when_the_chef_12_server "exists", organization: 'foo', server_scope: :context do
7
+ with_aws "when connected to AWS" do
8
+ it "aws_ebs_volume 'test_volume' created with default Name tag" do
9
+ expect_recipe {
10
+ aws_ebs_volume "test_volume"
11
+ }.to create_an_aws_ebs_volume('test_volume'
12
+ ).and have_aws_ebs_volume_tags('test_volume',
13
+ { 'Name' => 'test_volume' }
14
+ ).and be_idempotent
15
+ end
16
+
17
+ it "aws_ebs_volume 'test_volume' tags are updated" do
18
+ expect_recipe {
19
+ aws_ebs_volume "test_volume_a" do
20
+ aws_tags :byebye => 'true'
21
+ end
22
+ }.to create_an_aws_ebs_volume('test_volume_a'
23
+ ).and have_aws_ebs_volume_tags('test_volume_a',
24
+ { 'Name' => 'test_volume_a',
25
+ 'byebye' => 'true'
26
+ }
27
+ ).and be_idempotent
28
+
29
+ expect_recipe {
30
+ aws_ebs_volume "test_volume_a" do
31
+ aws_tags 'Name' => 'test_volume_b', :project => 'X'
32
+ end
33
+ }.to update_an_aws_ebs_volume('test_volume_a'
34
+ ).and have_aws_ebs_volume_tags('test_volume_a',
35
+ { 'Name' => 'test_volume_b',
36
+ 'project' => 'X'
37
+ }
38
+ ).and be_idempotent
39
+ end
40
+
41
+ it "aws_ebs_volume 'test_volume' tags are not changed when not updated" do
42
+ expect_recipe {
43
+ aws_ebs_volume "test_volume_c" do
44
+ aws_tags :byebye => 'true'
45
+ end
46
+ }.to create_an_aws_ebs_volume('test_volume_c'
47
+ ).and have_aws_ebs_volume_tags('test_volume_c',
48
+ { 'Name' => 'test_volume_c',
49
+ 'byebye' => 'true'
50
+ }
51
+ ).and be_idempotent
52
+
53
+ expect_recipe {
54
+ aws_ebs_volume "test_volume_c"
55
+ }.to have_aws_ebs_volume_tags('test_volume_c',
56
+ { 'Name' => 'test_volume_c',
57
+ 'byebye' => 'true'
58
+ }
59
+ ).and be_idempotent
60
+ end
61
+
62
+
63
+ it "aws_ebs_volume 'test_volume' created with new Name tag" do
64
+ expect_recipe {
65
+ aws_ebs_volume "test_volume_2" do
66
+ aws_tags :Name => 'test_volume_new'
67
+ end
68
+ }.to create_an_aws_ebs_volume('test_volume_2'
69
+ ).and have_aws_ebs_volume_tags('test_volume_2',
70
+ { 'Name' => 'test_volume_new' }
71
+ ).and be_idempotent
72
+ end
73
+
74
+ it "aws_ebs_volume 'test_volume' created with custom tag" do
75
+ expect_recipe {
76
+ aws_ebs_volume "test_volume_3" do
77
+ aws_tags :project => 'aws-provisioning'
78
+ end
79
+ }.to create_an_aws_ebs_volume('test_volume_3'
80
+ ).and have_aws_ebs_volume_tags('test_volume_3',
81
+ { 'Name' => 'test_volume_3',
82
+ 'project' => 'aws-provisioning'
83
+ }
84
+ ).and be_idempotent
85
+ end
86
+
87
+ it "aws_instance 'test_instance' created with custom tag", :super_slow do
88
+ expect_recipe {
89
+ machine 'test_instance' do
90
+ action :allocate
91
+ end
92
+ }.to create_an_aws_instance('test_instance')
93
+
94
+ expect_recipe {
95
+ aws_instance "test_instance" do
96
+ aws_tags :project => 'FUN'
97
+ end
98
+ }.to update_an_aws_instance('test_instance'
99
+ ).and have_aws_instance_tags('test_instance',
100
+ { 'Name' => 'test_instance',
101
+ 'project' => 'FUN'
102
+ }
103
+ ).and be_idempotent
104
+ end
105
+
106
+ it "machine 'test_machine' created using machine_options aws_tag", :super_slow do
107
+ expect_recipe {
108
+ machine 'test_machine' do
109
+ machine_options :aws_tags => { :mach_opt_sym => 'value', 'mach_opt_str' => 'value' }
110
+ action :allocate
111
+ end
112
+ }.to create_an_aws_instance('test_machine'
113
+ ).and have_aws_instance_tags('test_machine',
114
+ { 'Name' => 'test_machine',
115
+ 'mach_opt_sym' => 'value',
116
+ 'mach_opt_str' => 'value'
117
+ }
118
+ ).and be_idempotent
119
+ end
120
+
121
+ it "machine 'test_machine_2' created using default with_machine_options aws_tag", :super_slow do
122
+ expect_recipe {
123
+ with_machine_options :aws_tags => { :default => 'value1' }
124
+
125
+ machine 'test_machine_2' do
126
+ action :allocate
127
+ end
128
+ }.to create_an_aws_instance('test_machine_2'
129
+ ).and have_aws_instance_tags('test_machine_2',
130
+ { 'Name' => 'test_machine_2',
131
+ 'default' => 'value1'
132
+ }
133
+ ).and be_idempotent
134
+ end
135
+
136
+ it "load balancer 'lbtest' tagged with load_balancer_options" do
137
+ expect_recipe {
138
+ load_balancer 'lbtest' do
139
+ load_balancer_options :aws_tags => { :marco => 'polo', 'happyhappy' => 'joyjoy' },
140
+ :availability_zones => ["#{driver.aws_config.region}a", "#{driver.aws_config.region}b"] # TODO should enchance to accept letter AZs
141
+ end
142
+ }.to create_an_aws_load_balancer('lbtest'
143
+ ).and have_aws_load_balancer_tags('lbtest',
144
+ { 'marco' => 'polo',
145
+ 'happyhappy' => 'joyjoy'
146
+ }
147
+ ).and be_idempotent
148
+ expect_recipe {
149
+ load_balancer 'lbtest' do
150
+ load_balancer_options :aws_tags => { :default => 'value1' }
151
+ end
152
+ }.to update_an_aws_load_balancer('lbtest'
153
+ ).and have_aws_load_balancer_tags('lbtest',
154
+ { 'default' => 'value1' }
155
+ ).and be_idempotent
156
+ end
157
+
158
+ end
159
+ end
160
+ end