geoengineer 0.1.4 → 0.1.5

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 (47) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +1 -0
  5. data/lib/geoengineer/cli/geo_cli.rb +1 -0
  6. data/lib/geoengineer/cli/terraform_commands.rb +35 -0
  7. data/lib/geoengineer/environment.rb +1 -0
  8. data/lib/geoengineer/resource.rb +31 -0
  9. data/lib/geoengineer/resources/aws_alb.rb +2 -0
  10. data/lib/geoengineer/resources/aws_alb_listener.rb +16 -6
  11. data/lib/geoengineer/resources/aws_alb_target_group.rb +19 -7
  12. data/lib/geoengineer/resources/aws_customer_gateway.rb +4 -0
  13. data/lib/geoengineer/resources/aws_emr_cluster.rb +20 -0
  14. data/lib/geoengineer/resources/aws_iam_role.rb +1 -0
  15. data/lib/geoengineer/resources/aws_kinesis_stream.rb +10 -7
  16. data/lib/geoengineer/resources/aws_lambda_function.rb +4 -5
  17. data/lib/geoengineer/resources/aws_nat_gateway.rb +6 -7
  18. data/lib/geoengineer/resources/aws_network_interface.rb +22 -0
  19. data/lib/geoengineer/resources/aws_placement_group.rb +25 -0
  20. data/lib/geoengineer/resources/aws_route53_record.rb +45 -4
  21. data/lib/geoengineer/resources/aws_route53_zone.rb +34 -7
  22. data/lib/geoengineer/resources/aws_route_table.rb +1 -1
  23. data/lib/geoengineer/resources/aws_s3_bucket_object.rb +24 -0
  24. data/lib/geoengineer/resources/aws_vpn_connection.rb +17 -9
  25. data/lib/geoengineer/resources/aws_vpn_connection_route.rb +50 -14
  26. data/lib/geoengineer/resources/aws_waf_ipset.rb +35 -0
  27. data/lib/geoengineer/resources/aws_waf_rule.rb +26 -0
  28. data/lib/geoengineer/resources/aws_waf_web_acl.rb +26 -0
  29. data/lib/geoengineer/utils/aws_clients.rb +15 -0
  30. data/lib/geoengineer/utils/has_validations.rb +1 -0
  31. data/lib/geoengineer/version.rb +1 -1
  32. data/spec/resource_spec.rb +56 -0
  33. data/spec/resources/aws_alb_listener_spec.rb +6 -1
  34. data/spec/resources/aws_alb_spec.rb +7 -0
  35. data/spec/resources/aws_alb_target_group_spec.rb +22 -0
  36. data/spec/resources/aws_emr_cluster_spec.rb +23 -0
  37. data/spec/resources/aws_kinesis_stream_spec.rb +26 -11
  38. data/spec/resources/aws_network_interface_spec.rb +32 -0
  39. data/spec/resources/aws_placement_group_spec.rb +29 -0
  40. data/spec/resources/aws_route53_zone_spec.rb +19 -3
  41. data/spec/resources/aws_s3_bucket_object_spec.rb +4 -0
  42. data/spec/resources/aws_waf_ipset_spec.rb +65 -0
  43. data/spec/resources/aws_waf_rule_spec.rb +36 -0
  44. data/spec/resources/aws_waf_web_acl_spec.rb +36 -0
  45. data/spec/spec_helper.rb +1 -1
  46. metadata +23 -2
  47. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ebcdcd4573207cc095eeb99bcc20771ddae9f2fa
4
- data.tar.gz: d34079a107d98e72ebb3206395d6982a63238efc
3
+ metadata.gz: 10c2741c5361a9ab218a96c9de280f01ea20c9a5
4
+ data.tar.gz: d18876526519536af82c08f15128c20fab9047c5
5
5
  SHA512:
