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
@@ -7,15 +7,42 @@ class GeoEngineer::Resources::AwsRoute53Zone < GeoEngineer::Resource
7
7
  validate -> { validate_required_attributes([:name]) }
8
8
 
9
9
  after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
- after :initialize, -> { _geo_id -> { self.name } }
10
+ after :initialize, -> { _geo_id -> { "#{self._public_or_private}-#{self.name}." } }
11
+
12
+ def _public_or_private
13
+ self.vpc_id.nil? ? 'public' : self.vpc_id
14
+ end
15
+
16
+ def to_terraform_state
17
+ tfstate = super
18
+ tfstate[:primary][:attributes] = {
19
+ 'name' => name,
20
+ 'vpc_id' => vpc_id,
21
+ 'force_destroy' => (force_destroy || 'false')
22
+ }
23
+ tfstate
24
+ end
11
25
 
12
26
  def self._fetch_remote_resources(provider)
13
- hosted_zones = AwsClients.route53(provider).list_hosted_zones.hosted_zones.map(&:to_h)
27
+ _fetch_zones(provider).map { |zone| _generate_remote_zone(provider, zone) }
28
+ end
29
+
30
+ def self._fetch_zones(provider)
31
+ AwsClients.route53(provider).list_hosted_zones.hosted_zones.map(&:to_h)
32
+ end
33
+
34
+ def self._generate_remote_zone(provider, zone)
35
+ is_private_zone = zone.dig(:config, :private_zone) || false
36
+
37
+ zone[:id] = zone[:id].gsub(%r{^/hostedzone/}, '')
38
+ zone[:zone_id] = zone[:id]
39
+ zone[:_terraform_id] = zone[:id]
40
+ zone[:vpc_id] = _get_zone_vpc_id(provider, zone[:id]) if is_private_zone
41
+ zone[:_geo_id] = "#{is_private_zone ? zone[:vpc_id] : 'public'}-#{zone[:name]}"
42
+ zone
43
+ end
14
44
 
15
- hosted_zones.map do |zone|
16
- zone[:_terraform_id] = zone[:id]
17
- zone[:_geo_id] = zone[:name]
18
- zone
19
- end
45
+ def self._get_zone_vpc_id(provider, zone_id)
46
+ AwsClients.route53(provider).get_hosted_zone({ id: zone_id }).to_h[:vp_cs].first[:vpc_id]
20
47
  end
21
48
  end
@@ -7,7 +7,7 @@ class GeoEngineer::Resources::AwsRouteTable < GeoEngineer::Resource
7
7
  validate -> { validate_required_attributes([:vpc_id]) }
8
8
  validate -> { validate_has_tag(:Name) }
9
9
  validate -> {
10
- validate_subresource_required_attributes(:route, [:cider_block]) unless self.all_route.empty?
10
+ validate_subresource_required_attributes(:route, [:cidr_block]) unless self.all_route.empty?
11
11
  }
12
12
 
13
13
  after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
@@ -0,0 +1,24 @@
1
+ ########################################################################
2
+ # AwsS3Bucket is the +aws_s3_bucket_object+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/s3_bucket_object.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsS3BucketObject < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:bucket]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { bucket } }
10
+
11
+ def to_terraform_state
12
+ tfstate = super
13
+ tfstate[:primary][:attributes] = {
14
+ 'bucket' => bucket,
15
+ 'key' => key,
16
+ 'source' => source
17
+ }
18
+ tfstate
19
+ end
20
+
21
+ def short_type
22
+ 's3_object'
23
+ end
24
+ end
@@ -10,15 +10,23 @@ class GeoEngineer::Resources::AwsVpnConnection < 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 vpn_type(val = nil)
14
+ val ? self["type"] = val : self["type"]
15
+ end
16
+
13
17
  def self._fetch_remote_resources(provider)
