geoengineer 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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