chef-provisioning-aws 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -0
  3. data/README.md +26 -39
  4. data/Rakefile +13 -5
  5. data/lib/chef/provider/aws_iam_instance_profile.rb +60 -0
  6. data/lib/chef/provider/aws_iam_role.rb +98 -0
  7. data/lib/chef/provider/aws_image.rb +1 -1
  8. data/lib/chef/provider/aws_internet_gateway.rb +75 -0
  9. data/lib/chef/provider/aws_route_table.rb +3 -2
  10. data/lib/chef/provider/aws_s3_bucket.rb +4 -1
  11. data/lib/chef/provider/aws_security_group.rb +1 -1
  12. data/lib/chef/provider/aws_vpc.rb +50 -45
  13. data/lib/chef/provisioning/aws_driver.rb +22 -1
  14. data/lib/chef/provisioning/aws_driver/aws_provider.rb +13 -5
  15. data/lib/chef/provisioning/aws_driver/aws_resource.rb +173 -165
  16. data/lib/chef/provisioning/aws_driver/credentials.rb +12 -0
  17. data/lib/chef/provisioning/aws_driver/driver.rb +82 -37
  18. data/lib/chef/provisioning/aws_driver/super_lwrp.rb +56 -43
  19. data/lib/chef/provisioning/aws_driver/version.rb +1 -1
  20. data/lib/chef/resource/aws_dhcp_options.rb +1 -1
  21. data/lib/chef/resource/aws_ebs_volume.rb +1 -1
  22. data/lib/chef/resource/aws_eip_address.rb +1 -1
  23. data/lib/chef/resource/aws_iam_instance_profile.rb +33 -0
  24. data/lib/chef/resource/aws_iam_role.rb +55 -0
  25. data/lib/chef/resource/aws_image.rb +1 -1
  26. data/lib/chef/resource/aws_instance.rb +1 -1
  27. data/lib/chef/resource/aws_internet_gateway.rb +36 -6
  28. data/lib/chef/resource/aws_load_balancer.rb +1 -1
  29. data/lib/chef/resource/aws_network_acl.rb +1 -1
  30. data/lib/chef/resource/aws_network_interface.rb +1 -1
  31. data/lib/chef/resource/aws_route53_hosted_zone.rb +261 -0
  32. data/lib/chef/resource/aws_route53_record_set.rb +162 -0
  33. data/lib/chef/resource/aws_route_table.rb +1 -1
  34. data/lib/chef/resource/aws_security_group.rb +1 -1
  35. data/lib/chef/resource/aws_sns_topic.rb +1 -1
  36. data/lib/chef/resource/aws_subnet.rb +1 -1
  37. data/lib/chef/resource/aws_vpc.rb +1 -1
  38. data/lib/chef/resource/aws_vpc_peering_connection.rb +1 -1
  39. data/spec/aws_support.rb +11 -13
  40. data/spec/aws_support/matchers/create_an_aws_object.rb +7 -1
  41. data/spec/aws_support/matchers/have_aws_object_tags.rb +1 -1
  42. data/spec/aws_support/matchers/match_an_aws_object.rb +7 -1
  43. data/spec/aws_support/matchers/update_an_aws_object.rb +8 -2
  44. data/spec/integration/aws_eip_address_spec.rb +74 -0
  45. data/spec/integration/aws_iam_instance_profile_spec.rb +159 -0
  46. data/spec/integration/aws_iam_role_spec.rb +177 -0
  47. data/spec/integration/aws_internet_gateway_spec.rb +161 -0
  48. data/spec/integration/aws_network_interface_spec.rb +3 -4
  49. data/spec/integration/aws_route53_hosted_zone_spec.rb +522 -0
  50. data/spec/integration/aws_route_table_spec.rb +52 -4
  51. data/spec/integration/aws_s3_bucket_spec.rb +1 -1
  52. data/spec/integration/load_balancer_spec.rb +303 -8
  53. data/spec/integration/machine_batch_spec.rb +1 -0
  54. data/spec/integration/machine_image_spec.rb +32 -17
  55. data/spec/integration/machine_spec.rb +11 -29
  56. data/spec/unit/chef/provisioning/aws_driver/driver_spec.rb +0 -1
  57. data/spec/unit/chef/provisioning/aws_driver/route53_spec.rb +105 -0
  58. metadata +48 -6
