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,54 @@
1
+ ########################################################################
2
+ # HasLifecycle provides methods to enable lifecycle hooks
3
+ ########################################################################
4
+ module HasLifecycle
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ # ClassMethods
10
+ module ClassMethods
11
+ def _init_validation_hash
12
+ {
13
+ after: {},
14
+ before: {}
15
+ }
16
+ end
17
+
18
+ def lifecycle_actions(stage, step)
19
+ all = []
20
+ # inherit lifecycle_actions
21
+ sclazz = self.superclass
22
+ all.concat(sclazz.lifecycle_actions(stage, step)) if sclazz.respond_to?(:lifecycle_actions)
23
+
24
+ # Add this lifecycle actions
25
+ la_exists = @_actions && @_actions[stage] && @_actions[stage][step]
26
+ all.concat(@_actions[stage][step]) if la_exists
27
+ all
28
+ end
29
+
30
+ # Currently only supporting after(:initialize)
31
+ def after(lifecycle_step, method_name_or_proc)
32
+ @_actions = _init_validation_hash unless @_actions
33
+ @_actions[:after][lifecycle_step] = [] unless @_actions[:after][lifecycle_step]
34
+ @_actions[:after][lifecycle_step] << method_name_or_proc
35
+ end
36
+
37
+ def before(lifecycle_step, method_name_or_proc)
38
+ @_actions = _init_validation_hash unless @_actions
39
+ @_actions[:before][lifecycle_step] = [] unless @_actions[:before][lifecycle_step]
40
+ @_actions[:before][lifecycle_step] << method_name_or_proc
41
+ end
42
+ end
43
+
44
+ # This method will return a list of errors if not valid, or nil
45
+ def execute_lifecycle(stage, step)
46
+ self.class.lifecycle_actions(stage, step).each do |actions|
47
+ if actions.is_a? Proc
48
+ self.instance_exec(&actions)
49
+ else
50
+ self.send(actions)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ ########################################################################
2
+ # HasResources provides methods for a class to contain and query a set of resources
3
+ ########################################################################
4
+ module HasResources
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ # ClassMethods
10
+ module ClassMethods
11
+ def get_resource_class_from_type(type)
12
+ c_name = type.split('_').collect(&:capitalize).join
13
+ c_name = "GeoEngineer::Resources::#{c_name}"
14
+ clazz = Object.const_defined?(c_name) ? Object.const_get(c_name) : GeoEngineer::Resource
15
+ clazz
16
+ end
17
+ end
18
+
19
+ def resources
20
+ @_resources = [] unless @_resources
21
+ @_resources
22
+ end
23
+
24
+ # Overridden By Project and Environment
25
+ def all_resources
26
+ resources
27
+ end
28
+
29
+ def find_resource(type, id)
30
+ all_resources.select { |r| r.type == type && r.id == id }.first
31
+ end
32
+
33
+ def find_resource_by_ref(ref)
34
+ return nil unless ref.start_with?("${")
35
+ ref = ref.delete('${').delete('}')
36
+ type, name = ref.split('.')
37
+ find_resource(type, name)
38
+ end
39
+
40
+ def resources_grouped_by
41
+ all_resources.each_with_object({}) do |r, c|
42
+ value = yield r
43
+ c[value] ||= []
44
+ c[value] << r
45
+ c
46
+ end
47
+ end
48
+
49
+ def resources_of_type(type)
50
+ all_resources.select { |r| r.type == type }
51
+ end
52
+
53
+ # Factory to create resource and attach
54
+ def create_resource(type, id, &block)
55
+ # This will look for a class that is defined by the type so it can override functionality
56
+ # For example, if `type='aws_security_group'` then the class would be `AwsSecurityGroup`
57
+ clazz = self.class.get_resource_class_from_type(type)
58
+
59
+ res = clazz.new(type, id, &block)
60
+ resources << res # Add the resource
61
+ res
62
+ end
63
+ end
@@ -0,0 +1,43 @@
1
+ ########################################################################
2
+ # HasSubResources provides methods for a object to contain subresources
3
+ ########################################################################
4
+ module HasSubResources
5
+ # This overrides assign_block from HasAttributes
6
+ # when a block is passed to an attribute it becomes a SubResource
7
+ def assign_block(name, *args, &block)
8
+ sr = GeoEngineer::SubResource.new(self, name, &block)
9
+ subresources << sr
10
+ sr
11
+ end
12
+
13
+ def attribute_missing(name)
14
+ all = false
15
+ if name.start_with?('all_')
16
+ name = name[4..-1]
17
+ all = true
18
+ end
19
+ srl = subresources.select { |s| s.type == name.to_s }
20
+
21
+ if srl.empty?
22
+ return [] if all
23
+ return nil
24
+ else
25
+ return srl if all
26
+ return srl.first
27
+ end
28
+ end
29
+
30
+ def subresources
31
+ @_subresources = [] unless @_subresources
32
+ @_subresources
33
+ end
34
+
35
+ def delete_subresources_where(&block)
36
+ # Only leave sub resources that dont
37
+ @_subresources = subresources.reject(&block)
38
+ end
39
+
40
+ def delete_all_subresources(type)
41
+ @_subresources = subresources.select { |s| s.type != type.to_s }
42
+ end
43
+ end
@@ -0,0 +1,57 @@
1
+ require 'netaddr'
2
+
3
+ ########################################################################
4
+ # HasValidations provides methods to enable validations
5
+ ########################################################################
6
+ module HasValidations
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ # ClassMethods
12
+ module ClassMethods
13
+ def validations
14
+ all_validations = []
15
+ all_validations.concat(@_validations) if @_validations
16
+ # inherit validations
17
+ sclazz = self.superclass
18
+ all_validations.concat(sclazz.validations) if sclazz.respond_to?(:validations)
19
+ all_validations
20
+ end
21
+
22
+ def validate(method_name_or_proc)
23
+ @_validations = [] unless @_validations
24
+ @_validations << method_name_or_proc
25
+ end
26
+ end
27
+
28
+ # This method will return a list of errors if not valid, or nil
29
+ def errors
30
+ execute_lifecycle(:before, :validation) if self.respond_to? :execute_lifecycle
31
+ errs = []
32
+ self.class.validations.each do |validation|
33
+ errs << (validation.is_a?(Proc) ? self.instance_exec(&validation) : self.send(validation))
34
+ end
35
+ # remove nils
36
+ errs = errs.flatten.select { |x| !x.nil? }
37
+ errs
38
+ end
39
+
40
+ # Validation Helper Methods
41
+ def validate_required_attributes(keys)
42
+ errs = []
43
+ keys.each do |key|
44
+ errs << "#{key} attribute nil for #{self}" if self[key].nil?
45
+ end
46
+ errs
47
+ end
48
+
49
+ # Validates CIDR block format
50
+ # Returns error when argument fails validation
51
+ def validate_cidr_block(cidr_block)
52
+ parsed_cidr = NetAddr::CIDR.create(cidr_block)
53
+ return [parsed_cidr, nil]
54
+ rescue NetAddr::ValidationError
55
+ return [nil, "Bad cidr block \"#{cidr_block}\" #{for_resource}"]
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ ########################################################################
2
+ # NullObject and NullObject.maybe provide the NullObject pattern
3
+ # as defined {http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/ here}
4
+ #
5
+ ########################################################################
6
+ class NullObject
7
+ def method_missing(name, *args, &block)
8
+ nil
9
+ end
10
+
11
+ def self.maybe(value)
12
+ case value
13
+ when nil then NullObject.new
14
+ else value
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module GeoEngineer
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,140 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe("GeoEngineer::Environment") do
4
+ describe 'validations' do
5
+ it 'should have unique terrform id' do
6
+ env = GeoEngineer::Environment.new("test") {
7
+ region "us-west-1"
8
+ account_id 1
9
+ }
10
+ env.resource('type', 'id1') {
11
+ _terraform_id "tid"
12
+ _geo_id 'gid1'
13
+ }
14
+ env.resource('type', 'id2') {
15
+ _terraform_id "tid"
16
+ _geo_id 'gid2'
17
+ }
18
+ expect(env.all_resources.length).to eq 2
19
+ expect(env.errors.length).to eq 1
20
+ end
21
+
22
+ it 'should have unique geo_id' do
23
+ env = GeoEngineer::Environment.new("test") {
24
+ region "us-west-1"
25
+ account_id 1
26
+ }
27
+ env.resource('type', 'id1') {
28
+ _terraform_id "tid1"
29
+ _geo_id 'gid'
30
+ }
31
+ env.resource('type', 'id2') {
32
+ _terraform_id "tid2"
33
+ _geo_id 'gid'
34
+ }
35
+ expect(env.all_resources.length).to eq 2
36
+ expect(env.errors.length).to eq 1
37
+ end
38
+
39
+ it 'should have unique type and ids' do
40
+ env = GeoEngineer::Environment.new("test") {
41
+ region "us-west-1"
42
+ account_id 1
43
+ }
44
+ env.resource('type', 'id1') {
45
+ _terraform_id "tid1"
46
+ _geo_id 'gid1'
47
+ }
48
+ env.resource('type', 'id1') {
49
+ _terraform_id "tid2"
50
+ _geo_id 'gid2'
51
+ }
52
+ expect(env.all_resources.length).to eq 2
53
+ expect(env.errors.length).to eq 1
54
+ end
55
+ end
56
+
57
+ describe '#codifies_resources' do
58
+ it 'should return both codified and uncodified resources' do
59
+ class GeoEngineer::Resources::CodifiedResource < GeoEngineer::Resource
60
+ def self._fetch_remote_resources
61
+ [{ _geo_id: "geo_id1" }, { _geo_id: "geo_id2" }]
62
+ end
63
+ end
64
+
65
+ env = GeoEngineer::Environment.new("test")
66
+ env.resource('codified_resource', 'id1') {
67
+ _terraform_id "geo_id1"
68
+ }
69
+ expect(env.codified_resources('codified_resource').length).to eq 1
70
+ expect(env.uncodified_resources('codified_resource').length).to eq 1
71
+ end
72
+ end
73
+
74
+ describe '#to_terraform_state' do
75
+ it 'should return state of resources' do
76
+ env = GeoEngineer::Environment.new("test")
77
+ env.resource('type', 'id1') {
78
+ _terraform_id "tid"
79
+ _geo_id 'gid1'
80
+ }
81
+ tfstate = env.to_terraform_state
82
+ expect(tfstate[:modules].first[:resources].length).to eq 1
83
+ end
84
+ end
85
+
86
+ describe '#to_terraform_json' do
87
+ it 'should return terraform of all resources' do
88
+ env = GeoEngineer::Environment.new("test")
89
+ env.resource('type', 'id1') {
90
+ _terraform_id "tid"
91
+ _geo_id 'gid1'
92
+ }
93
+ tfjson = env.to_terraform_json
94
+ expect(tfjson[:resource].length).to eq 1
95
+ end
96
+ end
97
+
98
+ describe '#project' do
99
+ it 'should create a project with this as environment' do
100
+ env = GeoEngineer::Environment.new("test")
101
+ env.project("org", "name") {
102
+ environments 'test'
103
+ }
104
+ expect(env.projects.length).to eq 1
105
+ end
106
+
107
+ it 'should only load projects in the environment' do
108
+ env = GeoEngineer::Environment.new("test")
109
+ p0 = env.project("org", "0") {
110
+ environments 'test'
111
+ }
112
+
113
+ p1 = env.project("org", "1") {
114
+ environments 'nottest'
115
+ }
116
+
117
+ expect(p0.class).to eq GeoEngineer::Project
118
+ expect(p1.class).to eq NullObject
119
+ end
120
+ end
121
+
122
+ describe '#all_resources' do
123
+ it 'should include local and project resources (if project in env)' do
124
+ env = GeoEngineer::Environment.new("test")
125
+ env.resource('type', 'id0') { x 2 }
126
+
127
+ p0 = env.project("org", "0") {
128
+ environments 'test'
129
+ }
130
+ p0.resource('type', 'id1') { x 2 }
131
+
132
+ p1 = env.project("org", "1") {
133
+ environments 'nottest'
134
+ }
135
+ p1.resource('type', 'id2') { x 2 }
136
+
137
+ expect(env.all_resources.length).to eq 2
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,3 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe("GeoEngineer::Output")
@@ -0,0 +1,79 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe("GeoEngineer::Project") do
4
+ class OutModuleTemplate < GeoEngineer::Template
5
+ attr_reader :res1, :res2
6
+
7
+ def initialize(name, project, parameters)
8
+ super(name, project, parameters)
9
+
10
+ res1 = resource('type', '1') {
11
+ name "Resource1"
12
+ param parameters[:res1]
13
+ }
14
+
15
+ res2 = resource('type', '2') {
16
+ name "Resource2"
17
+ rel res1
18
+ }
19
+
20
+ @res1 = res1
21
+ @res2 = res2
22
+ end
23
+
24
+ def template_resources
25
+ [@res1, @res2]
26
+ end
27
+ end
28
+
29
+ class GeoEngineer::Templates::InModuleTemplate < GeoEngineer::Template
30
+ end
31
+
32
+ describe 'validations' do
33
+ it 'should validate its resources' do
34
+ project = GeoEngineer::Project.new('org', "project_name", nil)
35
+ project.environments = 'test'
36
+ project.resource('res', 'id') {
37
+ x 10
38
+ }
39
+ expect(project.errors.length).to eq 1 # geo_id nil
40
+ end
41
+
42
+ it 'should validate it has an environmnet' do
43
+ project = GeoEngineer::Project.new('org', "project_name", nil)
44
+ expect(project.errors.length).to eq 1
45
+ end
46
+ end
47
+
48
+ describe '#from_template' do
49
+ it 'should create a template from a class' do
50
+ project = GeoEngineer::Project.new('org', "project_name", nil)
51
+ temp1 = project.from_template('in_module_template', 'in')
52
+ temp2 = project.from_template('out_module_template', 'out')
53
+ expect(temp1.template_resources.length).to eq 0
54
+ expect(temp2.template_resources.length).to eq 2
55
+ end
56
+
57
+ it 'should error if the template is not found' do
58
+ project = GeoEngineer::Project.new('org', "project_name", nil)
59
+ expect { project.from_template('not_a_template') }.to raise_error(StandardError)
60
+ end
61
+ end
62
+
63
+ describe '#all_resources' do
64
+ it 'should return all resources created in templates and directly' do
65
+ project = GeoEngineer::Project.new('org', "project_name", nil)
66
+ project.resource('res', 'id') { x 10 }
67
+ project.from_template('out_module_template', 'out')
68
+ expect(project.all_resources.length).to eq 3
69
+ end
70
+ end
71
+
72
+ describe '#resource' do
73
+ it 'should create a resource and assign itself as project' do
74
+ project = GeoEngineer::Project.new('org', "project_name", nil)
75
+ res = project.resource('res', 'id') { x 10 }
76
+ expect(res.project).to eq project
77
+ end
78
+ end
79
+ end