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
data/bin/geo ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/geoengineer'
4
+ require_relative '../lib/geoengineer/cli/geo_cli'
5
+
6
+ geo = GeoCLI.instance
7
+ geo.run
@@ -0,0 +1,29 @@
1
+ ########################################################################
2
+ # GeoEngineer main Module
3
+ ########################################################################
4
+ module GeoEngineer
5
+ end
6
+
7
+ ########################################################################
8
+ # GeoEngineer::Resources Collection of Resources
9
+ ########################################################################
10
+ module GeoEngineer::Resources
11
+ end
12
+
13
+ ########################################################################
14
+ # GeoEngineer::Templates Collection of Templates
15
+ ########################################################################
16
+ module GeoEngineer::Templates
17
+ end
18
+
19
+ require 'aws-sdk'
20
+ require 'json'
21
+ require 'ostruct'
22
+ require 'uri'
23
+ require 'securerandom'
24
+
25
+ Dir["#{File.dirname(__FILE__)}/geoengineer/utils/**/*.rb"].each { |f| require f }
26
+
27
+ Dir["#{File.dirname(__FILE__)}/geoengineer/*.rb"].each { |f| require f }
28
+
29
+ Dir["#{File.dirname(__FILE__)}/geoengineer/resources/**/*.rb"].each { |f| require f }
@@ -0,0 +1,208 @@
1
+ require_relative '../../geoengineer'
2
+ require 'open3'
3
+ require 'commander'
4
+ require 'colorize'
5
+ require 'terminal-table'
6
+ require 'fileutils'
7
+ require 'json'
8
+ require 'singleton'
9
+
10
+ # Create GeoCLI for the command requires
11
+ class GeoCLI
12
+ end
13
+
14
+ require_relative './status_command'
15
+ require_relative './terraform_commands'
16
+
17
+ def environment(name, &block)
18
+ GeoCLI.instance.create_environment(name, &block)
19
+ end
20
+
21
+ def env
22
+ GeoCLI.instance.environment
23
+ end
24
+
25
+ def project(org, name, &block)
26
+ GeoCLI.instance.environment.project(org, name, &block)
27
+ end
28
+
29
+ # GeoCLI context
30
+ class GeoCLI
31
+ include Commander::Methods
32
+ include Singleton
33
+ include StatusCommand
34
+ include TerraformCommands
35
+
36
+ attr_accessor :environment, :env_name
37
+
38
+ # CLI FLAGS AND OPTIONS
39
+ attr_accessor :verbose, :no_color
40
+
41
+ def init_tmp_dir(name)
42
+ @tmpdir = "#{Dir.pwd}/tmp/#{name}"
43
+ FileUtils.mkdir_p @tmpdir
44
+ end
45
+
46
+ def init_terraform_files
47
+ @terraform_file = "terraform.tf.json"
48
+ @terraform_state_file = "terraform.tfstate"
49
+ @plan_file = "plan.terraform"
50
+
51
+ files = [
52
+ "#{@tmpdir}/#{@terraform_state_file}.backup",
53
+ "#{@tmpdir}/#{@terraform_file}",
54
+ "#{@tmpdir}/#{@terraform_state_file}",
55
+ "#{@tmpdir}/#{@plan_file}"
56
+ ]
57
+
58
+ files.each do |file|
59
+ File.delete(file) if File.exist?(file)
60
+ end
61
+ end
62
+
63
+ def create_environment(name, &block)
64
+ return @environment if @environment
65
+ if name != @env_name
66
+ puts "Not loading environment #{name} as env_name is #{@env_name}" if @verbose
67
+ return NullObject.new
68
+ end
69
+
70
+ @environment = GeoEngineer::Environment.new(name, &block)
71
+ init_tmp_dir(name)
72
+ init_terraform_files()
73
+ @environment
74
+ end
75
+
76
+ def require_from_pwd(file)
77
+ require "#{Dir.pwd}/#{file}"
78
+ end
79
+
80
+ def require_environment(options)
81
+ @env_name = options.environment || ENV['GEO_ENV'] || 'staging'
82
+ puts "Using environment '#{@env_name}'\n" if @verbose
83
+ begin
84
+ require_from_pwd "environments/#{@env_name}"
85
+ rescue LoadError
86
+ puts "unable to load 'environments/#{@env_name}'" if @verbose
87
+ end
88
+ end
89
+
90
+ def require_all_projects
91
+ Dir["#{Dir.pwd}/projects/**/*.rb"].each do |project_file|
92
+ puts "LOADING #{project_file}" if @verbose
93
+ require project_file
94
+ end
95
+ end
96
+
97
+ def require_project_file(project_file)
98
+ if !File.exist?(project_file) && !File.exist?("#{project_file}.rb")
99
+ throw "The file \"#{project_file}\" does not exist"
100
+ end
101
+ require_from_pwd project_file
102
+ end
103
+
104
+ def require_geo_files(args)
105
+ return require_all_projects if args.empty?
106
+ args.each { |project_file| require_project_file(project_file) }
107
+ end
108
+
109
+ def print_validation_errors(errs)
110
+ puts errs.map { |s| "ERROR: #{s}".colorize(:red) }
111
+ puts "Total Errors #{errs.length}"
112
+ end
113
+
114
+ def shell_exec(cmd, verbose = @verbose)
115
+ stdin, stdout_and_stderr, wait_thr = Open3.popen2e({}, *cmd)
116
+
117
+ puts(">> #{cmd}\n") if verbose
118
+ stdout_and_stderr.each do |line|
119
+ puts(line) if verbose
120
+ end
121
+ puts("<< Exited with status: #{wait_thr.value.exitstatus}\n\n") if verbose
122
+
123
+ stdin.close
124
+ stdout_and_stderr.close
125
+
126
+ wait_thr.value
127
+ end
128
+
129
+ # This defines the typical action in geo engineer
130
+ # - require the environment
131
+ # - require the geo files
132
+ # - ensure everything is valid
133
+ # - execute the action
134
+ # - execute the after hook
135
+ def init_action(action_name)
136
+ lambda do |args, options|
137
+ require_environment(options)
138
+ require_geo_files(args)
139
+ throw "Environment not set" unless @environment
140
+
141
+ @environment.execute_lifecycle(:before, action_name.to_sym)
142
+
143
+ errs = @environment.errors.flatten.sort
144
+ return print_validation_errors(errs) unless errs.empty?
145
+
146
+ yield args, options
147
+ @environment.execute_lifecycle(:after, action_name.to_sym)
148
+ end
149
+ end
150
+
151
+ def yes?(question)
152
+ answer = ask question
153
+ answer.strip.upcase.start_with? "YES"
154
+ end
155
+
156
+ def graph_cmd
157
+ command :graph do |c|
158
+ c.syntax = 'geo graph [<geo_files>]'
159
+ c.description = 'Generate and graph of the environment resources to GraphViz'
160
+ action = lambda do |args, options|
161
+ puts env.to_dot
162
+ end
163
+ c.action init_action(:graph, &action)
164
+ end
165
+ end
166
+
167
+ def gloabl_options
168
+ global_option('-e', '--environment <name>', "Environment to use")
169
+
170
+ @verbose = true
171
+ global_option('--quiet', 'reduce the noisy outputs (default they are on)') {
172
+ @verbose = false
173
+ }
174
+
175
+ @no_color = ''
176
+ global_option('--no-color', 'removes color from the terraform output') {
177
+ String.disable_colorization = true
178
+ @no_color = ' -no-color'
179
+ }
180
+ end
181
+
182
+ def terraform_installed?
183
+ terraform_version = shell_exec('which terraform')
184
+ terraform_version.exitstatus.zero?
185
+ end
186
+
187
+ def run
188
+ program :name, 'GeoEngineer'
189
+ program :version, '0.0.1'
190
+ program :description, 'GeoEngineer will help you Terraform your resources'
191
+ always_trace!
192
+
193
+ # check terraform installed
194
+ return puts "Please install terraform" unless terraform_installed?
195
+
196
+ # gloabl_options
197
+ gloabl_options
198
+
199
+ # Add commands
200
+ plan_cmd
201
+ apply_cmd
202
+ graph_cmd
203
+ status_cmd
204
+
205
+ # Execute the CLI
206
+ run!
207
+ end
208
+ end
@@ -0,0 +1,101 @@
1
+ # Status Command for Geo
2
+ module GeoCLI::StatusCommand
3
+ def calculate_type_status(codified, uncodified)
4
+ total = codified.count + uncodified.count
5
+ {
6
+ codified: codified.count,
7
+ uncodified: uncodified.count,
8
+ total: total,
9
+ percent: (100.0 * codified.count) / total
10
+ }
11
+ end
12
+
13
+ def status_resource_rows(reses)
14
+ rows = []
15
+ rows << :separator
16
+ rows << ['TerraformID', 'GeoID']
17
+ rows << :separator
18
+ reses.each do |sg|
19
+ g_id = sg._geo_id
20
+ g_id = "<<" if sg._terraform_id == sg._geo_id
21
+ rows << [sg._terraform_id, g_id]
22
+ end
23
+ rows << :separator
24
+ rows
25
+ end
26
+
27
+ def status_type_rows(type, codified, uncodified, stats)
28
+ rows = []
29
+
30
+ # Codified resources
31
+ rows << [{ value: "### CODIFIED #{type} ###".colorize(:green), colspan: 2, alignment: :left }]
32
+ rows.concat status_resource_rows(codified)
33
+
34
+ # Uncodified resources
35
+ rows << [{ value: "### UNCODIFIED #{type} ###".colorize(:red), colspan: 2, alignment: :left }]
36
+ rows.concat status_resource_rows(uncodified)
37
+
38
+ rows.concat status_rows(stats)
39
+ puts Terminal::Table.new({ rows: rows })
40
+ end
41
+
42
+ def default_status_types
43
+ [
44
+ "aws_security_group",
45
+ "aws_elb",
46
+ "aws_db_instance",
47
+ "aws_elasticache_cluster",
48
+ "aws_s3_bucket",
49
+ "aws_sqs_queue"
50
+ ]
51
+ end
52
+
53
+ def calculate_status(type_stats)
54
+ totals = {
55
+ codified: 0,
56
+ uncodified: 0,
57
+ total: 0
58
+ }
59
+ type_stats.each do |type, stats|
60
+ totals[:codified] += stats[:codified]
61
+ totals[:uncodified] += stats[:uncodified]
62
+ totals[:total] += stats[:total]
63
+ end
64
+ totals[:percent] = (100.0 * totals[:codified]) / totals[:total]
65
+ totals
66
+ end
67
+
68
+ def status_rows(stats)
69
+ rows = []
70
+ rows << ['CODIFIED'.colorize(:green), stats[:codified]]
71
+ rows << ['UNCODIFIED'.colorize(:red), stats[:uncodified]]
72
+ rows << ['TOTAL'.colorize(:blue), stats[:total]]
73
+ rows << ['PERCENT CODIFIED'.colorize({ mode: :bold }), format('%.2f%', stats[:percent])]
74
+ rows
75
+ end
76
+
77
+ def status_action
78
+ lambda do |args, options|
79
+ type_stats = {}
80
+ default_status_types.each do |type|
81
+ codified = @environment.codified_resources(type)
82
+ uncodified = @environment.uncodified_resources(type)
83
+ type_stats[type] = calculate_type_status(codified, uncodified)
84
+ status_type_rows(type, codified, uncodified, type_stats[type]) if @verbose
85
+ end
86
+
87
+ status = calculate_status(type_stats)
88
+ puts Terminal::Table.new({ rows: status_rows(status) }) if @verbose
89
+ puts JSON.pretty_generate(status)
90
+ end
91
+ end
92
+
93
+ def status_cmd
94
+ command :status do |c|
95
+ c.syntax = 'geo status [<geo_files>]'
96
+ c.description = 'Displays the the new, managed and unmanaged resources'
97
+ action = status_action
98
+ c.action init_action(:status, &action)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,59 @@
1
+ ########################################################################
2
+ # TerraformCommands provides command line terraform commands
3
+ # +plan+ and +apply+ for GeoEngineer
4
+ ########################################################################
5
+ module GeoCLI::TerraformCommands
6
+ def create_terraform_files
7
+ # create terraform file
8
+ File.open("#{@tmpdir}/#{@terraform_file}", 'w') { |file|
9
+ file.write(JSON.pretty_generate(@environment.to_terraform_json()))
10
+ }
11
+
12
+ # create terrafrom state
13
+ File.open("#{@tmpdir}/#{@terraform_state_file}", 'w') { |file|
14
+ file.write(JSON.pretty_generate(@environment.to_terraform_state()))
15
+ }
16
+ end
17
+
18
+ def terraform_plan
19
+ plan_commands = [
20
+ "cd #{@tmpdir}",
21
+ "terraform plan -state=#{@terraform_state_file} -out=#{@plan_file} #{@no_color}"
22
+ ]
23
+ shell_exec(plan_commands.join(" && "), true)
24
+ end
25
+
26
+ def terraform_apply
27
+ apply_commands = [
28
+ "cd #{@tmpdir}",
29
+ "terraform apply -state=#{@terraform_state_file} #{@plan_file} #{@no_color}"
30
+ ]
31
+ shell_exec(apply_commands.join(" && "), true)
32
+ end
33
+
34
+ def plan_cmd
35
+ command :plan do |c|
36
+ c.syntax = 'geo plan [<geo_files>]'
37
+ c.description = 'Generate and show an execution plan'
38
+ action = lambda do |args, options|
39
+ create_terraform_files
40
+ terraform_plan
41
+ end
42
+ c.action init_action(:plan, &action)
43
+ end
44
+ end
45
+
46
+ def apply_cmd
47
+ command :apply do |c|
48
+ c.syntax = 'geo apply [<geo_files>]'
49
+ c.description = 'Apply an execution plan'
50
+ action = lambda do |args, options|
51
+ create_terraform_files
52
+ return puts "Plan Broken" if terraform_plan.exitstatus.nonzero?
53
+ return puts "Rejecting Plan" unless yes?("Apply the above plan? [YES/NO]")
54
+ terraform_apply
55
+ end
56
+ c.action init_action(:apply, &action)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,186 @@
1
+ ########################################################################
2
+ # An Environment is a group of projects, resources and attributes,
3
+ # build to create a terraform file.
4
+ # The goal of GeoEngineer is to build an environment that can be created.
5
+ #
6
+ # An environment has resources, has arbitrary attributes, validations and lifecycle hooks
7
+ ########################################################################
8
+ class GeoEngineer::Environment
9
+ include HasAttributes
10
+ include HasResources
11
+ include HasValidations
12
+ include HasLifecycle
13
+
14
+ attr_reader :name, :projects
15
+
16
+ validate -> { validate_required_attributes([:region, :account_id]) }
17
+
18
+ # Validate resources have unique attributes
19
+ validate -> {
20
+ resources_grouped_by(&:terraform_name)
21
+ .select { |k, v| v.length > 1 }
22
+ .map { |k, v| "Non-unique type.id #{v.first.for_resource}" }
23
+ }
24
+
25
+ validate -> {
26
+ resources_grouped_by(&:_terraform_id)
27
+ .select { |k, v| v.length > 1 && !v.first._terraform_id.nil? }
28
+ .map { |k, v| "Non-unique _terraform_id #{v.first._terraform_id} #{v.first.for_resource}" }
29
+ }
30
+
31
+ validate -> {
32
+ resources_grouped_by(&:_geo_id)
33
+ .select { |k, v| v.length > 1 }
34
+ .map { |k, v| "Non-unique _geo_id #{v.first._geo_id} #{v.first.for_resource}" }
35
+ }
36
+
37
+ # Validate all resources
38
+ validate -> { resources.map(&:errors).flatten }
39
+
40
+ # Validate all projects (which validate resources)
41
+ validate -> { projects.map(&:errors).flatten }
42
+
43
+ before :validation, -> { self.region = self.region || ENV['AWS_REGION'] }
44
+
45
+ def initialize(name, &block)
46
+ @name = name
47
+ @projects = []
48
+ @outputs = []
49
+ self.send("#{name}?=", true) # e.g. staging?
50
+ instance_exec(self, &block) if block_given?
51
+ end
52
+
53
+ def resource(type, id, &block)
54
+ return find_resource(type, id) unless block_given?
55
+ resource = create_resource(type, id, &block)
56
+ resource.environment = self
57
+ resource
58
+ end
59
+
60
+ def output(id, value, &block)
61
+ output = GeoEngineer::Output.new(id, value, &block)
62
+ @outputs << output
63
+ output
64
+ end
65
+
66
+ def all_resources
67
+ reses = resources
68
+ @projects.each { |project| reses += project.all_resources }
69
+ reses
70
+ end
71
+
72
+ # Factory for creating projects inside an environment
73
+ def project(org, name, &block)
74
+ # do not add the project a second time
75
+ exists = @projects.select { |p| p.org == org && p.name == name }.first
76
+ return exists if exists
77
+
78
+ project = GeoEngineer::Project.new(org, name, self, &block)
79
+
80
+ supported_environments = [project.environments].flatten
81
+ # do not add the project if the project is not supported by this environment
82
+ return NullObject.new unless supported_environments.include? @name
83
+
84
+ @projects << project
85
+ project
86
+ end
87
+
88
+ # DOT Methods
89
+ # Given an attribute it tries to identify a dependency and return it
90
+ def extract_dependencies(x)
91
+ if x.is_a? Array
92
+ x.map { |y| extract_dependencies(y) }.flatten
93
+ elsif x.is_a?(String)
94
+ res = self.find_resource_by_ref(x)
95
+ return [res] if res
96
+ elsif x.is_a?(GeoEngineer::Resource)
97
+ return [x]
98
+ end
99
+ []
100
+ end
101
+
102
+ def depends_on(res)
103
+ all_attributes = []
104
+ all_attributes.concat res.attributes.values
105
+ all_attributes.concat res.subresources.map { |sr| sr.attributes.values }.flatten
106
+ dependencies = Set.new(all_attributes.map { |x| extract_dependencies(x) }.flatten)
107
+ dependencies.delete(nil)
108
+ dependencies
109
+ end
110
+
111
+ def to_dot
112
+ str = ["digraph {"]
113
+ str.concat(projects.map(&:to_dot))
114
+ all_resources.each do |res|
115
+ str << depends_on(res).map { |r| " #{res.to_ref.inspect} -> #{r.to_ref.inspect}" }
116
+ end
117
+ str << " }"
118
+ str.join("\n")
119
+ end
120
+
121
+ # Terraform Methods
122
+ def to_terraform
123
+ # Force preventing the destruction of any resource unless explicitly set
124
+ # Hopefully this will stop accidentally the environment
125
+ unless self.allow_destroy
126
+ all_resources.each { |r|
127
+ r.lifecycle { prevent_destroy true }
128
+ }
129
+ end
130
+
131
+ tf_resources = all_resources.map(&:to_terraform)
132
+ tf_resources += @outputs.compact.map(&:to_terraform)
133
+ tf_resources.join("\n\n")
134
+ end
135
+
136
+ def to_terraform_json
137
+ unless self.allow_destroy
138
+ all_resources.each { |r|
139
+ r.lifecycle { prevent_destroy true }
140
+ }
141
+ end
142
+
143
+ h = { resource: json_resources }
144
+ h[:output] = @outputs.map(&:to_terraform_json) unless @outputs.empty?
145
+ h
146
+ end
147
+
148
+ def json_resources
149
+ all_resources.each_with_object({}) do |r, c|
150
+ c[r.type] ||= {}
151
+ c[r.type][r.id] = r.to_terraform_json
152
+ c
153
+ end
154
+ end
155
+
156
+ def to_terraform_state
157
+ reses = all_resources.select(&:_terraform_id) # _terraform_id must not be nil
158
+ reses = reses.map { |r| { "#{r.type}.#{r.id}" => r.to_terraform_state() } }.reduce({}, :merge)
159
+
160
+ {
161
+ version: 1,
162
+ serial: 1,
163
+ modules: [
164
+ {
165
+ path: [:root],
166
+ outputs: {},
167
+ resources: reses
168
+ }
169
+ ]
170
+ }
171
+ end
172
+
173
+ # This method looks into AWS for resources that are not yet codified
174
+ def codified_resources(type)
175
+ # managed resources have a remote resource
176
+ res = self.resources_of_type(type).select { |r| !r.remote_resource.nil? }
177
+ res.sort_by(&:terraform_name)
178
+ end
179
+
180
+ def uncodified_resources(type)
181
+ # unmanaged resources have a remote resource without local_resource
182
+ clazz = self.class.get_resource_class_from_type(type)
183
+ res = clazz.fetch_remote_resources.select { |r| r.local_resource.nil? }
184
+ res.sort_by(&:terraform_name)
185
+ end
186
+ end