14
- AwsClients.ec2(provider)
15
- .describe_vpn_connections['vpn_connections'].map(&:to_h).map do |connection|
16
- connection.merge(
17
- {
18
- _terraform_id: connection[:vpn_connection_id],
19
- _geo_id: connection[:tags].find { |tag| tag[:key] == "Name" }&.dig(:value)
20
- }
21
- )
22
- end
18
+ AwsClients
19
+ .ec2(provider)
20
+ .describe_vpn_connections['vpn_connections']
21
+ .reject { |connection| connection['state'] == 'deleted' } # Necessary for development
22
+ .map(&:to_h)
23
+ .map do |connection|
24
+ connection.merge(
25
+ {
26
+ _terraform_id: connection[:vpn_connection_id],
27
+ _geo_id: connection[:tags].find { |tag| tag[:key] == "Name" }&.dig(:value)
28
+ }
29
+ )
30
+ end
23
31
  end
24
32
  end
@@ -6,30 +6,66 @@
6
6
  class GeoEngineer::Resources::AwsVpnConnectionRoute < GeoEngineer::Resource
7
7
  validate -> { validate_required_attributes([:destination_cidr_block, :vpn_connection_id]) }
8
8
 
9
- after :initialize, -> { _terraform_id -> { "#{destination_cidr_block}:#{vpn_connection_id}" } }
9
+ after :initialize, -> {
10
+ _terraform_id -> {
11
+ connection_route_id unless terraform_ref?
12
+ }
13
+ }
14
+
15
+ after :initialize, -> {
16
+ _geo_id -> {
17
+ connection_route_id
18
+ }
19
+ }
20
+
21
+ # Is the VPN connection id a terraform ref or an id
22
+ def terraform_ref?
23
+ /^\${[a-zA-Z0-9\._-]+}$/.match(vpn_connection_id)
24
+ end
25
+
26
+ def to_terraform_state
27
+ tfstate = super
28
+
29
+ tfstate[:primary][:attributes] = {
30
+ 'destination_cidr_block' => destination_cidr_block,
31
+ 'vpn_connection_id' => vpn_connection_id
32
+ }
33
+
34
+ tfstate
35
+ end
36
+
37
+ def connection_route_id
38
+ self.class.build_connection_route_id(
39
+ destination_cidr_block,
40
+ vpn_connection_id
41
+ )
42
+ end
43
+
44
+ def self.build_connection_route_id(cidr, connection_id)
45
+ "#{cidr}:#{connection_id}"
46
+ end
10
47
 
11
48
  def support_tags?
12
49
  false
13
50
  end
14
51
 
15
52
  def self._fetch_remote_resources(provider)
16
- AwsClients
17
- .ec2(provider)
18
- .describe_vpn_connections['vpn_connections']
19
- .map(&:to_h)
20
- .select { |connection| !connection[:routes].empty? }
21
- .map { |connection| _generate_routes(connection) }
22
- .flatten
53
+ AwsClients.ec2(provider)
54
+ .describe_vpn_connections['vpn_connections']
55
+ .map(&:to_h)
56
+ .select { |connection| !connection[:routes].empty? }
57
+ .map { |connection| _generate_routes(connection) }
58
+ .flatten
23
59
  end
24
60
 
25
61
  def self._generate_routes(connection)
26
62
  connection[:routes].map do |route|
27
- route.merge(
28
- {
29
- _terraform_id: "#{route[:destination_cidr_block]}:#{connection[:vpn_connection_id]}",
30
- vpn_connection_id: connection[:vpn_connection_id]
31
- }
32
- )
63
+ cidr = route[:destination_cidr_block]
64
+ connection_id = connection[:vpn_connection_id]
65
+
66
+ id = build_connection_route_id(cidr, connection_id)
67
+
68
+ route.merge({ _terraform_id: id, _geo_id: id })
33
69
  end
34
70
  end
35
71
  end