6
- metadata.gz: '0092827fe84c1e9325ec4511133f7d80299bbd9d67a96e47ac62c84c11fc7e5c792e298ecbc78442d9ab219c9d5168a16eb12b2b162b2df53f138e28bc308e55'
7
- data.tar.gz: ddd2677004c40b506e3336fa0882236a8c231181aa1f9ce405816b8de95ec446a02eee7e687c5ab2dec3dc741bc9bc4c9b8f8a72655103bf3764b1155ad54918
6
+ metadata.gz: ff50a5841f091791bf99ce86b57d5bd74b6f20ae4dbac98b1a096271c3ed54475959bcb2f1801028f9aadcb7839cf60b15630f149d010f409872624375c1e76b
7
+ data.tar.gz: e67c59d14db3bebc2414d6cf245a5070c78fd43e62a098a92f9682fa47f9f491771ede031eb5242d18d20562b84369b17dd43e6595b2ed7e8c96d8667a0f49c1
Binary file
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -61,6 +61,7 @@ environment("staging") {
61
61
  account_id "1"
62
62
  subnet "1"
63
63
  vpc_id "1"
64
+ allow_destroy true ## Defaults to false. Set to true to support `geo destroy ...`
64
65
  }
65
66
 
66
67
  # Create the first_project to be in the `staging` environment
@@ -189,6 +189,7 @@ class GeoCLI
189
189
  def add_commands
190
190
  plan_cmd
191
191
  apply_cmd
192
+ destroy_cmd
192
193
  graph_cmd
193
194
  status_cmd
194
195
  end
@@ -22,6 +22,7 @@ module GeoCLI::TerraformCommands
22
22
  def terraform_plan
