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,44 @@
1
+ ###############################################################################################
2
+ # AwsElasticacheReplicationGroup is the +aws_elasticache_replication_group+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/elasticache_replication_group.html Terraform Docs}
5
+ ##################################################################################################
6
+ class GeoEngineer::Resources::AwsElasticacheReplicationGroup < GeoEngineer::Resource
7
+ validate -> {
8
+ validate_required_attributes(
9
+ [
10
+ :replication_group_id,
11
+ :replication_group_description,
12
+ :number_cache_clusters,
13
+ :node_type,
14
+ :port
15
+ ]
16
+ )
17
+ }
18
+
19
+ after :initialize, -> { _terraform_id -> { replication_group_id } }
20
+
21
+ def to_terraform_state
22
+ tfstate = super
23
+
24
+ attributes = {}
25
+
26
+ # Workaround for availability zones
27
+ availability_zones.each_with_index do |az, i|
28
+ attributes["availability_zones.#{i}"] = az
29
+ end
30
+ attributes['availability_zones.#'] = availability_zones.count.to_s
31
+
32
+ tfstate[:primary][:attributes] = attributes
33
+ tfstate
34
+ end
35
+
36
+ def self._fetch_remote_resources
37
+ ec = AwsClients.elasticache
38
+ ec.describe_replication_groups['replication_groups'].map(&:to_h).map do |rg|
39
+ rg[:_terraform_id] = rg[:replication_group_id]
40
+ rg[:_geo_id] = rg[:replication_group_id]
41
+ rg
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ ###############################################################################################
2
+ # AwsElasticacheSubnetGroup is the +aws_elasticache_subnet_group+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/elasticache_subnet_group.html Terraform Docs}
5
+ ##################################################################################################
6
+ class GeoEngineer::Resources::AwsElasticacheSubnetGroup < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name, :subnet_ids]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { name } }
10
+
11
+ def support_tags?
12
+ false
13
+ end
14
+
15
+ def self._fetch_remote_resources
16
+ cache_subnet_groups = AwsClients.elasticache.describe_cache_subnet_groups
17
+
18
+ cache_subnet_groups['cache_subnet_groups'].map(&:to_h).map do |csg|
19
+ csg[:name] = csg[:cache_subnet_group_name]
20
+ csg[:_terraform_id] = csg[:cache_subnet_group_name]
21
+ csg[:_geo_id] = csg[:cache_subnet_group_name]
22
+ csg
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ ########################################################################
2
+ # AwsElasticsearchDomain is the +aws_elasticsearch_domain+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/elasticsearch_domain.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsElasticsearchDomain < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:domain_name]) }
8
+
9
+ after :initialize, -> {
10
+ _terraform_id -> {
11
+ "arn:aws:es:#{environment.region}:#{environment.account_id}:domain/#{self.domain_name}"
12
+ }
13
+ }
14
+ after :initialize, -> { _geo_id -> { domain_name } }
15
+
16
+ def to_terraform_state
17
+ tfstate = super
18
+ tfstate[:primary][:attributes] = {
19
+ 'domain_name' => domain_name,
20
+ 'access_policies' => access_policies
21
+ }
22
+ tfstate
23
+ end
24
+
25
+ def short_type
26
+ "es"
27
+ end
28
+
29
+ def self._fetch_remote_resources
30
+ AwsClients.elasticsearch.list_domain_names['domain_names'].map(&:to_h).map do |esd|
31
+ esd[:_geo_id] = esd[:domain_name]
32
+ esd
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,57 @@
1
+ ########################################################################
2
+ # AwsElb is the +aws_elb+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/elb.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsElb < GeoEngineer::Resource
7
+ validate :validate_unique_lb_ports
8
+ validate -> { validate_required_attributes([:name]) }
9
+ validate -> { validate_subresource_required_attributes(:access_logs, [:bucket]) }
10
+ validate -> {
11
+ validate_subresource_required_attributes(
12
+ :listener,
13
+ [
14
+ :instance_port,
15
+ :instance_protocol,
16
+ :lb_port,
17
+ :lb_protocol
18
+ ]
19
+ )
20
+ }
21
+ validate -> {
22
+ validate_subresource_required_attributes(
23
+ :health_check,
24
+ [
25
+ :healthy_threshold, :unhealthy_threshold, :target, :interval, :timeout
26
+ ]
27
+ )
28
+ }
29
+ validate -> { validate_required_subresource(:listener) }
30
+
31
+ after :initialize, -> { _terraform_id -> { name } }
32
+
33
+ def validate_unique_lb_ports
34
+ errors = []
35
+ ports = []
36
+ self.all_listener.each do |listener|
37
+ if ports.include? listener.lb_port
38
+ errors << "AwsElb non-unique listener lb_port #{for_resource}"
39
+ end
40
+ ports << listener.lb_port
41
+ end
42
+ errors
43
+ end
44
+
45
+ def short_type
46
+ "elb"
47
+ end
48
+
49
+ def self._fetch_remote_resources
50
+ AwsClients.elb.describe_load_balancers['load_balancer_descriptions'].map(&:to_h).map do |elb|
51
+ elb[:_terraform_id] = elb[:load_balancer_name]
52
+ elb[:_geo_id] = elb[:load_balancer_name]
53
+ elb[:name] = elb[:load_balancer_name]
54
+ elb
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,53 @@
1
+ ########################################################################
2
+ # AwsIamPolicy +aws_iam_policy+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/iam_policy.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsIamPolicy < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name, :policy]) }
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
+ arn = NullObject.maybe(remote_resource).arn
18
+ default_version_id = NullObject.maybe(remote_resource).default_version_id
19
+ policy = _get_policy_document(arn, default_version_id)
20
+
21
+ tfstate = super
22
+ tfstate[:primary][:attributes] = {
23
+ 'policy' => policy
24
+ }
25
+ tfstate
26
+ end
27
+
28
+ def support_tags?
29
+ false
30
+ end
31
+
32
+ def _get_policy_document(arn, version_id)
33
+ response = AwsClients.iam.get_policy_version({ policy_arn: arn,
34
+ version_id: version_id })
35
+ URI.decode(response.policy_version.document)
36
+ end
37
+
38
+ def self._all_remote_policies
39
+ AwsClients.iam.list_policies.each.map(&:policies).flatten.map(&:to_h)
40
+ end
41
+
42
+ def self._fetch_remote_resources
43
+ _all_remote_policies.map(&:to_h).map do |policy|
44
+ {
45
+ '_terraform_id' => policy[:arn],
46
+ '_geo_id' => policy[:policy_name],
47
+ 'arn' => policy[:arn],
48
+ 'default_version_id' => policy[:default_version_id],
49
+ 'name' => policy[:policy_name]
50
+ }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ ########################################################################
2
+ # AwsIamUser +aws_iam_user+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/iam_user.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsIamUser < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([: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
+ 'force_destroy' => (force_destroy || 'false')
21
+ }
22
+ tfstate
23
+ end
24
+
25
+ def support_tags?
26
+ false
27
+ end
28
+
29
+ def self._all_remote_users
30
+ AwsClients.iam.list_users.each.map(&:users).flatten.map(&:to_h)
31
+ end
32
+
33
+ def self._fetch_remote_resources
34
+ _all_remote_users.map do |user|
35
+ {
36
+ '_terraform_id' => user[:user_name],
37
+ '_geo_id' => user[:user_name],
38
+ 'name' => user[:user_name]
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ ########################################################################
2
+ # AwsInstance is the +aws_db_parameter_group+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/db_parameter_group.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsInstance < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:ami, :instance_type]) }
8
+ validate -> { validate_has_tag(:Name) }
9
+
10
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
11
+ after :initialize, -> { _geo_id -> { NullObject.maybe(tags)[:Name] } }
12
+
13
+ def self._all_remote_instances
14
+ AwsClients.ec2.describe_instances.reservations.map(&:instances).flatten.map(&:to_h)
15
+ end
16
+
17
+ def self._fetch_remote_resources
18
+ _all_remote_instances.map do |i|
19
+ i[:_terraform_id] = i[:instance_id]
20
+ i[:_geo_id] = i[:tags] ? i[:tags].select { |x| x[:key] == "Name" }.first[:value] : nil
21
+ i
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ ########################################################################
2
+ # AwsProxyProtocolPolicy is the +aws_proxy_protocol_policy+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/proxy_protocol_policy.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsProxyProtocolPolicy < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:load_balancer, :instance_ports]) }
8
+
9
+ # _terraform_id is the ELB, and the name of the policy, i.e. "TFEnableProxyProtocol"
10
+ after :initialize, -> {
11
+ _terraform_id -> { "#{NullObject.maybe(load_balancer)._terraform_id}:TFEnableProxyProtocol" }
12
+ }
13
+
14
+ # The loadbalancer and the instance ports are necessary in the terraform state for the policy
15
+ def to_terraform_state
16
+ tfstate = super
17
+ tfstate[:primary][:attributes] = {
18
+ 'load_balancer' => load_balancer._terraform_id,
19
+ 'instance_ports.#' => instance_ports.length.to_s
20
+ }
21
+ instance_ports.each_with_index { |ip, i|
22
+ tfstate[:primary][:attributes]["instance_ports.#{i}"] = ip
23
+ }
24
+ tfstate
25
+ end
26
+
27
+ def support_tags?
28
+ false
29
+ end
30
+
31
+ def short_type
32
+ "elbppp"
33
+ end
34
+
35
+ # This is a weird resource and it is not listed
36
+ def self._fetch_remote_resources
37
+ []
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ ########################################################################
2
+ # AwsRedshiftCluster is the +aws_redshift_cluster+ terrform resource,
3
+ #
4
+ # Terraform Docs
5
+ # {https://www.terraform.io/docs/providers/aws/r/redshift_cluster.html#cluster_version}
6
+ ########################################################################
7
+ class GeoEngineer::Resources::AwsRedshiftCluster < GeoEngineer::Resource
8
+ validate -> {
9
+ validate_required_attributes(
10
+ [:cluster_identifier, :node_type, :master_password, :master_username]
11
+ )
12
+ }
13
+ validate -> {
14
+ validate_required_attributes([:number_of_nodes]) if self.cluster_type == 'multi-node'
15
+ }
16
+ validate -> { validate_required_attributes([:bucket_name]) if self.enable_logging }
17
+
18
+ after :initialize, -> { _terraform_id -> { cluster_identifier } }
19
+
20
+ def self._fetch_remote_resources
21
+ AwsClients.redshift.describe_clusters.clusters.map(&:to_h)
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ ########################################################################
2
+ # AwsRoute53Record is the +aws_route53_record+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/route53_record.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsRoute53Record < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:zone_id, :name, :type]) }
8
+ validate -> { validate_required_attributes([:ttl, :records]) unless self.alias }
9
+
10
+ validate -> {
11
+ if self.alias
12
+ validate_subresource_required_attributes(:alias, [:name, :zone_id, :evaluate_target_health])
13
+ end
14
+ }
15
+
16
+ after :initialize, -> { _terraform_id -> { "#{zone_id}_#{name}_#{type}" } }
17
+
18
+ def self._fetch_remote_resources
19
+ _fetch_zones.map { |zone| _fetch_records_for_zone(zone) }.flatten.compact
20
+ end
21
+
22
+ def self._fetch_zones
23
+ AwsClients.route53.list_hosted_zones.hosted_zones.map(&:to_h)
24
+ end
25
+
26
+ def self._fetch_records_for_zone(zone)
27
+ records = AwsClients.route53.list_resource_record_sets({ hosted_zone_id: zone[:id] })
28
+ records.resource_record_sets.map(&:to_h).map do |record|
29
+ record.merge({ _terraform_id: "#{record[:zone_id]}_#{record[:name]}_#{record[:type]}" })
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ ########################################################################
2
+ # AwsRoute53Zone is the +aws_route53_zone+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/route53_zone.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsRoute53Zone < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:name]) }
8
+
9
+ after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
10
+ after :initialize, -> { _geo_id -> { self.name } }
11
+
12
+ def self._fetch_remote_resources
13
+ hosted_zones = AwsClients.route53.list_hosted_zones.hosted_zones.map(&:to_h)
14
+
15
+ hosted_zones.map do |zone|
16
+ zone[:_terraform_id] = zone[:id]
17
+ zone[:_geo_id] = zone[:name]
18
+ zone
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ ########################################################################
2
+ # AwsS3Bucket is the +aws_s3_bucket+ terrform resource,
3
+ #
4
+ # {https://www.terraform.io/docs/providers/aws/r/s3_bucket.html Terraform Docs}
5
+ ########################################################################
6
+ class GeoEngineer::Resources::AwsS3Bucket < GeoEngineer::Resource
7
+ validate -> { validate_required_attributes([:bucket]) }
8
+ validate :validate_policy_json
9
+
10
+ after :initialize, -> { _terraform_id -> { bucket } }
11
+ before :validation, -> {
12
+ # AWS/Terraform reorganises and adds information to the policy that affects it
13
+ # This adds that information to ensure unnecessary change
14
+ if policy && validate_policy_json.nil?
15
+ old_policy = self.policy
16
+ policy_hash = JSON.parse(old_policy)
17
+ # First each statement must have Sid
18
+ (policy_hash["Statement"] || []).each { |s| s["Sid"] = s["Sid"] || "" }
19
+
20
+ self.policy = policy_hash.to_json
21
+ end
22
+ }
23
+
24
+ def validate_policy_json
25
+ return unless policy
26
+ JSON.parse(policy)
27
+ return nil
28
+ rescue JSON::ParserError
29
+ return "Error: policy #{for_resource} invalid JSON"
30
+ end
31
+
32
+ def to_terraform_state
33
+ tfstate = super
34
+ tfstate[:primary][:attributes] = {
35
+ 'acl' => (acl || 'private'),
36
+ 'force_destroy' => (force_destroy || 'false'),
37
+ 'policy' => policy
38
+ }
39
+ tfstate
40
+ end
41
+
42
+ def short_type
43
+ "s3"
44
+ end
45
+
46
+ def self._fetch_remote_resources
47
+ AwsClients.s3.list_buckets[:buckets].map(&:to_h).map do |s3b|
48
+ s3b[:_terraform_id] = s3b[:name]
49
+ s3b[:_geo_id] = s3b[:name]
50
+ s3b[:bucket] = s3b[:name]
51
+ s3b
52
+ end
53
+ end
54
+ end