@@ -0,0 +1,177 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ def ec2_principal
5
+ <<-EOF
6
+ {
7
+ "Version": "2012-10-17",
8
+ "Statement": [
9
+ {
10
+ "Action": "sts:AssumeRole",
11
+ "Principal": {
12
+ "Service": "ec2.amazonaws.com"
13
+ },
14
+ "Effect": "Allow",
15
+ "Sid": ""
16
+ }
17
+ ]
18
+ }
19
+ EOF
20
+ end
21
+
22
+ def rds_principal
23
+ <<-EOF
24
+ {
25
+ "Version": "2012-10-17",
26
+ "Statement": [
27
+ {
28
+ "Action": "sts:AssumeRole",
29
+ "Principal": {
30
+ "Service": "rds.amazonaws.com"
31
+ },
32
+ "Effect": "Allow",
33
+ "Sid": ""
34
+ }
35
+ ]
36
+ }
37
+ EOF
38
+ end
39
+
40
+ def rds_role_policy
41
+ <<-EOF
42
+ {
43
+ "Version": "2012-10-17",
44
+ "Statement": [
45
+ {
46
+ "Sid": "Stmt1441787971000",
47
+ "Effect": "Allow",
48
+ "Action": [
49
+ "rds:*"
50
+ ],
51
+ "Resource": [
52
+ "*"
53
+ ]
54
+ }
55
+ ]
56
+ }
57
+ EOF
58
+ end
59
+
60
+ def iam_role_policy
61
+ <<-EOF
62
+ {
63
+ "Version": "2012-10-17",
64
+ "Statement": [
65
+ {
66
+ "Effect": "Allow",
67
+ "Action": "iam:*",
68
+ "Resource": "*"
69
+ }
70
+ ]
71
+ }
72
+ EOF
73
+ end
74
+
75
+ describe Chef::Resource::AwsIamRole do
76
+ extend AWSSupport
77
+
78
+ when_the_chef_12_server "exists", organization: "foo", server_scope: :context do
79
+ with_aws "when connected to AWS" do
80
+
81
+ let(:role_name) {
82
+ name_postfix = SecureRandom.hex(8)
83
+ "cp_test_iam_role_#{name_postfix}"
84
+ }
85
+
86
+ it "creates an aws_iam_role with minimum attributes" do
87
+ expect_recipe {
88
+ aws_iam_role role_name do
89
+ assume_role_policy_document ec2_principal
90
+ end
91
+ }.to create_an_aws_iam_role(role_name) { |aws_object|
92
+ expect(Chef::JSONCompat.parse(URI.decode(aws_object.assume_role_policy_document))).to eq(Chef::JSONCompat.parse(ec2_principal))
93
+ }.and be_idempotent
94
+ end
95
+
96
+ it "creates an aws_iam_role with maximum attributes" do
97
+ expect_recipe {
98
+ aws_iam_role role_name do
99
+ path "/"
100
+ assume_role_policy_document ec2_principal
101
+ inline_policies a: iam_role_policy
102
+ end
103
+ }.to create_an_aws_iam_role(role_name,
104
+ path: "/",
105
+ policies: [{name: "a"}]
106
+ ) { |aws_object|
107
+ expect(Chef::JSONCompat.parse(URI.decode(aws_object.assume_role_policy_document))).to eq(Chef::JSONCompat.parse(ec2_principal))
108
+ expect(Chef::JSONCompat.parse(URI.decode(aws_object.policies.first.policy_document))).to eq(Chef::JSONCompat.parse(iam_role_policy))
109
+ }.and be_idempotent
110
+ end
111
+
112
+ context "with an existing aws_iam_role" do
113
+ # Doing this in a before(:each) block for 2 reasons:
114
+ # 1) the context-level methods only destroy the item after the context is finished,
115
+ # and I want the tests to assert on a new item each example
116
+ # 2) the let(:role_name) cannot be used at the context level, only at
117
+ # the example/before/after level
118
+ before(:each) do
119
+ converge {
120
+ aws_iam_role role_name do
121
+ path "/"
122
+ assume_role_policy_document ec2_principal
123
+ inline_policies a: iam_role_policy
124
+ end
125
+ }
126
+ end
127
+
128
+ after(:each) do
129
+ converge {
130
+ aws_iam_role role_name do
131
+ action :destroy
132
+ end
133
+ }
134
+ end
135
+
136
+
137
+ it "updates all available fields" do
138
+ expect_recipe {
139
+ aws_iam_role role_name do
140
+ assume_role_policy_document rds_principal
141
+ inline_policies b: rds_role_policy
142
+ end
143
+ }.to create_an_aws_iam_role(role_name,
144
+ path: "/",
145
+ policies: [{name: "b"}]
146
+ ) { |aws_object|
147
+ expect(Chef::JSONCompat.parse(URI.decode(aws_object.assume_role_policy_document))).to eq(Chef::JSONCompat.parse(rds_principal))
148
+ expect(Chef::JSONCompat.parse(URI.decode(aws_object.policies.first.policy_document))).to eq(Chef::JSONCompat.parse(rds_role_policy))
149
+ }.and be_idempotent
150
+ end
151
+
152
+ it "clears inline_policies with an empty hash" do
153
+ expect_recipe {
154
+ aws_iam_role role_name do
155
+ inline_policies Hash.new
156
+ end
157
+ }.to create_an_aws_iam_role(role_name,
158
+ path: "/",
159
+ policies: []
160
+ ).and be_idempotent
161
+ end
162
+
163
+ it "deletes the aws_iam_role" do
164
+ r = recipe {
165
+ aws_iam_role role_name do
166
+ action :destroy
167
+ end
168
+ }
169
+ expect(r).to destroy_an_aws_iam_role(role_name)
170
+ expect { driver.iam_client.list_role_policies(role_name: role_name).policy_names }.to raise_error(::Aws::IAM::Errors::NoSuchEntity)
171
+ end
172
+ end
173
+
174
+ end
175
+
176
+ end
177
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chef::Resource::AwsInternetGateway do
4
+ extend AWSSupport
5
+
6
+ when_the_chef_12_server 'exists', organization: 'foo', server_scope: :context do
7
+ with_aws 'with a VPC' do
8
+
9
+ aws_vpc 'test_vpc_igw_a' do
10
+ cidr_block '10.0.0.0/24'
11
+ end
12
+
13
+ aws_vpc 'test_vpc_igw_b' do
14
+ cidr_block '10.0.1.0/24'
15
+ end
16
+
17
+ it "creates an aws_internet_gateway with no parameters" do
18
+ expect_recipe {
19
+ aws_internet_gateway 'test_internet_gateway'
20
+ }.to create_an_aws_internet_gateway('test_internet_gateway').and be_idempotent
21
+ end
22
+
23
+ it "creates an aws_internet_gateway and attaches it to the specified VPC" do
24
+ expect_recipe {
25
+ aws_internet_gateway 'test_internet_gateway' do
26
+ vpc test_vpc_igw_a.aws_object.id
27
+ end
28
+ }.to create_an_aws_internet_gateway('test_internet_gateway',
29
+ vpc: test_vpc_igw_a.aws_object
30
+ ).and be_idempotent
31
+ end
32
+
33
+ context 'with the IGW attached to an existing VPC' do
34
+ aws_internet_gateway 'test_internet_gateway' do
35
+ vpc test_vpc_igw_a.aws_object.id
36
+ end
37
+
38
+ it "updates it to the new VPC" do
39
+ expect_recipe {
40
+ aws_internet_gateway 'test_internet_gateway' do
41
+ vpc test_vpc_igw_b
42
+ end
43
+ }.to update_an_aws_internet_gateway('test_internet_gateway',
44
+ vpc: test_vpc_igw_b.aws_object
45
+ ).and be_idempotent
46
+ end
47
+ end
48
+
49
+ context 'with the IGW attached to an existing VPC' do
50
+ aws_internet_gateway 'test_internet_gateway' do
51
+ vpc test_vpc_igw_a.aws_object.id
52
+ end
53
+
54
+ it "detaches it from the VPC" do
55
+ expect_recipe {
56
+ aws_internet_gateway 'test_internet_gateway' do
57
+ action :detach
58
+ end
59
+ }.to update_an_aws_internet_gateway('test_internet_gateway',
60
+ vpc: nil
61
+ ).and be_idempotent
62
+ end
63
+ end
64
+
65
+ context 'with the IGW attached to an existing VPC' do
66
+ aws_internet_gateway 'test_internet_gateway' do
67
+ vpc test_vpc_igw_a.aws_object.id
68
+ end
69
+
70
+ it "detaches the VPC and destroys the IGW" do
71
+ r = recipe {
72
+ aws_internet_gateway 'test_internet_gateway' do
73
+ action :destroy
74
+ end
75
+ }
76
+ expect(r).to destroy_an_aws_internet_gateway('test_internet_gateway').and be_idempotent
77
+
78
+ expect(test_vpc_igw_a.aws_object.internet_gateway).to eq(nil)
79
+ end
80
+
81
+ context 'with a VPC with its own managed internet gateway' do
82
+ aws_vpc 'test_vpc_preexisting_igw' do
83
+ cidr_block '10.0.1.0/24'
84
+ internet_gateway true
85
+ end
86
+
87
+ it "deletes the old managed IGW and attaches the new one" do
88
+ existing_igw = test_vpc_preexisting_igw.aws_object.internet_gateway
89
+
90
+ expect_recipe {
91
+ aws_internet_gateway 'test_internet_gateway' do
92
+ vpc test_vpc_preexisting_igw.aws_object
93
+ end
94
+ }.to create_an_aws_internet_gateway('test_internet_gateway',
95
+ vpc: test_vpc_preexisting_igw.aws_object
96
+ ).and be_idempotent
97
+
98
+ expect(existing_igw.exists?).to eq(false)
99
+ end
100
+ end
101
+
102
+ context 'with a VPC and an attached aws_internet_gateway resource' do
103
+ aws_internet_gateway 'test_internet_gateway'
104
+ aws_vpc 'test_vpc_preexisting_igw' do
105
+ cidr_block '10.0.1.0/24'
106
+ internet_gateway test_internet_gateway
107
+ end
108
+
109
+ it "leaves the attachment alone if internet_gateway is set to true" do
110
+ expect(test_vpc_preexisting_igw.aws_object.internet_gateway).to eq(test_internet_gateway.aws_object)
111
+ expect_recipe {
112
+ aws_vpc 'test_vpc_preexisting_igw' do
113
+ cidr_block '10.0.1.0/24'
114
+ internet_gateway true
115
+ end
116
+ }.to match_an_aws_vpc('test_vpc_preexisting_igw',
117
+ internet_gateway: test_internet_gateway.aws_object
118
+ ).and be_idempotent
119
+ end
120
+
121
+ it "deletes the attachment if internet_gateway is set to false" do
122
+ expect_recipe {
123
+ aws_vpc 'test_vpc_preexisting_igw' do
124
+ cidr_block '10.0.1.0/24'
125
+ internet_gateway false
126
+ end
127
+ }.to match_an_aws_vpc('test_vpc_preexisting_igw',
128
+ internet_gateway: nil
129
+ ).and match_an_aws_internet_gateway('test_internet_gateway',
130
+ vpc: nil
131
+ ).and be_idempotent
132
+ end
133
+ end
134
+
135
+ context 'with a VPC and an attached aws_internet_gateway resource' do
136
+ aws_internet_gateway 'test_internet_gateway1'
137
+ aws_internet_gateway 'test_internet_gateway2'
138
+ aws_vpc 'test_vpc_preexisting_igw' do
139
+ cidr_block '10.0.1.0/24'
140
+ internet_gateway test_internet_gateway1.aws_object
141
+ end
142
+
143
+ it "switches the attachment to a newly specified aws_internet_gateway" do
144
+ expect(test_vpc_preexisting_igw.aws_object.internet_gateway).to eq(test_internet_gateway1.aws_object)
145
+ expect_recipe {
146
+ aws_internet_gateway 'test_internet_gateway2' do
147
+ vpc 'test_vpc_preexisting_igw'
148
+ end
149
+ }.to match_an_aws_internet_gateway('test_internet_gateway1',
150
+ vpc: nil
151
+ ).and match_an_aws_internet_gateway('test_internet_gateway2',
152
+ vpc: test_vpc_preexisting_igw.aws_object
153
+ ).and be_idempotent
154
+ end
155
+
156
+ end
157
+ end
158
+ end
159
+
160
+ end
161
+ end
@@ -4,19 +4,18 @@ describe "AwsNetworkInterface" do
4
4
  when_the_chef_12_server "exists", organization: 'foo', server_scope: :context do
