chef-provisioning-aws 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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