geoengineer 0.1.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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +476 -0
  4. data/bin/geo +7 -0
  5. data/lib/geoengineer.rb +29 -0
  6. data/lib/geoengineer/cli/geo_cli.rb +208 -0
  7. data/lib/geoengineer/cli/status_command.rb +101 -0
  8. data/lib/geoengineer/cli/terraform_commands.rb +59 -0
  9. data/lib/geoengineer/environment.rb +186 -0
  10. data/lib/geoengineer/output.rb +27 -0
  11. data/lib/geoengineer/project.rb +79 -0
  12. data/lib/geoengineer/resource.rb +200 -0
  13. data/lib/geoengineer/resources/aws_db_instance.rb +46 -0
  14. data/lib/geoengineer/resources/aws_db_parameter_group.rb +25 -0
  15. data/lib/geoengineer/resources/aws_elasticache_cluster.rb +50 -0
  16. data/lib/geoengineer/resources/aws_elasticache_parameter_group.rb +30 -0
  17. data/lib/geoengineer/resources/aws_elasticache_replication_group.rb +44 -0
  18. data/lib/geoengineer/resources/aws_elasticache_subnet_group.rb +25 -0
  19. data/lib/geoengineer/resources/aws_elasticsearch_domain.rb +35 -0
  20. data/lib/geoengineer/resources/aws_elb.rb +57 -0
  21. data/lib/geoengineer/resources/aws_iam_policy.rb +53 -0
  22. data/lib/geoengineer/resources/aws_iam_user.rb +42 -0
  23. data/lib/geoengineer/resources/aws_instance.rb +24 -0
  24. data/lib/geoengineer/resources/aws_proxy_protocol_policy.rb +39 -0
  25. data/lib/geoengineer/resources/aws_redshift_cluster.rb +23 -0
  26. data/lib/geoengineer/resources/aws_route53_record.rb +32 -0
  27. data/lib/geoengineer/resources/aws_route53_zone.rb +21 -0
  28. data/lib/geoengineer/resources/aws_s3_bucket.rb +54 -0
  29. data/lib/geoengineer/resources/aws_security_group.rb +53 -0
  30. data/lib/geoengineer/resources/aws_ses_receipt_rule.rb +38 -0
  31. data/lib/geoengineer/resources/aws_ses_receipt_rule_set.rb +28 -0
  32. data/lib/geoengineer/resources/aws_sns_topic.rb +28 -0
  33. data/lib/geoengineer/resources/aws_sns_topic_subscription.rb +42 -0
  34. data/lib/geoengineer/resources/aws_sqs_queue.rb +37 -0
  35. data/lib/geoengineer/resources/iam/statement.rb +43 -0
  36. data/lib/geoengineer/sub_resource.rb +35 -0
  37. data/lib/geoengineer/template.rb +28 -0
  38. data/lib/geoengineer/utils/aws_clients.rb +63 -0
  39. data/lib/geoengineer/utils/has_attributes.rb +97 -0
  40. data/lib/geoengineer/utils/has_lifecycle.rb +54 -0
  41. data/lib/geoengineer/utils/has_resources.rb +63 -0
  42. data/lib/geoengineer/utils/has_sub_resources.rb +43 -0
  43. data/lib/geoengineer/utils/has_validations.rb +57 -0
  44. data/lib/geoengineer/utils/null_object.rb +17 -0
  45. data/lib/geoengineer/version.rb +3 -0
  46. data/spec/environment_spec.rb +140 -0
  47. data/spec/output_spec.rb +3 -0
  48. data/spec/project_spec.rb +79 -0
  49. data/spec/resource_spec.rb +169 -0
  50. data/spec/resources/aws_db_instance_spec.rb +23 -0
  51. data/spec/resources/aws_db_parameter_group_spec.rb +23 -0
  52. data/spec/resources/aws_elasticache_replication_group_spec.rb +29 -0
  53. data/spec/resources/aws_elasticache_subnet_group_spec.rb +31 -0
  54. data/spec/resources/aws_elasticcache_cluster_spec.rb +23 -0
  55. data/spec/resources/aws_elasticcache_parameter_group_spec.rb +26 -0
  56. data/spec/resources/aws_elasticsearch_domain_spec.rb +22 -0
  57. data/spec/resources/aws_elb_spec.rb +65 -0
  58. data/spec/resources/aws_iam_policy.rb +35 -0
  59. data/spec/resources/aws_iam_user.rb +35 -0
  60. data/spec/resources/aws_instance_spec.rb +26 -0
  61. data/spec/resources/aws_proxy_protocol_policy_spec.rb +5 -0
  62. data/spec/resources/aws_redshift_cluster_spec.rb +25 -0
  63. data/spec/resources/aws_route53_record_spec.rb +41 -0
  64. data/spec/resources/aws_route53_zone_spec.rb +34 -0
  65. data/spec/resources/aws_s3_bucket_spec.rb +39 -0
  66. data/spec/resources/aws_security_group_spec.rb +80 -0
  67. data/spec/resources/aws_ses_receipt_rule.rb +33 -0
  68. data/spec/resources/aws_ses_receipt_rule_set.rb +25 -0
  69. data/spec/resources/aws_sns_topic_spec.rb +23 -0
  70. data/spec/resources/aws_sns_topic_subscription.rb +35 -0
  71. data/spec/resources/aws_sqs_queue_spec.rb +20 -0
  72. data/spec/rubocop_spec.rb +13 -0
  73. data/spec/spec_helper.rb +71 -0
  74. data/spec/sub_resource_spec.rb +20 -0
  75. data/spec/template_spec.rb +39 -0
  76. data/spec/utils/has_attributes_spec.rb +118 -0
  77. data/spec/utils/has_lifecycle_spec.rb +24 -0
  78. data/spec/utils/has_resources_spec.rb +68 -0
  79. data/spec/utils/has_subresources_spec.rb +29 -0
  80. data/spec/utils/has_validations_spec.rb +35 -0
  81. data/spec/utils/null_object_spec.rb +18 -0
  82. metadata +303 -0