5
5
  with_aws "when connected to AWS" do
6
6
 
7
- context "setting up public VPC", :super_slow do
7
+ context "setting up public VPC" do
8
8
 
9
- # Putting this in its own context so it doesn't slow down other tests
10
9
  setup_public_vpc
11
10
 
12
- it "creates an aws_network_interface resource with maximum attributes" do
11
+ it "creates an aws_network_interface resource with maximum attributes", :super_slow do
13
12
  expect_recipe {
14
13
  machine "test_machine" do
15
14
  machine_options bootstrap_options: {
16
15
  subnet_id: 'test_public_subnet',
17
16
  security_group_ids: ['test_security_group']
18
17
  }
19
- action :allocate
18
+ action :ready
20
19
  end
21
20
 
22
21
  aws_network_interface 'test_network_interface' do
@@ -0,0 +1,522 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chef::Resource::AwsRoute53HostedZone 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
+
9
+ context "aws_route53_hosted_zone" do
10
+
11
+ # for the occasional spec where the test zone won't be automatically deleted, the spec can set
12
+ # @zone_to_delete to communicate the zone name to the 'after' block. (this can't be done just with
13
+ # let-vars because attribute values in dependent RecordSet resources have to be hard-coded.)
14
+ let(:zone_to_delete) { @zone_to_delete }
15
+
16
+ after(:example) {
17
+ if zone_to_delete
18
+ converge {
19
+ aws_route53_hosted_zone zone_to_delete do
20
+ action :destroy
21
+ end
22
+ }
23
+ end
24
+ }
25
+
26
+ let(:zone_name) { "aws-spec-#{Time.now.to_i}.com" }
27
+
28
+ context ":create" do
29
+ it "creates a hosted zone without attributes" do
30
+ expect(recipe {
31
+ aws_route53_hosted_zone zone_name
32
+ }).to create_an_aws_route53_hosted_zone(zone_name).and be_idempotent
33
+ end
34
+
35
+ it "creates a hosted zone with attributes" do
36
+ test_comment = "Test comment for spec."
37
+
38
+ expect_recipe {
39
+ aws_route53_hosted_zone zone_name do
40
+ comment test_comment
41
+ end
42
+ }.to create_an_aws_route53_hosted_zone(zone_name,
43
+ config: { comment: test_comment }).and be_idempotent
44
+ end
45
+
46
+ # we don't want to go overboard testing all our validations, but this is the one that can cause the
47
+ # most difficult user confusion, and AWS won't catch it.
48
+ it "crashes if the zone name has a trailing dot" do
49
+ expect_converge {
50
+ aws_route53_hosted_zone "#{zone_name}."
51
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /domain name cannot end with a dot/)
52
+ end
53
+
54
+ it "updates the zone comment" do
55
+ expected_comment = "Updated comment."
56
+
57
+ expect_recipe {
58
+ aws_route53_hosted_zone zone_name do
59
+ comment "Initial comment."
60
+ end
61
+ aws_route53_hosted_zone zone_name do
62
+ comment expected_comment
63
+ end
64
+ }.to create_an_aws_route53_hosted_zone(zone_name,
65
+ config: { comment: expected_comment }).and be_idempotent
66
+ end
67
+ end
68
+
69
+ context "RecordSets" do
70
+ let(:sdk_cname_rr) {
71
+ {
72
+ name: "some-host.feegle.com.", # AWS adds the trailing dot.
73
+ type: "CNAME",
74
+ ttl: 3600,
75
+ resource_records: [{ value: "some-other-host" }],
76
+ }
77
+ }
78
+
79
+ it "crashes on duplicate RecordSets" do
80
+ expect_converge {
81
+ aws_route53_hosted_zone "chasm.com" do
82
+ record_sets {
83
+ aws_route53_record_set "wooster1" do
84
+ rr_name "wooster.chasm.com"
85
+ type "CNAME"
86
+ ttl 300
87
+ resource_records ["some-other-host"]
88
+ end
89
+ aws_route53_record_set "wooster2" do
90
+ rr_name "wooster.chasm.com"
91
+ type "A"
92
+ ttl 3600
93
+ resource_records ["141.222.1.1"]
94
+ end
95
+ }
96
+ end
97
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /Duplicate RecordSet found in resource/)
98
+ end
99
+
100
+ # normally wouldn't bother with this, but it's best to be safe with the inlined resources.
101
+ it "crashes on a RecordSet with an invalid action" do
102
+ expect_converge {
103
+ aws_route53_hosted_zone zone_name do
104
+ record_sets {
105
+ aws_route53_record_set "wooster1" do
106
+ action :invoke
107
+ rr_name "wooster.example.com"
108
+ type "CNAME"
109
+ ttl 300
110
+ end
111
+ }
112
+ end
113
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /Option action must be equal to one of/)
114
+ end
115
+
116
+ it "creates a hosted zone with a RecordSet" do
117
+ expect_recipe {
118
+ aws_route53_hosted_zone "feegle.com" do
119
+ record_sets {
120
+ aws_route53_record_set "some-hostname CNAME" do
121
+ rr_name "some-host.feegle.com"
122
+ type "CNAME"
123
+ ttl 3600
124
+ resource_records ["some-other-host"]
125
+ end
126
+ }
127
+ end
128
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
129
+ resource_record_sets: [{}, {}, sdk_cname_rr]).and be_idempotent
130
+ # the empty {} acts as a wildcard, and all zones have SOA and NS records we want to skip.
131
+ end
132
+
133
+ it "creates a hosted zone with a RecordSet with an RR name with a trailing dot" do
134
+ expect_recipe {
135
+ aws_route53_hosted_zone "feegle.com" do
136
+ record_sets {
137
+ aws_route53_record_set "some-host.feegle.com." do
138
+ type "CNAME"
139
+ ttl 3600
140
+ resource_records ["some-other-host"]
141
+ end
142
+ }
143
+ end
144
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
145
+ resource_record_sets: [{}, {}, sdk_cname_rr]).and be_idempotent
146
+ end
147
+
148
+ # AWS's error for this is "FATAL problem: DomainLabelEmpty encountered", so we help the user out.
149
+ it "crashes with a RecordSet with a mismatched zone name with a trailing dot" do
150
+ expect_converge {
151
+ aws_route53_hosted_zone "feegle.com" do
152
+ record_sets {
153
+ aws_route53_record_set "some-host.wrong-zone.com." do
154
+ type "CNAME"
155
+ ttl 3600
156
+ resource_records ["some-other-host"]
157
+ end
158
+ }
159
+ end
160
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /RecordSet name.*does not match parent/)
161
+ end
162
+
163
+ it "creates and updates a RecordSet" do
164
+ expected_rr = sdk_cname_rr.merge({ ttl: 1800 })
165
+
166
+ expect_recipe {
167
+ aws_route53_hosted_zone "feegle.com" do
168
+ record_sets {
169
+ aws_route53_record_set "some-hostname CNAME" do
170
+ rr_name "some-host"
171
+ type "CNAME"
172
+ ttl 3600
173
+ resource_records ["some-other-host"]
174
+ end
175
+ }
176
+ end
177
+
178
+ aws_route53_hosted_zone "feegle.com" do
179
+ record_sets {
180
+ aws_route53_record_set "some-hostname CNAME" do
181
+ rr_name "some-host"
182
+ type "CNAME"
183
+ ttl 1800
184
+ resource_records ["some-other-host"]
185
+ end
186
+ }
187
+ end
188
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
189
+ resource_record_sets: [{}, {}, expected_rr]).and be_idempotent
190
+ end
191
+
192
+ it "creates and deletes a RecordSet" do
193
+ expect_recipe {
194
+ aws_route53_hosted_zone "feegle.com" do
195
+ record_sets {
196
+ aws_route53_record_set "some-api-host" do
197
+ type "CNAME"
198
+ ttl 3600
199
+ resource_records ["some-other-host"]
200
+ end
201
+ }
202
+ end
203
+
204
+ aws_route53_hosted_zone "feegle.com" do
205
+ record_sets {
206
+ aws_route53_record_set "some-api-host" do
207
+ action :destroy
208
+ type "CNAME"
209
+ ttl 3600
210
+ resource_records ["some-other-host"]
211
+ end
212
+ }
213
+ end
214
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
215
+ resource_record_sets: [{}, {}]).and be_idempotent
216
+ end
217
+
218
+ it "automatically uses the parent zone name in the RecordSet name" do
219
+ expect_recipe {
220
+ aws_route53_hosted_zone "feegle.com" do
221
+ record_sets {
222
+ aws_route53_record_set "some-host" do
223
+ type "CNAME"
224
+ ttl 3600
225
+ resource_records ["some-other-host"]
226
+ end
227
+ }
228
+ end
229
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
230
+ resource_record_sets: [{}, {}, sdk_cname_rr]).and be_idempotent
231
+ end
232
+
233
+ it "raises the AWS exception when trying to delete a record using mismatched values" do
234
+ @zone_to_delete = zone_name = "raise-aws-exception.com"
235
+
236
+ expect_converge {
237
+ aws_route53_hosted_zone zone_name do
238
+ record_sets {
239
+ aws_route53_record_set "some-hostname CNAME" do
240
+ rr_name "some-api-host.raise-aws-exception.com"
241
+ type "CNAME"
242
+ ttl 3600
243
+ resource_records ["some-other-host"]
244
+ end
245
+ }
246
+ end
247
+ }.not_to raise_error
248
+
249
+ expect_converge {
250
+ aws_route53_hosted_zone zone_name do
251
+ record_sets {
252
+ aws_route53_record_set "some-hostname CNAME" do
253
+ action :destroy
254
+ rr_name "some-api-host.raise-aws-exception.com"
255
+ type "CNAME"
256
+ ttl 100
257
+ resource_records ["some-other-host"]
258
+ end
259
+ }
260
+ end
261
+ }.to raise_error(Aws::Route53::Errors::InvalidChangeBatch, /Tried to delete.*the values provided do not match the current values/)
262
+ end
263
+
264
+ it "uses the resource name as the :rr_name" do
265
+ expect_recipe {
266
+ aws_route53_hosted_zone "feegle.com" do
267
+ record_sets {
268
+ aws_route53_record_set "some-host" do
269
+ type "CNAME"
270
+ ttl 3600
271
+ resource_records ["some-other-host"]
272
+ end
273
+ }
274
+ end
275
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
276
+ resource_record_sets: [{}, {}, sdk_cname_rr]).and be_idempotent
277
+ end
278
+
279
+ context "inheriting default property values" do
280
+ it "provides zone defaults for RecordSet values" do
281
+ expected_a = {
282
+ name: "another-host.feegle.com.",
283
+ type: "A",
284
+ ttl: 3600,
285
+ resource_records: [{value: "8.8.8.8"}]
286
+ }
287
+ expect_recipe {
288
+ aws_route53_hosted_zone "feegle.com" do
289
+ defaults ttl: 3600, type: "CNAME"
290
+ record_sets {
291
+ aws_route53_record_set "some-host" do
292
+ resource_records ["some-other-host"]
293
+ end
294
+ aws_route53_record_set "another-host" do
295
+ type "A"
296
+ resource_records ["8.8.8.8"]
297
+ end
298
+ }
299
+ end
300
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
301
+ resource_record_sets: [{}, {},
302
+ expected_a, sdk_cname_rr]).and be_idempotent
303
+ end
304
+
305
+ it "only provides defaults for certain properties" do
306
+ expect_converge {
307
+ aws_route53_hosted_zone "feegle.com" do
308
+ defaults invalid_default: 42
309
+ record_sets {
310
+ aws_route53_record_set "some-host" do
311
+ resource_records ["some-other-host"]
312
+ end
313
+ aws_route53_record_set "another-host" do
314
+ type "A"
315
+ resource_records ["8.8.8.8"]
316
+ end
317
+ }
318
+ end
319
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /'defaults' keys may be any of/)
320
+ end
321
+
322
+ it "checks for requiredness" do
323
+ expect_converge {
324
+ aws_route53_hosted_zone "feegle.com" do
325
+ defaults ttl: 3600
326
+ record_sets {
327
+ aws_route53_record_set "some-host" do
328
+ resource_records ["some-other-host"]
329
+ end
330
+ }
331
+ end
332
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /required/i)
333
+ end
334
+ end
335
+
336
+ context "individual RR types" do
337
+ let(:expected) {{
338
+ cname: {
339
+ name: "cname-host.feegle.com.",
340
+ type: "CNAME",
341
+ ttl: 1800,
342
+ resource_records: [{ value: "8.8.8.8" }],
343
+ },
344
+ a: {
345
+ name: "a-host.feegle.com.",
346
+ type: "A",
347
+ ttl: 1800,
348
+ resource_records: [{ value: "141.222.1.1"}, { value: "8.8.8.8" }],
349
+ },
350
+ aaaa: {
351
+ name: "aaaa-host.feegle.com.",
352
+ type: "AAAA",
353
+ ttl: 1800,
354
+ resource_records: [{ value: "2607:f8b0:4010:801::1001"},
355
+ { value: "2607:f8b9:4010:801::1001" }],
356
+ },
357
+ mx: {
358
+ name: "mx-host.feegle.com.",
359
+ type: "MX",
360
+ ttl: 1800,
361
+ # AWS does *not* append a dot to these.
362
+ resource_records: [{ value: "10 mail1.example.com"}, { value: "15 mail2.example.com."}],
363
+ },
364
+ txt: {
365
+ name: "txt-host.feegle.com.",
366
+ type: "TXT",
367
+ resource_records: [{ value: '"Very Important Data"' },
368
+ { value: '"Even More Important Data"' }],
369
+ },
370
+ srv: {
371
+ name: "srv-host.feegle.com.",
372
+ type: "SRV",
373
+ resource_records: [{ value: "10 50 8889 chef-server.example.com" },
374
+ { value: "20 70 80 narf.net" }],
375
+ },
376
+ }}
377
+
378
+ it "handles CNAME records" do
379
+ expect_recipe {
380
+ aws_route53_hosted_zone "feegle.com" do
381
+ record_sets {
382
+ aws_route53_record_set "CNAME-host" do
383
+ type "CNAME"
384
+ ttl 1800
385
+ resource_records ["8.8.8.8"]
386
+ end
387
+ }
388
+ end
389
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
390
+ resource_record_sets: [ {}, {}, expected[:cname] ]).and be_idempotent
391
+
392
+ expect_converge {
393
+ aws_route53_hosted_zone "feegle.com" do
394
+ record_sets {
395
+ aws_route53_record_set "CNAME-host" do
396
+ type "CNAME"
397
+ ttl 1800
398
+ resource_records ["141.222.1.1", "8.8.8.8"]
399
+ end
400
+ }
401
+ end
402
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /CNAME records.*have a single value/)
403
+ end
404
+
405
+ it "handles A records" do
406
+ expect_recipe {
407
+ aws_route53_hosted_zone "feegle.com" do
408
+ record_sets {
409
+ aws_route53_record_set "A-host" do
410
+ type "A"
411
+ ttl 1800
412
+ resource_records ["141.222.1.1", "8.8.8.8"]
413
+ end
414
+ }
415
+ end
416
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
417
+ resource_record_sets: [ {}, {}, expected[:a] ]).and be_idempotent
418
+
419
+ expect_converge {
420
+ aws_route53_hosted_zone "feegle.com" do
421
+ record_sets {
422
+ aws_route53_record_set "A-host" do
423
+ type "A"
424
+ ttl 1800
425
+ resource_records ["hostnames-dont-go-here.com", "8.8.8.8"]
426
+ end
427
+ }
428
+ end
429
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /A records are of the form/)
430
+ end
431
+
432
+ # we don't validate IPv6 addresses, because they are complex.
433
+ it "handles AAAA records" do
434
+ expect_recipe {
435
+ aws_route53_hosted_zone "feegle.com" do
436
+ record_sets {
437
+ aws_route53_record_set "AAAA-host" do
438
+ type "AAAA"
439
+ ttl 1800
440
+ resource_records ["2607:f8b0:4010:801::1001", "2607:f8b9:4010:801::1001"]
441
+ end
442
+ }
443
+ end
444
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
445
+ resource_record_sets: [ {}, {}, expected[:aaaa] ]).and be_idempotent
446
+ end
447
+
448
+ it "handles MX records" do
449
+ expect_recipe {
450
+ aws_route53_hosted_zone "feegle.com" do
451
+ record_sets {
452
+ aws_route53_record_set "MX-host" do
453
+ type "MX"
454
+ ttl 1800
455
+ resource_records ["10 mail1.example.com", "15 mail2.example.com."]
456
+ end
457
+ }
458
+ end
459
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
460
+ resource_record_sets: [ {}, {}, expected[:mx] ]).and be_idempotent
461
+ expect_converge {
462
+ aws_route53_hosted_zone "feegle.com" do
463
+ record_sets {
464
+ aws_route53_record_set "MX-host" do
465
+ type "MX"
466
+ ttl 1800
467
+ resource_records ["10mail1.example.com", "mail2.example.com."]
468
+ end
469
+ }
470
+ end
471
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /MX records must have a priority and mail server/)
472
+ end
473
+
474
+ # we don't validate TXT values:
475
+ # http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html#TXTFormat
476
+ it "handles TXT records" do
477
+ expect_recipe {
478
+ aws_route53_hosted_zone "feegle.com" do
479
+ record_sets {
480
+ aws_route53_record_set "TXT-host" do
481
+ type "TXT"
482
+ ttl 300
483
+ resource_records %w["Very\ Important\ Data" "Even\ More\ Important\ Data"]
484
+ end
485
+ }
486
+ end
487
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
488
+ resource_record_sets: [ {}, {}, expected[:txt] ]).and be_idempotent
489
+ end
490
+
491
+ it "handles SRV records" do
492
+ expect_recipe {
493
+ aws_route53_hosted_zone "feegle.com" do
494
+ record_sets {
495
+ aws_route53_record_set "SRV-host" do
496
+ type "SRV"
497
+ ttl 300
498
+ resource_records ["10 50 8889 chef-server.example.com", "20 70 80 narf.net"]
499
+ end
500
+ }
501
+ end
502
+ }.to create_an_aws_route53_hosted_zone("feegle.com",
503
+ resource_record_sets: [ {}, {}, expected[:srv] ]).and be_idempotent
504
+
505
+ expect_converge {
506
+ aws_route53_hosted_zone "feegle.com" do
507
+ record_sets {
508
+ aws_route53_record_set "SRV-host" do
509
+ type "SRV"
510
+ ttl 300
511
+ resource_records ["1050 8889 chef-server.example.com", "narf.net"]
512
+ end
513
+ }
514
+ end
515
+ }.to raise_error(Chef::Exceptions::ValidationFailed, /SRV.*priority, weight, port, and hostname/)
516
+ end
517
+ end # end RR types
518
+ end
519
+ end
520
+ end
521
+ end
522
+ end