23
23
  plan_commands = [
24
24
  "cd #{@tmpdir}",
25
+ "terraform init",
25
26
  "terraform refresh",
26
27
  "terraform plan --refresh=false -parallelism=#{terraform_parallelism}" \
27
28
  " -state=#{@terraform_state_file} -out=#{@plan_file} #{@no_color}"
@@ -30,6 +31,17 @@ module GeoCLI::TerraformCommands
30
31
  shell_exec(plan_commands.join(" && "), true)
31
32
  end
32
33
 
34
+ def terraform_plan_destroy
35
+ plan_destroy_commands = [
36
+ "cd #{@tmpdir}",
37
+ "terraform refresh",
38
+ "terraform plan -destroy --refresh=false -parallelism=#{terraform_parallelism}" \
39
+ " -state=#{@terraform_state_file} -out=#{@plan_file} #{@no_color}"
40
+ ]
41
+
42
+ shell_exec(plan_destroy_commands.join(" && "), true)
43
+ end
44
+
33
45
  def terraform_apply
34
46
  apply_commands = [
35
47
  "cd #{@tmpdir}",
@@ -39,6 +51,15 @@ module GeoCLI::TerraformCommands
39
51
  shell_exec(apply_commands.join(" && "), true)
40
52
  end
41
53
 
54
+ def terraform_destroy
55
+ destroy_commands = [
56
+ "cd #{@tmpdir}",
57
+ "terraform apply -parallelism=#{terraform_parallelism}" \
58
+ " #{@plan_file} #{@no_color}"
59
+ ]
60
+ shell_exec(destroy_commands.join(" && "), true)
61
+ end
62
+
42
63
  def plan_cmd
43
64
  command :plan do |c|
44
65
  c.syntax = 'geo plan [<geo_files>]'
@@ -64,4 +85,18 @@ module GeoCLI::TerraformCommands
64
85
  c.action init_action(:apply, &action)
65
86
  end
66
87
  end
88
+
89
+ def destroy_cmd
90
+ command :destroy do |c|
91
+ c.syntax = 'geo destroy [<geo_files>]'
92
+ c.description = 'Destroy an execution plan'
93
+ action = lambda do |args, options|
94
+ create_terraform_files
95
+ return puts "Plan Broken" if terraform_plan_destroy.exitstatus.nonzero?
96
+ return puts "Rejecting Plan" unless yes?("Apply the above plan? [YES/NO]")
97
+ terraform_destroy
98
+ end
99
+ c.action init_action(:destroy, &action)
100
+ end
101
+ end
67
102
  end
@@ -72,6 +72,7 @@ class GeoEngineer::Environment
72
72
 
73
73
  def resource(type, id, &block)
74
74
  return find_resource(type, id) unless block_given?
75
+
75
76
  resource = create_resource(type, id, &block)
76
77
  resource.environment = self
77
78
  resource
@@ -114,6 +114,37 @@ class GeoEngineer::Resource
114
114
  self
115
115
  end
116
116
 
117
+ def duplicate(new_id, &block)
118
+ parent = @project || @environment
119
+ return unless parent
120
+
121
+ duplicated = duplicate_resource(parent, self, new_id)
122
+ duplicated.reset
123
+ duplicated.instance_exec(duplicated, &block) if block_given?
124
+ duplicated.execute_lifecycle(:after, :initialize)
125
+
126
+ duplicated
127
+ end
128
+
129
+ def duplicate_resource(parent, progenitor, new_id)
130
+ parent.resource(progenitor.type, new_id) do
131
+ # We want to set all attributes from the parent, EXCEPT _geo_id and _terraform_id
132
+ # Which should be set according to the init logic
133
+ progenitor.attributes.each do |key, value|
134
+ self[key] = value unless %w(_geo_id _terraform_id).include?(key)
135
+ end
136
+
137
+ progenitor.subresources.each do |subresource|
138
+ duplicated_subresource = GeoEngineer::SubResource.new(self, subresource.type) do
139
+ subresource.attributes.each do |key, value|
140
+ self[key] = value
141
+ end
142
+ end
143
+ self.subresources << duplicated_subresource
144
+ end
145
+ end
146
+ end
147
+
117
148
  def _json_file(attribute, path, binding_obj = nil)
118
149
  raise "file #{path} not found" unless File.file?(path)
119
150
 
@@ -29,6 +29,8 @@ class GeoEngineer::Resources::AwsAlb < GeoEngineer::Resource
29
29
 
30
30
  def self._fetch_remote_resources(provider)
31
31
  albs = AwsClients.alb(provider).describe_load_balancers['load_balancers'].map(&:to_h)
32
+ return [] if albs.empty?
33
+
32
34
  tags = AwsClients.alb(provider)
33
35
  .describe_tags({ resource_arns: albs.map { |alb| alb[:load_balancer_arn] } })
34
36
  .tag_descriptions
@@ -4,23 +4,33 @@
4
4
  # {https://www.terraform.io/docs/providers/aws/r/alb_listener.html Terraform Docs}
5
5
  ########################################################################
6
6
  class GeoEngineer::Resources::AwsAlbListener < GeoEngineer::Resource
7
- validate -> { validate_required_attributes([:load_balancer_arn, :port, :default_action]) }
7
+ validate -> {
8
+ validate_required_attributes([:_load_balancer_name, :load_balancer_arn, :port, :default_action])
9
+ }
8
10
  validate -> {
9
11
  validate_subresource_required_attributes(:default_action, [:target_group_arn, :type])
10
12
  }
11
13
 
14
+ # Since we can't know the ARN until the ALB exists, it is not a good candidate for the
15
+ # _geo_id - instead we use the ALB name, which is also unique per region
16
+ after :initialize, -> { _geo_id -> { "#{_load_balancer_name}::#{port}" } }
12
17
  after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
13
- after :initialize, -> { _geo_id -> { "#{load_balancer_arn}::#{port}" } }
14
18
 
15
19
  def short_type
16
20
  "alb_listener"
17
21
  end
18
22
 
19
- def self._merge_attributes(listener)
23
+ def support_tags?
24
+ false
25
+ end
26
+
27
+ def self._merge_attributes(listener, alb)
20
28
  listener.merge(
21
29
  {
22
- _geo_id: "#{listener[:load_balancer_arn]}::#{listener[:port]}",
23
- _terraform_id: listener[:listener_arn]
30
+ _geo_id: "#{alb[:load_balancer_name]}::#{listener[:port]}",
31
+ _terraform_id: listener[:listener_arn],
32
+ load_balancer_arn: alb[:load_balancer_arn],
33
+ load_balancer_name: alb[:load_balancer_name]
24
34
  }
25
35
  )
26
36
  end
@@ -33,7 +43,7 @@ class GeoEngineer::Resources::AwsAlbListener < GeoEngineer::Resource
33
43
  .describe_listeners({ load_balancer_arn: alb[:load_balancer_arn] })
34
44
  .listeners
35
45
  .map(&:to_h)
36
- .map { |listener| _merge_attributes(listener) }
46
+ .map { |listener| _merge_attributes(listener, alb) }
37
47
  end.flatten.compact
38
48
  end
39
49
  end
@@ -10,21 +10,33 @@ class GeoEngineer::Resources::AwsAlbTargetGroup < GeoEngineer::Resource
10
10
  validate -> { validate_subresource_required_attributes(:stickiness, [:type]) }
11
11
 
12
12
  after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
13
- after :initialize, -> { _geo_id -> { "#{vpc_id}::#{protocol}::#{port}" } }
13
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
14
14
 
15
15
  def short_type
16
16
  "alb_target_group"
17
17
  end
18
18
 
19
- def self._fetch_remote_resources(provider)
20
- target_groups = AwsClients.alb(provider).describe_target_groups.target_groups.map(&:to_h)
21
- target_groups.map do |group|
22
- group.merge(
19
+ def self._merge_attributes(target_groups, tags)
20
+ target_groups.map do |target_group|
21
+ target_tags = tags.find { |desc| desc[:resource_arn] == target_group[:target_group_arn] }
22
+ target_group.merge(
23
23
  {
24
- _geo_id: "#{group[:vpc_id]}::#{group[:protocol]}::#{group[:port]}",
25
- _terraform_id: group[:target_group_arn]
24
+ _terraform_id: target_group[:target_group_arn],
25
+ _geo_id: (target_tags || {})[:tags]&.find { |tag| tag[:key] == "Name" }&.dig(:value)
26
26
  }
27
27
  )
28
28
  end
29
29
  end
30
+
31
+ def self._fetch_remote_resources(provider)
32
+ target_groups = AwsClients.alb(provider).describe_target_groups.target_groups
33
+ return [] if target_groups.empty?
34
+
35
+ tags = AwsClients.alb(provider)
36
+ .describe_tags({ resource_arns: target_groups.map(&:target_group_arn) })
37
+ .tag_descriptions
38
+ .map(&:to_h)
39
+
40
+ _merge_attributes(target_groups.map(&:to_h), tags)
41
+ end
30
42
  end
@@ -10,6 +10,10 @@ class GeoEngineer::Resources::AwsCustomerGateway < GeoEngineer::Resource
10
10
  after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
11
11
  after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
12
12
 
13
+ def gateway_type(val = nil)
14
+ val ? self["type"] = val : self["type"]
15
+ end
16
+
13
17
  def self._fetch_remote_resources(provider)
14
18
  AwsClients.ec2(provider)
15
19
  .describe_customer_gateways['customer_gateways']
@@ -0,0 +1,20 @@
1
+ ########################################################################
2
+ # AwsEmr is the +aws_emr_cluster+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/emr_cluster.html}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsEmrCluster < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name]) }
8
+ validate -> { validate_has_tag(:Name) }
9
+
10
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
11
+ after :initialize, -> { _geo_id -> { name } }
12
+
13
+ def self._fetch_remote_resources(provider)
14
+ AwsClients.emr(provider).list_clusters['clusters'].map(&:to_h).map do |cluster|
15
+ cluster[:_terraform_id] = cluster[:id]
16
+ cluster[:_geo_id] = cluster[:name]
17
+ cluster
18
+ end
19
+ end
20
+ end
@@ -19,6 +19,7 @@ class GeoEngineer::Resources::AwsIamRole < GeoEngineer::Resource
19
19
 
20
20
  attributes = {}
21
21
  attributes['arn'] = arn if arn
22
+ attributes['force_detach_policies'] = force_detach_policies || 'false'
22
23
  attributes['assume_role_policy'] = _normalize_json(assume_role_policy) if assume_role_policy
23
24
 
24
25
  tfstate[:primary][:attributes] = attributes
@@ -19,14 +19,17 @@ class GeoEngineer::Resources::AwsKinesisStream < GeoEngineer::Resource
19
19
  tfstate
20
20
  end
21
21
 
22
+ def self._stream_description(stream_name)
23
+ AwsClients.kinesis
24
+ .describe_stream({ stream_name: stream_name })
25
+ .stream_description
26
+ .to_h
27
+ end
28
+
22
29
  def self._all_streams(provider)
23
- streams = []
24
- AwsClients.kinesis(provider).list_streams[:stream_names].each do |stream_name|
25
- AwsClients.kinesis.describe_stream({ stream_name: stream_name }).map(&:to_h).map do |stream|
26
- streams << stream[:stream_description]
27
- end
28
- end
29
- streams
30
+ AwsClients.kinesis(provider)
31
+ .list_streams[:stream_names]
32
+ .map { |s| self._stream_description(s) }
30
33
  end
31
34
 
32
35
  def self._fetch_remote_resources(provider)
@@ -32,13 +32,12 @@ class GeoEngineer::Resources::AwsLambdaFunction < GeoEngineer::Resource
32
32
  tfstate
33
33
  end
34
34
 
35
- def support_tags?
36
- false
37
- end
38
-
39
35
  def self._fetch_remote_resources(provider)
40
36
  AwsClients.lambda(provider).list_functions['functions'].map(&:to_h).map do |function|
41
- function.merge({ _terraform_id: function[:function_name] })
37
+ function.merge({
38
+ _terraform_id: function[:function_name],
39
+ _geo_id: function[:function_name]
40
+ })
42
41
  end
43
42
  end
44
43
  end
@@ -4,7 +4,7 @@
4
4
  # {https://www.terraform.io/docs/providers/aws/r/nat_gateway.html Terraform Docs}
5
5
  ########################################################################
6
6
  class GeoEngineer::Resources::AwsNatGateway < GeoEngineer::Resource
7
- validate -> { validate_required_attributes([:subnet_id, :allocation_id]) }
7
+ validate -> { validate_required_attributes(%i(subnet_id allocation_id)) }
8
8
 
9
9
  after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
10
  after :initialize, -> { _geo_id -> { "#{allocation_id}::#{subnet_id}" } }
@@ -18,12 +18,11 @@ class GeoEngineer::Resources::AwsNatGateway < GeoEngineer::Resource
18
18
  # AWS SDK has `nat_gateway_addresses` as an array, but you should only be able to
19
19
  # have exactly 1 elastic IP association. This logic should cover the bases...
20
20
  allocation = gateway[:nat_gateway_addresses].find { |addr| addr.key?(:allocation_id) }
21
- gateway.merge(
22
- {
23
- _terraform_id: gateway[:nat_gateway_id],
24
- _geo_id: "#{allocation[:allocation_id]}::#{gateway[:subnet_id]}"
25
- }
26
- )
21
+
22
+ gateway[:_terraform_id] = gateway[:nat_gateway_id]
23
+ gateway[:_geo_id] = "#{allocation[:allocation_id]}::#{gateway[:subnet_id]}"
24
+
25
+ gateway
27
26
  end
28
27
  end
29
28
  end
@@ -0,0 +1,22 @@
1
+ ########################################################################
2
+ # AwsNetworkInterface is the +aws_network_interface+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/network_interface.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsNetworkInterface < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:subnet_id]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
+ after :initialize, -> { _geo_id -> { Array(private_ips).join(',') } }
11
+
12
+ def self._fetch_remote_resources(provider)
13
+ interfaces = AwsClients.ec2(provider).describe_network_interfaces
14
+
15
+ interfaces['network_interfaces'].map(&:to_h).map do |interface|
16
+ addresses = interface[:private_ip_addresses].collect { |a| a[:private_ip_address] }
17
+ interface[:_terraform_id] = interface[:network_interface_id]
18
+ interface[:_geo_id] = addresses.join(',')
19
+ interface
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ ########################################################################
2
+ # AwsPlacementGroup is the +aws_placement_group+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/placement_group.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsPlacementGroup < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name, :strategy]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
+ after :initialize, -> { _geo_id -> { name } }
11
+
12
+ def support_tags?
13
+ false
14
+ end
15
+
16
+ def self._fetch_remote_resources(provider)
17
+ pgs = AwsClients.ec2(provider).describe_placement_groups['placement_groups']
18
+ pgs.map(&:to_h).map do |group|
19
+ group[:_terraform_id] = group[:group_name]
20
+ group[:_geo_id] = group[:group_name]
21
+ group[:name] = group[:group_name]
22
+ group
23
+ end
24
+ end
25
+ end
@@ -3,6 +3,8 @@
3
3
  #
4
4
  # {https://www.terraform.io/docs/providers/aws/r/route53_record.html Terraform Docs}
5
5
  ########################################################################
6
+
7
+ # Note: Currently, 'name' must be the fully qualified domain name.
6
8
  class GeoEngineer::Resources::AwsRoute53Record < GeoEngineer::Resource
7
9
  validate -> { validate_required_attributes([:zone_id, :name, :type]) }
8
10
  validate -> { validate_required_attributes([:ttl, :records]) unless self.alias }
@@ -13,7 +15,26 @@ class GeoEngineer::Resources::AwsRoute53Record < GeoEngineer::Resource
13
15
  end
14
16
  }
15
17
 
16
- after :initialize, -> { _terraform_id -> { "#{zone_id}_#{name}_#{type}" } }
18
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
19
+ after :initialize, -> { _geo_id -> { "#{zone_id}_#{self.name&.downcase}_#{record_type}" } }
20
+
21
+ def to_terraform_state
22
+ tfstate = super
23
+ tfstate[:primary][:attributes] = {
24
+ 'id' => _terraform_id,
25
+ 'name' => name,
26
+ 'type' => record_type
27
+ }
28
+ tfstate
29
+ end
30
+
31
+ def record_type(val = nil)
32
+ val ? self["type"] = val : self["type"]
33
+ end
34
+
35
+ def fqdn
36
+ self["name"].downcase
37
+ end
17
38
 
18
39
  def support_tags?
19
40
  false
@@ -28,9 +49,29 @@ class GeoEngineer::Resources::AwsRoute53Record < GeoEngineer::Resource
28
49
  end
29
50
 
30
51
  def self._fetch_records_for_zone(provider, zone)
31
- records = AwsClients.route53(provider).list_resource_record_sets({ hosted_zone_id: zone[:id] })
32
- records.resource_record_sets.map(&:to_h).map do |record|
33
- record.merge({ _terraform_id: "#{record[:zone_id]}_#{record[:name]}_#{record[:type]}" })
52
+ zone_id = zone[:id].gsub(%r{^/hostedzone/}, '')
53
+ response = AwsClients.route53(provider).list_resource_record_sets({ hosted_zone_id: zone_id })
54
+
55
+ records = []
56
+ response.each do |page|
57
+ records += page.resource_record_sets.map(&:to_h).map do |record|
58
+ name = _fetch_name(record, zone)
59
+ id = "#{zone_id}_#{name}_#{record[:type]}"
60
+ record.merge({ fqdn: name, _terraform_id: id, _geo_id: id })
61
+ end
62
+ end
63
+
64
+ records
65
+ end
66
+
67
+ def self._fetch_name(record, zone)
68
+ # Need to trim the trailing dot, as well as convert ASCII 42 (Octal 52) to
69
+ # the wildcard star. Route53 uses that for wildcard records.
70
+ name = record[:name].downcase.gsub(/\.$/, '').gsub(/^\\052/, '*')
71
+ zone_name = zone[:name].gsub(/\.$/, '')
72
+ if name !~ /#{zone_name}$/
73
+ name = name.empty? ? zone_name : "#{name}.#{zone_name}"
34
74
  end
75
+ name
35
76
  end
36
77
  end