@@ -0,0 +1,53 @@
1
+ ########################################################################
2
+ # AwsSecurityGroup is the +aws_security_group+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/security_group.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsSecurityGroup < GeoEngineer::Resource
7
+ validate :validate_correct_cidr_blocks
8
+ validate -> { validate_required_attributes([:name, :description]) }
9
+ validate -> {
10
+ validate_subresource_required_attributes(:ingress, [:from_port, :protocol, :to_port])
11
+ }
12
+ validate -> {
13
+ validate_subresource_required_attributes(:egress, [:from_port, :protocol, :to_port])
14
+ }
15
+ validate -> { validate_has_tag(:Name) }
16
+
17
+ before :validation, -> { flatten_cidr_and_sg_blocks }
18
+
19
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
20
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
21
+
22
+ def flatten_cidr_and_sg_blocks
23
+ (self.all_ingress + self.all_egress).each do |in_eg|
24
+ in_eg.cidr_blocks = in_eg.cidr_blocks.flatten if in_eg.cidr_blocks
25
+ in_eg.security_groups = in_eg.security_groups.flatten if in_eg.security_groups
26
+ end
27
+ end
28
+
29
+ def validate_correct_cidr_blocks
30
+ errors = []
31
+ (self.all_ingress + self.all_egress).each do |in_eg|
32
+ next unless in_eg.cidr_blocks
33
+ in_eg.cidr_blocks.each do |cidr|
34
+ _, error = validate_cidr_block(cidr)
35
+ errors << error unless error.nil?
36
+ end
37
+ end
38
+ errors
39
+ end
40
+
41
+ def short_type
42
+ "sg"
43
+ end
44
+
45
+ def self._fetch_remote_resources
46
+ AwsClients.ec2.describe_security_groups['security_groups'].map(&:to_h).map do |sg|
47
+ sg[:name] = sg[:group_name]
48
+ sg[:_terraform_id] = sg[:group_id]
49
+ sg[:_geo_id] = sg[:tags] ? sg[:tags].select { |x| x[:key] == "Name" }.first[:value] : nil
50
+ sg
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ ########################################################################
2
+ # AwsSesReceiptRule is the +ses_receipt_rule+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/ses_receipt_rule.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsSesReceiptRule < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name, :rule_set_name]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
11
+ }
12
+ after :initialize, -> {
13
+ _geo_id -> { name.to_s }
14
+ }
15
+
16
+ def to_terraform_state
17
+ tfstate = super
18
+ tfstate[:primary][:attributes] = {
19
+ 'name' => name,
20
+ 'rule_set_name' => rule_set_name,
21
+ 'enabled' => (enabled || 'false')
22
+ }
23
+ tfstate
24
+ end
25
+
26
+ def support_tags?
27
+ false
28
+ end
29
+
30
+ def self._fetch_remote_resources
31
+ AwsClients.ses.describe_active_receipt_rule_set.rules.map(&:to_h).map do |rule|
32
+ {
33
+ '_terraform_id' => rule[:name],
34
+ '_geo_id' => rule[:name]
35
+ }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ ########################################################################
2
+ # AwsSesReceiptRuleSet is the +ses_receipt_rule_set+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/ses_receipt_rule_set.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsSesReceiptRuleSet < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:rule_set_name]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
11
+ }
12
+ after :initialize, -> {
13
+ _geo_id -> { rule_set_name.to_s }
14
+ }
15
+
16
+ def support_tags?
17
+ false
18
+ end
19
+
20
+ def self._fetch_remote_resources
21
+ AwsClients.ses.list_receipt_rule_sets.rule_sets.map(&:to_h).map do |rule_set|
22
+ {
23
+ '_terraform_id' => rule_set[:name],
24
+ '_geo_id' => rule_set[:name]
25
+ }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ ########################################################################
2
+ # AwsSnsTopic is the +aws_sns_topic+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/sns_topic.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsSnsTopic < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> {
11
+ "arn:aws:sns:#{environment.region}:#{environment.account_id}:#{name}"
12
+ }
13
+ }
14
+
15
+ def support_tags?
16
+ false
17
+ end
18
+
19
+ def self._fetch_remote_resources
20
+ AwsClients.sns.list_topics.topics.map(&:to_h).map do |topic|
21
+ {
22
+ '_terraform_id' => topic[:topic_arn],
23
+ '_geo_id' => topic[:topic_arn],
24
+ 'name' => topic[:topic_arn].split(':').last
25
+ }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ ########################################################################
2
+ # AwsSnsSubscription is the +sns_topic_subscription+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/sns_topic_subscription.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsSnsTopicSubscription < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:protocol, :topic_arn, :endpoint]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
11
+ }
12
+ after :initialize, -> {
13
+ _geo_id -> { "#{topic_arn}::#{protocol}::#{endpoint}" }
14
+ }
15
+
16
+ def to_terraform_state
17
+ tfstate = super
18
+ tfstate[:primary][:attributes] = {
19
+ 'topic_arn' => topic_arn,
20
+ 'endpoint' => endpoint,
21
+ 'protocol' => protocol,
22
+ 'confirmation_timeout_in_minutes' => "1",
23
+ 'endpoint_auto_confirms' => "false"
24
+ }
25
+ tfstate
26
+ end
27
+
28
+ def support_tags?
29
+ false
30
+ end
31
+
32
+ def self._fetch_remote_resources
33
+ AwsClients.sns.list_subscriptions.subscriptions.map(&:to_h).map do |subscription|
34
+ {
35
+ '_terraform_id' => subscription[:subscription_arn],
36
+ '_geo_id' => "#{subscription[:topic_arn]}::" \
37
+ "#{subscription[:protocol]}::" \
38
+ "#{subscription[:endpoint]}"
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,37 @@
1
+ ########################################################################
2
+ # AwsSqsQueue is the +aws_sqs_queue+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/sqs_queue.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsSqsQueue < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> {
11
+ "https://sqs.#{environment.region}.amazonaws.com/#{environment.account_id}/#{name}"
12
+ }
13
+ }
14
+
15
+ # The loadbalancer and the instance ports are necessary in the terraform state for the policy
16
+ def to_terraform_state
17
+ tfstate = super
18
+ tfstate[:primary][:attributes] = {
19
+ 'name' => name
20
+ }
21
+ tfstate
22
+ end
23
+
24
+ def support_tags?
25
+ false
26
+ end
27
+
28
+ def self._fetch_remote_resources
29
+ AwsClients.sqs.list_queues['queue_urls'].map do |queue|
30
+ {
31
+ '_terraform_id' => queue,
32
+ '_geo_id' => queue,
33
+ 'name' => URI.parse(queue).path.split('/').last
34
+ }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ module GeoEngineer::IAM
2
+ ########################################################################
3
+ # A Statement object is a single iam policy statement with a Sid,
4
+ # effect, action, and condition. Used to assist validating IAM policies.
5
+ ########################################################################
6
+ class Statement
7
+ attr_reader :sid, :action, :effect
8
+
9
+ def initialize(raw)
10
+ @action = raw["Action"]
11
+ @effect = raw["Effect"]
12
+ @raw = raw
13
+ @sid = raw["Sid"]
14
+ end
15
+
16
+ def secure_transport?
17
+ secure_transport = @raw.dig('Condition', 'Bool', 'aws:SecureTransport')
18
+ secure_transport == "true"
19
+ end
20
+
21
+ def ip_restrictions
22
+ cidr_blocks = []
23
+ cidr_blocks << @raw.dig('Condition', 'IpAddress', 'aws:SourceIP')
24
+ cidr_blocks << @raw.dig('Condition', 'IpAddressIfExists', 'aws:SourceIP')
25
+ cidr_blocks.flatten.compact
26
+ end
27
+
28
+ def ip_restriction_exists?
29
+ return true unless ip_restrictions.empty?
30
+ end
31
+
32
+ def vpc_restrictions
33
+ vpcs = []
34
+ vpcs << @raw.dig('Condition', 'StringEqualsifExists', 'aws:sourceVpce')
35
+ vpcs << @raw.dig('Condition', 'ForAnyValue:StringEquals', 'aws:sourceVpce')
36
+ vpcs.flatten.compact
37
+ end
38
+
39
+ def vpc_restriction_exists?
40
+ return true unless vpc_restrictions.empty?
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,35 @@
1
+ ########################################################################
2
+ # SubResources are included in resources with their own rules
3
+ #
4
+ # For example, +ingress+ in +aws_security_group+ is a subresource.
5
+ #
6
+ # A SubResource can have arbitrary attributes
7
+ ########################################################################
8
+ class GeoEngineer::SubResource
9
+ include HasAttributes
10
+
11
+ attr_reader :type
12
+
13
+ def initialize(resource, type, &block)
14
+ @resource = resource
15
+ @type = type.to_s
16
+ instance_exec(self, &block) if block_given?
17
+ end
18
+
19
+ def _terraform_id
20
+ @resource._terraform_id
21
+ end
22
+
23
+ def to_terraform
24
+ sb = [" #{@type} { "]
25
+ sb.concat terraform_attributes.map { |k, v|
26
+ " #{k.to_s.inspect} = #{v.inspect}"
27
+ }
28
+ sb << " }"
29
+ sb.join("\n")
30
+ end
31
+
32
+ def to_terraform_json
33
+ [@type, terraform_attributes]
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ ########################################################################
2
+ # Override to define recommended patterns of resource use
3
+ ########################################################################
4
+ class GeoEngineer::Template
5
+ include HasAttributes
6
+ include HasResources
7
+
8
+ def initialize(name, project, parameters = {})
9
+ @project = project
10
+ @name = name
11
+ @environment = @project.environment
12
+ end
13
+
14
+ def resource(type, id, &block)
15
+ return find_resource(type, id) unless block_given?
16
+ resource = create_resource(type, id, &block)
17
+ resource.template = self
18
+ resource.project = @project
19
+ resource.environment = @environment
20
+ resource
21
+ end
22
+
23
+ # The resources that are passed to the block on instantiation
24
+ # This can be overridden to specify the order of the templates resources
25
+ def template_resources
26
+ resources
27
+ end
28
+ end
@@ -0,0 +1,63 @@
1
+ ########################################################################
2
+ # AwsClients contains a list of aws-clients for use
3
+ # The main reason for their central management is their initialisation testing and stubbing
4
+ ########################################################################
5
+ class AwsClients
6
+ def self.stub!
7
+ @stub_aws = true
8
+ end
9
+
10
+ def self.stubbed?
11
+ @stub_aws || false
12
+ end
13
+
14
+ # Clients
15
+
16
+ def self.ec2
17
+ @aws_ec2 ||= Aws::EC2::Client.new({ stub_responses: stubbed? })
18
+ end
19
+
20
+ def self.elasticache
21
+ @aws_elasticache ||= Aws::ElastiCache::Client.new({ stub_responses: stubbed? })
22
+ end
23
+
24
+ def self.elasticsearch
25
+ @aws_elasticsearch ||= Aws::ElasticsearchService::Client.new({ stub_responses: stubbed? })
26
+ end
27
+
28
+ def self.elb
29
+ @aws_elb ||= Aws::ElasticLoadBalancing::Client.new({ stub_responses: stubbed? })
30
+ end
31
+
32
+ def self.iam
33
+ @aws_iam ||= Aws::IAM::Client.new({ stub_responses: stubbed? })
34
+ end
35
+
36
+ def self.rds
37
+ @aws_rds ||= Aws::RDS::Client.new({ stub_responses: stubbed? })
38
+ end
39
+
40
+ def self.redshift
41
+ @aws_redshift ||= Aws::Redshift::Client.new({ stub_responses: stubbed? })
42
+ end
43
+
44
+ def self.route53
45
+ @aws_route53 ||= Aws::Route53::Client.new({ stub_responses: stubbed? })
46
+ end
47
+
48
+ def self.s3
49
+ @aws_s3 ||= Aws::S3::Client.new({ stub_responses: stubbed? })
50
+ end
51
+
52
+ def self.ses
53
+ @aws_ses ||= Aws::SES::Client.new({ stub_responses: stubbed? })
54
+ end
55
+
56
+ def self.sns
57
+ @aws_sns ||= Aws::SNS::Client.new({ stub_responses: stubbed? })
58
+ end
59
+
60
+ def self.sqs
61
+ @aws_sqs ||= Aws::SQS::Client.new({ stub_responses: stubbed? })
62
+ end
63
+ end
@@ -0,0 +1,97 @@
1
+ ########################################################################
2
+ # HasAttributes allows objects to have arbitrary attributes associated with it.
3
+ ########################################################################
4
+ module HasAttributes
5
+ def attributes
6
+ @_attributes = {} unless @_attributes
7
+ @_attributes
8
+ end
9
+
10
+ def [](key)
11
+ retrieve_attribute(key.to_s)
12
+ end
13
+
14
+ def []=(key, val)
15
+ assign_attribute(key.to_s, [val])
16
+ end
17
+
18
+ def delete(key)
19
+ attributes.delete(key.to_s)
20
+ end
21
+
22
+ def assign_block(name, *args, &block)
23
+ throw "#{self.class.inspect} cannot handle block"
24
+ end
25
+
26
+ def assign_attribute(name, args)
27
+ # this is a setter
28
+ name = name[0...-1] if name.end_with?"="
29
+ val = args.length == 1 ? args[0] : args
30
+ attributes[name] = val
31
+ val
32
+ end
33
+
34
+ def retrieve_attribute(name)
35
+ # this is a getter
36
+ val = attributes[name]
37
+ val = attribute_missing(name) if val.nil?
38
+ if val.is_a? Proc
39
+ val = val.call()
40
+ # cache the value to override the Proc
41
+ attributes[name] = val
42
+ end
43
+ val
44
+ end
45
+
46
+ def attribute_missing(name)
47
+ nil
48
+ end
49
+
50
+ # This method allows attributes to be defined on an instance
51
+ # without explicitly defining which attributes are allowed
52
+ #
53
+ # The flow is:
54
+ # 1. If the method receives a block, it will pass it to the `assign_block` method to be handled.
55
+ # By default this method will throw an error, but can be overridden by the class.
56
+ # 2. If the method has one or more argument it will assume it is a setter
57
+ # and store the argument as an attribute.
58
+ # 3. If the method has no arguments it will assume it is a getter and retrieve the value.
59
+ # If the retrieved value is a `Proc` it will execute it and store the returned value,
60
+ # this will allow for caching expensive calls and only calling if requested
61
+ def method_missing(name, *args, &block)
62
+ name = name.to_s
63
+ if block_given?
64
+ # A class can override a
65
+ assign_block(name, *args, &block)
66
+ elsif args.length >= 1
67
+ assign_attribute(name, args)
68
+ elsif args.empty?
69
+ retrieve_attribute(name)
70
+ end
71
+ end
72
+
73
+ # must override because of Object#timeout
74
+ def timeout(*args)
75
+ method_missing(:timeout, *args)
76
+ end
77
+
78
+ # This method creates a set of attributes for terraform to consume
79
+ def terraform_attributes
80
+ attributes
81
+ .select { |k, v| !k.nil? && !k.start_with?('_') }
82
+ .map { |k, v| [k, terraform_attribute_ref(k)] }
83
+ .select { |k, v| !v.nil? }
84
+ .to_h
85
+ end
86
+
87
+ def terraform_attribute_ref(k)
88
+ v = retrieve_attribute(k)
89
+ if v.is_a? GeoEngineer::Resource # convert Resource to reference for terraform
90
+ v.to_ref
91
+ elsif v.is_a? Array # map resources if attribute is an array
92
+ v.map { |vi| vi.is_a?(GeoEngineer::Resource) ? vi.to_ref : vi }
93
+ else
94
+ v
95
+ end
96
+ end
97
+ end