@@ -0,0 +1,35 @@
1
+ ########################################################################
2
+ # AwsWafIpset is the +aws_waf_ipset+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/waf_ipset.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsWafIpset < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name]) }
8
+ validate :validate_correct_cidr_blocks
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.waf(provider).list_ip_sets['ip_sets'].map(&:to_h).map do |s|
15
+ s.merge(
16
+ {
17
+ _terraform_id: s[:ip_set_id],
18
+ _geo_id: s[:name]
19
+ }
20
+ )
21
+ s
22
+ end
23
+ end
24
+
25
+ def validate_correct_cidr_blocks
26
+ errors = []
27
+ error = validate_cidr_block(self.ip_set_descriptors&.value)
28
+ errors << error unless error.nil?
29
+ errors
30
+ end
31
+
32
+ def support_tags?
33
+ false
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ ########################################################################
2
+ # AwsWafRule is the +aws_waf_rule+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/waf_rule.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsWafRule < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:metric_name, :name]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
+ after :initialize, -> { _geo_id -> { name } }
11
+
12
+ def self._fetch_remote_resources(provider)
13
+ AwsClients.waf(provider).list_rules['rules'].map(&:to_h).map do |s|
14
+ s.merge(
15
+ {
16
+ _terraform_id: s[:rule_id],
17
+ _geo_id: s[:name]
18
+ }
19
+ )
20
+ end
21
+ end
22
+
23
+ def support_tags?
24
+ false
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ ########################################################################
2
+ # AwsWafWebAcl is the +aws_waf_web_acl+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/waf_web_acl.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsWafWebAcl < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:metric_name, :default_action, :name, :rules]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
+ after :initialize, -> { _geo_id -> { name } }
11
+
12
+ def self._fetch_remote_resources(provider)
13
+ AwsClients.waf(provider).list_web_acls['web_acls'].map(&:to_h).map do |acl|
14
+ acl.merge(
15
+ {
16
+ _terraform_id: acl[:web_acl_id],
17
+ _geo_id: acl[:name]
18
+ }
19
+ )
20
+ end
21
+ end
22
+
23
+ def support_tags?
24
+ false
25
+ end
26
+ end
@@ -14,6 +14,7 @@ class AwsClients
14
14
  def self.client_params(provider = nil)
15
15
  client_params = { stub_responses: stubbed? }
16
16
  client_params[:region] = provider.region if provider
17
+ client_params[:retry_limit] = Integer(ENV['AWS_RETRY_LIMIT']) if ENV['AWS_RETRY_LIMIT']
17
18
  client_params
18
19
  end
19
20
 
@@ -178,4 +179,18 @@ class AwsClients
178
179
  Aws::KMS::Client
179
180
  )
180
181
  end
182
+
183
+ def self.waf(provider = nil)
184
+ self.client_cache(
185
+ provider,
186
+ Aws::WAF::Client
187
+ )
188
+ end
189
+
190
+ def self.emr(provider = nil)
191
+ self.client_cache(
192
+ provider,
193
+ Aws::EMR::Client
194
+ )
195
+ end
181
196
  end
@@ -51,6 +51,7 @@ module HasValidations
51
51
  # Validates CIDR block format
52
52
  # Returns error when argument fails validation
53
53
  def validate_cidr_block(cidr_block)
54
+ return "Empty cidr block" if cidr_block.nil? || cidr_block.empty?
54
55
  return if NetAddr::CIDR.create(cidr_block)
55
56
  rescue NetAddr::ValidationError
56
57
  return "Bad cidr block \"#{cidr_block}\" #{for_resource}"
@@ -1,3 +1,3 @@
1
1
  module GeoEngineer
2
- VERSION = '0.1.4'.freeze
2
+ VERSION = '0.1.5'.freeze
3
3
  end
@@ -325,6 +325,62 @@ describe GeoEngineer::Resource do
325
325
  end
326
326
  end
327
327
 
328
+ describe '#duplicate' do
329
+ let!(:project) do
330
+ GeoEngineer::Project.new('org', 'project_name', nil) {
331
+ tags {
332
+ a '1'
333
+ }
334
+ }
335
+ end
336
+ let!(:resource_class) do
337
+ class GeoEngineer::Resources::Derp < GeoEngineer::Resource
338
+ validate -> { validate_has_tag(:Name) }
339
+ after :initialize, -> {
340
+ _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
341
+ }
342
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
343
+ after :initialize, -> { _number -> { NullObject.maybe(_geo_id)[-1] } }
344
+
345
+ def self._fetch_remote_resources(provider)
346
+ [
347
+ { _geo_id: "geo_id1", _terraform_id: "t1 baby!" },
348
+ { _geo_id: "geo_id2", _terraform_id: "t who?" }
349
+ ]
350
+ end
351
+ end
352
+ end
353
+
354
+ let(:subject) do
355
+ project.resource('derp', 'id') {
356
+ tags {
357
+ Name "geo_id1"
358
+ }
359
+ }
360
+ end
361
+
362
+ it 'copies over attributes and subresources' do
363
+ copy = subject.duplicate('duplicate')
364
+ # We haven't changed anything, so it should all match
365
+ expect(copy.type).to eq(subject.type)
366
+ expect(copy._geo_id).to eq(subject._geo_id)
367
+ expect(copy._terraform_id).to eq(subject._terraform_id)
368
+ expect(copy._number).to eq(subject._number)
369
+ expect(copy.tags["Name"]).to eq(subject.tags["Name"])
370
+ end
371
+
372
+ it 'handles procs appropriately' do
373
+ copy = subject.duplicate('duplicate')
374
+ copy.tags["Name"] = "geo_id2"
375
+
376
+ expect(copy.type).to eq(subject.type)
377
+ expect(copy._geo_id).to_not eq(subject._geo_id)
378
+ expect(copy._terraform_id).to_not eq(subject._terraform_id)
379
+ expect(copy._number).to_not eq(subject._number)
380
+ expect(copy._number).to eq("2")
381
+ end
382
+ end
383
+
328
384
  describe 'class method' do
329
385
  describe('#type_from_class_name') do
330
386
  it 'should return resource' do
@@ -12,7 +12,12 @@ describe GeoEngineer::Resources::AwsAlbListener do
12
12
  alb_client.stub_responses(
13
13
  :describe_load_balancers,
14
14
  {
15
- load_balancers: [{ load_balancer_arn: "foo/bar-baz" }]
15
+ load_balancers: [
16
+ {
17
+ load_balancer_arn: "foo/bar-baz",
18
+ load_balancer_name: "foo-bar-baz"
19
+ }
20
+ ]
16
21
  }
17
22
  )
18
23
  alb_client.stub_responses(
@@ -29,5 +29,12 @@ describe GeoEngineer::Resources::AwsAlb do
29
29
  remote_resources = GeoEngineer::Resources::AwsAlb._fetch_remote_resources(nil)
30
30
  expect(remote_resources.length).to eq 1
31
31
  end
32
+
33
+ it "should work if no ALB's exist" do
34
+ alb_client.stub_responses(:describe_load_balancers, { load_balancers: [] })
35
+
36
+ remote_resources = GeoEngineer::Resources::AwsAlb._fetch_remote_resources(nil)
37
+ expect(remote_resources.length).to eq 0
38
+ end
32
39
  end
33
40
  end
@@ -28,8 +28,30 @@ describe GeoEngineer::Resources::AwsAlbTargetGroup do
28
28
  ]
29
29
  }
30
30
  )
31
+ alb_client.stub_responses(
32
+ :describe_tags,
33
+ {
34
+ tag_descriptions: [
35
+ {
36
+ resource_arn: "targetgroup/foo/bar-baz",
37
+ tags: [{ key: "Name", value: "foo/bar-baz" }]
38
+ },
39
+ {
40
+ resource_arn: "targetgroup/foo/test-test",
41
+ tags: [{ key: "Name", value: "foo/test-test" }]
42
+ }
43
+ ]
44
+ }
45
+ )
31
46
  remote_resources = GeoEngineer::Resources::AwsAlbTargetGroup._fetch_remote_resources(nil)
32
47
  expect(remote_resources.length).to eq 2
33
48
  end
49
+
50
+ it "should work if no ALB's exist" do
51
+ alb_client.stub_responses(:describe_target_groups, { target_groups: [] })
52
+
53
+ remote_resources = GeoEngineer::Resources::AwsAlbTargetGroup._fetch_remote_resources(nil)
54
+ expect(remote_resources.length).to eq 0
55
+ end
34
56
  end
35
57
  end