nexoform 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: be5988ba5a83aaf424f2d9f8b0164f738d87daca
4
+ data.tar.gz: ea0eb52b1ee24f85b886f66dd5d9c2fe073ee7e8
5
+ SHA512:
6
+ metadata.gz: f51b9ec53e05106619663d190710fd741ee43f6fabf8daa45d45ab5c294c88b71231ce3d89bb00d3e8787c9e58b501520c83461e93098bf75b1bbca5fbbfdb7b
7
+ data.tar.gz: b4b7623970dd7dfc6db14e3f3dc2bad2b5808fbaba6bfc65f00a9dfb1a65eb996a1797b5ff3b9a81697181b9d285d746ddee876a9d05add42eb3019a1a4f07ed
data/bin/nexoform ADDED
@@ -0,0 +1,473 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'rainbow/refinement'
5
+ using Rainbow
6
+
7
+ require 'thor'
8
+ require 'yaml'
9
+ require 'json'
10
+ require 'shellwords'
11
+
12
+ require 'nexoform'
13
+
14
+ # For some reason this method definition can't be inside the NexoformBin class
15
+ # because Thor won't find it when generating description documention
16
+ def default_plan_filename
17
+ 'nexoform.tfplan'
18
+ end
19
+
20
+ class NexoformBin < Thor
21
+ class_option :environment, type: :string, aliases: 'e', required: false
22
+ class_option :assume_yes, type: :boolean, aliases: 'y', default: false
23
+
24
+ desc 'apply', 'Apply changes (Runs a terraform apply)'
25
+ long_desc <<-LONGDESC
26
+ Applies any applicable changes. Under the hood, this
27
+ command runs a terraform apply.
28
+
29
+ If you pass --plan, the specified file will be used for the plan
30
+ If you pass --noplan, no plan file will be used
31
+ If you don't pass either, no plan file will be used unless the default
32
+ is present. If it is, you'll be prompted about using it
33
+
34
+ > $ nexoform apply --environment 'dev' [--plan=#{default_plan_filename}]
35
+ LONGDESC
36
+ option :plan, type: :string, aliases: 'p', required: false
37
+ option :noplan, type: :boolean, aliases: 'n', default: false
38
+ option :overwrite, type: :boolean, required: false
39
+ def apply
40
+ exec_apply(options)
41
+ end
42
+
43
+ desc 'plan', 'Print out changes that will be made on next apply (runs a terraform plan)'
44
+ long_desc <<-LONGDESC
45
+ Prints out any changes that will be made the next time
46
+ a nexoform apply is run. Under the hood, this command
47
+ runs a terraform plan.
48
+
49
+ If you pass an arg to 'out' the plan will be saved to that filename.
50
+ If you pass '--save' or '-s' the plan will be saved to '#{default_plan_filename}'
51
+ If you pass '--nosave' or '-n' the plan will not be saved
52
+ If you pass none of those, you'll be prompted about saving the plan
53
+
54
+ > $ nexoform plan --environment 'dev' [--save] [--out='#{default_plan_filename}']
55
+ LONGDESC
56
+ option :out, type: :string, aliases: 'o', required: false
57
+ option :save, type: :boolean, aliases: 's', required: false
58
+ option :nosave, type: :boolean, aliases: 'n', required: false
59
+ option :overwrite, type: :boolean, required: false
60
+ def plan
61
+ exec_plan(options)
62
+ end
63
+
64
+ desc 'destroy', 'Destroy all provisioned resources (runs a terraform destroy)'
65
+ long_desc <<-LONGDESC
66
+ Destroys any resources that have been provisioned
67
+
68
+ > $ nexoform destroy --environment 'dev'
69
+ LONGDESC
70
+ def destroy
71
+ exec_destroy(options)
72
+ end
73
+
74
+ desc 'output', 'Print any output from terraform'
75
+ long_desc <<-LONGDESC
76
+ Prints any output from last terraform state. Runs a 'terraform output'
77
+
78
+ > $ nexoform output
79
+ LONGDESC
80
+ def output
81
+ execute('terraform output')
82
+ end
83
+
84
+ desc 'version', 'Check current installed version of nexoform'
85
+ def version
86
+ puts "Nexoform - Version: #{Nexoform.version}"
87
+ end
88
+
89
+ desc 'config-file', 'Write a default config file'
90
+ long_desc <<-LONGDESC
91
+ Writes a nexoform config file to #{Nexoform::Config.filename}
92
+ containing the default settings. This can then be configured
93
+ as preferred.
94
+
95
+ > $ nexoform config-file [--upgrade] [--force] [--project-name 'simplenexus']
96
+ LONGDESC
97
+ option :force, type: :boolean, aliases: 'f', default: false
98
+ option :upgrade, type: :boolean, aliases: 'u', default: false
99
+ option :'project-name', type: :string, aliases: 'c', required: false
100
+ def config_file
101
+ exec_config_file(options)
102
+ end
103
+
104
+ desc 'list-envs', 'List the environments'
105
+ long_desc <<-LONGDESC
106
+ Lists the available environments and the current default (if applicable).
107
+ These are defined in the config file at #{Nexoform::Config.filename}
108
+
109
+ > $ nexoform list-envs
110
+ LONGDESC
111
+ def list_envs
112
+ exec_list_envs(options)
113
+ end
114
+
115
+ private
116
+
117
+ def print_next_color(str)
118
+ print Rainbow(str).color(next_color)
119
+ end
120
+
121
+ def puts_next_color(str)
122
+ puts Rainbow(str).color(next_color)
123
+ end
124
+
125
+ def config_file_present?
126
+ unless File.exist?(Nexoform::Config.filename)
127
+ print 'Could not find nexoform config file. Please create one at '.red
128
+ puts Nexoform::Config.filename.yellow
129
+ print_next_color 'You can generate a starter config file with: '
130
+ puts_next_color '`nexoform config-file`'
131
+ exit 2
132
+ end
133
+ end
134
+
135
+ def terraform_installed?
136
+ unless Nexoform::Bash.run_command('which terraform').success?
137
+ puts 'Terraform does not look to be installed, or is not in PATH.'.red
138
+ print 'To get started, check out: '.yellow
139
+ puts_next_color 'https://www.terraform.io/intro/getting-started/install.html'
140
+ exit 3
141
+ end
142
+ end
143
+
144
+ def terraform_files_present?
145
+ if Dir.glob('*.tf').empty?
146
+ puts "There aren't any terraform files here!".red
147
+ print 'To get started, check out: '.yellow
148
+ puts_next_color 'https://www.terraform.io/intro/getting-started/build.html'
149
+ exit 4
150
+ end
151
+ end
152
+
153
+ def plan_file_recent?(options)
154
+ # TODO: - check to see that the plan file, if it exists, was modified within the last 12 hours
155
+ # if it was not, warn/exit or something else more appropriate
156
+ end
157
+
158
+ def sanity_check(options)
159
+ config_file_present?
160
+ terraform_installed?
161
+ terraform_files_present?
162
+ plan_file_recent?(options)
163
+ end
164
+
165
+ def escape(str)
166
+ Shellwords.escape(str)
167
+ end
168
+
169
+ def env(options)
170
+ if options['environment']
171
+ options['environment']
172
+ elsif Nexoform::Config.default_env
173
+ Nexoform::Config.default_env
174
+ else
175
+ puts 'No environment was specified, and there is no default set.'.red
176
+ puts 'Please try again with a --environment specified'.yellow
177
+ exit 1
178
+ end
179
+ end
180
+
181
+ def execute(command)
182
+ puts
183
+ puts '-----------------------------------------------'.green
184
+ puts '| Executing command: '.green
185
+ puts '|'.green
186
+ puts '| '.green + command.blue
187
+ puts '-----------------------------------------------'.green
188
+ puts
189
+ retval = Nexoform::Bash.run_command_loud(command)
190
+ unless retval.success?
191
+ puts '* The previous command exited with failure. Please check the output and try again:'.red
192
+ print ' Command: '.red
193
+ puts command.yellow
194
+ exit retval.exitstatus.to_i
195
+ end
196
+ end
197
+
198
+ def terraform_init(options)
199
+ args = %W[
200
+ -backend=true
201
+ -force-copy
202
+ -get=true
203
+ -get-plugins=true
204
+ -reconfigure
205
+ -upgrade=true
206
+ -backend-config="bucket=#{escape(Nexoform::Config.bucket(env(options)))}"
207
+ -backend-config="key=#{escape(Nexoform::Config.key(env(options)))}"
208
+ -backend-config="region=#{escape(Nexoform::Config.region(env(options)))}"
209
+ ]
210
+ "terraform init #{args.join(' ')}"
211
+ end
212
+
213
+ def run_init(options)
214
+ print_next_color '* Initializing terraform for environment: '
215
+ execute(terraform_init(options))
216
+ end
217
+
218
+ def var_file_arg(options)
219
+ "-var-file #{escape(Nexoform::Config.var_file(env(options)))}"
220
+ end
221
+
222
+ def terraform_refresh(options)
223
+ "terraform refresh #{var_file_arg(options)}"
224
+ end
225
+
226
+ def run_refresh(options)
227
+ print_next_color 'Refreshing terraform state against actual state of ' \
228
+ 'resources for environment: '
229
+ print_next_color env(options)
230
+ execute(terraform_refresh(options))
231
+ end
232
+
233
+ def needs_prompt_for_plan_file?(options)
234
+ (options[:plan].nil? || options[:plan].empty?) && !options[:noplan]
235
+ end
236
+
237
+ def plan_file_in_dir?
238
+ !Dir.glob('*.tfplan').empty?
239
+ end
240
+
241
+ def prompt_for_plan_file(options)
242
+ return options if options[:noplan]
243
+
244
+ if Nexoform::Config.plan_disabled?(env(options))
245
+ puts_next_color '* Plan file is disabled in config file. Not using a plan file'
246
+ return options.merge(noplan: true)
247
+ end
248
+
249
+ if needs_prompt_for_plan_file?(options) && plan_file_in_dir?
250
+ # use config file if it has the info
251
+ if Nexoform::Config.has_plan_file?(env(options))
252
+ plan_file = Nexoform::Config.plan_file(env(options))
253
+ puts_next_color "* Using #{plan_file} for plan file as specified in " \
254
+ "config file #{Nexoform::Config.filename} for env #{env(options)}."
255
+ return options.merge(plan: plan_file)
256
+ else
257
+ plan_name = Dir.glob('*.tfplan').first
258
+ print "You didn't specify a --plan <filename>, but there is a plan " \
259
+ "file present. Would you like to use the plan '#{plan_name}'? " \
260
+ "(pass --plan or --noplan if you don't want to see this prompt) (Y/N)?: ".yellow
261
+ resp = STDIN.gets.chomp
262
+ if resp =~ /y/i
263
+ puts_next_color "* Using plan file: #{plan_name}"
264
+ return options.merge(plan: plan_name)
265
+ else
266
+ puts '* Not using a plan file'.yellow
267
+ end
268
+ end
269
+ end
270
+ options
271
+ end
272
+
273
+ def exit_if_plan_file_should_but_does_not_exist(options)
274
+ if options[:plan] && !File.exist?(options[:plan])
275
+ puts "* Was told to use plan file '#{options[:plan]}' but it does not exist!".red
276
+ puts '* Please run `nexoform plan` and try again, or Re-run with --noplan'.red
277
+ exit 6
278
+ end
279
+ end
280
+
281
+ def apply_with_plan_file?(options)
282
+ return false if options[:noplan]
283
+
284
+ options[:plan]
285
+ end
286
+
287
+ def terraform_apply(options)
288
+ varfile = apply_with_plan_file?(options) ? '' : var_file_arg(options)
289
+ planfile = apply_with_plan_file?(options) ? options[:plan] : ''
290
+ "terraform apply #{varfile} #{planfile}"
291
+ end
292
+
293
+ def exec_apply(options)
294
+ sanity_check(options)
295
+ options = prompt_for_plan_file(options)
296
+ exit_if_plan_file_should_but_does_not_exist(options)
297
+ unless options[:plan]
298
+ run_init(options)
299
+ run_refresh(options)
300
+ end
301
+ print_next_color 'Applying any infrastructure changes ' \
302
+ 'for environment: '
303
+ puts_next_color env(options)
304
+ execute(terraform_apply(options))
305
+ end
306
+
307
+ def prompt_for_save_file(options)
308
+ return options if options[:nosave]
309
+
310
+ if Nexoform::Config.has_plan_file?(env(options))
311
+ plan_file = Nexoform::Config.plan_file(env(options))
312
+ puts_next_color "* Found plan file in #{Nexoform::Config.filename} for env #{env(options)}. Using #{plan_file} for plan file"
313
+ opts = options.merge(out: plan_file)
314
+
315
+ if Nexoform::Config.has_plan_file_overwrite?(env(options))
316
+ pf_overwrite = Nexoform::Config.plan_file_overwrite(env(options))
317
+ puts_next_color "* Found overwrite setting for plan file '#{plan_file}' in config file '#{Nexoform::Config.filename}'. Setting to '#{pf_overwrite}'"
318
+ opts = opts.merge(overwrite: pf_overwrite)
319
+ else
320
+ puts "* Didn't see an overwrite setting for plan file '#{plan_file}' in the config file '#{Nexoform::Config.filename}' for env '#{env(options)}'. Skipping".yellow
321
+ end
322
+
323
+ return opts
324
+ elsif Nexoform::Config.plan_disabled?(env(options))
325
+ puts_next_color '* Plan file is disabled in config file. Not using a plan file'
326
+ return options.merge(nosave: true)
327
+ else
328
+ if !options[:out] && options[:save]
329
+ return options.merge(out: default_plan_filename)
330
+ elsif !options[:out]
331
+ puts "You didn't specify a --out <filename>. Would you like to save the plan? (pass --nosave if you don't want to see this prompt)\n".yellow
332
+ puts "1. 'Y' for default filename (#{default_plan_filename}), ".cyan
333
+ puts "2. 'N' for no saving, ".red
334
+ puts '3. Type in a filename'.blue
335
+ print "\nYour choice: ".yellow
336
+ resp = STDIN.gets.chomp
337
+ if resp =~ /^y$/ || resp =~ /^1$/
338
+ puts_next_color "* Using default filename: #{default_plan_filename}"
339
+ return options.merge(out: default_plan_filename)
340
+ elsif resp =~ /^n$/i || resp =~ /^2$/i || resp.empty?
341
+ puts '* Not saving plan'.yellow
342
+ return options
343
+ else
344
+ puts_next_color "* Using filename: #{resp}"
345
+ return options.merge(out: resp)
346
+ end
347
+ end
348
+ end
349
+
350
+ options
351
+ end
352
+
353
+ def prompt_if_plan_file_exists(options)
354
+ if options[:out] && File.exist?(options[:out])
355
+ if options[:overwrite].nil? || options[:overwrite] == 'ask'
356
+ print "The specified plan file '#{options[:out]}' already exists. Over write? (Y/N): ".yellow
357
+ resp = STDIN.gets.chomp
358
+ if resp =~ /n/i
359
+ puts '* User declined writing over plan file. Exiting.'.red
360
+ exit 5
361
+ end
362
+ elsif options[:overwrite] == false
363
+ puts "* Overwrite is set to false but the plan file '#{options[:out]}' exists. " \
364
+ 'Exitting. Please change the setting or delete/move the plan file before ' \
365
+ 'trying again.'.red
366
+ exit 5
367
+ elsif options[:overwrite].is_a?(String) && options[:overwrite] != 'ask'
368
+ puts "* Plan File Overwrite for env '#{env(options)}' is set to '#{options[:overwrite]}' which is not a valid value. Must be either 'yes', 'no', or 'ask'".red
369
+ exit 5
370
+ end
371
+ end
372
+ end
373
+
374
+ def plan_with_plan_file?(options)
375
+ return false if options[:noplan]
376
+
377
+ options[:plan]
378
+ end
379
+
380
+ def terraform_plan(options)
381
+ out = options[:out] ? "-out=#{options[:out]}" : ''
382
+ "terraform plan #{var_file_arg(options)} #{out}"
383
+ end
384
+
385
+ def exec_plan(options)
386
+ sanity_check(options)
387
+ options = prompt_for_save_file(options)
388
+ prompt_if_plan_file_exists(options)
389
+ run_init(options)
390
+ run_refresh(options)
391
+ print_next_color '* Running terraform plan for environment: '
392
+ puts_next_color env(options)
393
+ execute(terraform_plan(options))
394
+ end
395
+
396
+ def terraform_destroy(options)
397
+ "terraform destroy #{var_file_arg(options)}"
398
+ end
399
+
400
+ def exec_destroy(options)
401
+ sanity_check(options)
402
+ run_init(options)
403
+ run_refresh(options)
404
+ print_next_color '* Destroying any infrastructure resources ' \
405
+ 'for environment: '
406
+ puts_next_color env(options)
407
+ execute(terraform_destroy(options))
408
+ end
409
+
410
+ def config_file_action(options)
411
+ if options[:upgrade]
412
+ 'u'
413
+ elsif options[:force]
414
+ 'o'
415
+ else
416
+ # Upgrade isn't implemented yet. Uncomment when it is
417
+ # print "A config file already exists at #{Nexoform::Config.filename}. [U]pgrade, [O]verwrite with default settings, or do [N]othing? (U/O/N): ".yellow
418
+ print "A config file already exists at #{Nexoform::Config.filename}. [O]verwrite with default settings, or do [N]othing? (O/N): ".yellow
419
+ STDIN.gets.chomp
420
+ end
421
+ end
422
+
423
+ def take_action(action, project_name)
424
+ if action =~ /u/i
425
+ puts "* Upgrading config file at #{Nexoform::Config.filename}".green
426
+ Nexoform::Config.upgrade_settings_file
427
+ elsif action =~ /o/i
428
+ puts "* Overwriting config file with new version at #{Nexoform::Config.filename}".yellow
429
+ Nexoform::Config.write_default_settings_file(project_name)
430
+ else
431
+ puts '* User declined. Not writing config file'.red
432
+ nil
433
+ end
434
+ end
435
+
436
+ def exec_config_file(options)
437
+ sanity_check(options)
438
+ if File.exist?(Nexoform::Config.filename)
439
+ take_action(config_file_action(options), options[:'project-name'])
440
+ else
441
+ puts "* Writing new config file to #{Nexoform::Config.filename}".green
442
+ Nexoform::Config.write_default_settings_file(options[:'project-name'])
443
+ end
444
+ end
445
+
446
+ def next_color
447
+ # colors = %i[cyan indianred magenta aqua]
448
+ colors = %i[cyan indianred magenta]
449
+ @last_num ||= -1
450
+ @last_num = (@last_num + 1) % colors.length
451
+ colors[@last_num]
452
+ end
453
+
454
+ def exec_list_envs(options)
455
+ sanity_check(options)
456
+ puts_next_color "\nValid environments (defined in your config file): \n"
457
+ Nexoform::Config.envs.each do |env|
458
+ puts Rainbow(" - #{env}").color(next_color)
459
+ end
460
+ puts
461
+ end
462
+ end
463
+
464
+ if !ARGV.empty? && %w[-v --version].include?(ARGV.first)
465
+ puts "Nexoform - Version: #{Nexoform.version}"
466
+ else
467
+ begin
468
+ NexoformBin.start(ARGV)
469
+ rescue StandardError => e
470
+ puts "* Encountered an error. Make sure your config-file isn't messed up".red
471
+ puts "\n* Exception message: #{e.message}".yellow
472
+ end
473
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'active_support/core_ext/hash/indifferent_access'
5
+
6
+ module Nexoform
7
+ class Config
8
+ def self.has_config_file?(dir)
9
+ File.exist?("#{dir}/nexoform.yml")
10
+ end
11
+
12
+ def self.find_config_file(starting_dir)
13
+ if has_config_file?(starting_dir)
14
+ "#{starting_dir}/nexoform.yml"
15
+ elsif starting_dir == '/'
16
+ default_filename
17
+ else
18
+ find_config_file(File.dirname(starting_dir)) # recurse up to /
19
+ end
20
+ end
21
+
22
+ def self.default_filename
23
+ './nexoform.yml'
24
+ end
25
+
26
+ def self.filename
27
+ find_config_file(Dir.pwd)
28
+ end
29
+
30
+ def self.proj_name(project_name)
31
+ project_name && !project_name.empty? ? project_name : '<companyname>'
32
+ end
33
+
34
+ def self.default_yaml(project_name)
35
+ %(---
36
+ nexoform:
37
+ environments:
38
+ default: dev # optional default env so you don't have to specify
39
+ dev: # name of environment
40
+ varFile: dev.tfvars # terraform var-file to use
41
+ plan: # optional block. Avoids getting prompted
42
+ enabled: yes # yes | no. If no, a plan file is not used
43
+ file: dev.tfplan # file the plan is saved to automatically
44
+ overwrite: yes # overwrite existing file. could be: yes | no | ask
45
+ state: # configuration for state management s3 backend
46
+ bucket: #{project_name}-terraform-state
47
+ key: dev.tfstate
48
+ region: us-east-1
49
+ staging: # name of environment
50
+ varFile: staging.tfvars # terraform var-file to use
51
+ plan: # optional block. Avoids getting prompted
52
+ enabled: yes # yes | no. If no, a plan file is not used
53
+ file: staging.tfplan # file the plan is saved to automatically
54
+ overwrite: yes # overwrite existing file. could be: yes | no | ask
55
+ state: # configuration for state management s3 backend
56
+ bucket: #{project_name}-terraform-state
57
+ key: staging.tfstate
58
+ region: us-east-1
59
+ prod: # name of environment
60
+ varFile: prod.tfvars # terraform var-file to use
61
+ plan: # optional block. Avoids getting prompted
62
+ enabled: yes # yes | no. If no, a plan file is not used
63
+ file: prod.tfplan # file the plan is saved to automatically
64
+ overwrite: yes # overwrite existing file. could be: yes | no | ask
65
+ state: # configuration for state management s3 backend
66
+ bucket: #{project_name}-terraform-state
67
+ key: prod.tfstate
68
+ region: us-east-1
69
+ ).split("\n").map { |s| s.sub(' ' * 8, '') }.join("\n")
70
+ end
71
+
72
+ def self.default_settings(project_name = nil)
73
+ YAML.safe_load(default_yaml(proj_name(project_name))).with_indifferent_access
74
+ end
75
+
76
+ def self.settings(filename = self.filename)
77
+ YAML.load_file(filename).with_indifferent_access if File.exist?(filename)
78
+ end
79
+
80
+ def self.write_settings(settings, filename = self.filename, is_yaml: false)
81
+ settings = settings.to_yaml unless is_yaml
82
+ settings.gsub!(/\s*!ruby\/hash:ActiveSupport::HashWithIndifferentAccess/, '')
83
+ File.write(filename, settings)
84
+ end
85
+
86
+ def self.write_default_settings_file(project_name = nil)
87
+ write_settings(default_yaml(proj_name(project_name)), filename, is_yaml: true)
88
+ end
89
+
90
+ def self.debug?
91
+ settings[:nexoform][:debug]
92
+ end
93
+
94
+ def self.envs
95
+ settings[:nexoform][:environments].keys.reject { |k| k == 'default' }
96
+ end
97
+
98
+ def self.var_file(environment)
99
+ find_value(%I[nexoform environments #{environment} varFile])
100
+ end
101
+
102
+ def self.default_env
103
+ settings[:nexoform][:environments][:default]
104
+ end
105
+
106
+ def self.bucket(environment)
107
+ find_value(%I[nexoform environments #{environment} state bucket])
108
+ end
109
+
110
+ def self.key(environment)
111
+ find_value(%I[nexoform environments #{environment} state key])
112
+ end
113
+
114
+ def self.region(environment)
115
+ find_value(%I[nexoform environments #{environment} state region])
116
+ end
117
+
118
+ def self.plan_enabled(environment)
119
+ find_value(%I[nexoform environments #{environment} plan enabled])
120
+ end
121
+
122
+ def self.plan_disabled?(environment)
123
+ !plan_enabled(environment)
124
+ rescue ConfigError => e
125
+ false
126
+ end
127
+
128
+ def self.plan_file(environment)
129
+ find_value(%I[nexoform environments #{environment} plan file])
130
+ end
131
+
132
+ def self.has_plan_file?(environment)
133
+ return false if plan_disabled?(environment)
134
+
135
+ plan_file(environment)
136
+ rescue ConfigError => e
137
+ false
138
+ end
139
+
140
+ def self.plan_file_overwrite(environment)
141
+ find_value(%I[nexoform environments #{environment} plan overwrite])
142
+ end
143
+
144
+ def self.has_plan_file_overwrite?(environment)
145
+ plan_file_overwrite(environment)
146
+ true
147
+ rescue ConfigError => e
148
+ false
149
+ end
150
+
151
+ private
152
+
153
+ class ConfigError < StandardError
154
+ end
155
+
156
+ def self.find_value(keys)
157
+ keys.reduce(settings) do |last_val, key|
158
+ if last_val[key].nil?
159
+ raise ConfigError, "Key '#{key}' in chain '#{keys.join(' -> ')}' produced a " \
160
+ 'nil value. The expected key is missing in your config file.'
161
+ end
162
+ last_val[key]
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ module Nexoform
6
+ module Sh
7
+ def self.run_command(command, print_stdout = false)
8
+ stdout = `#{command}`
9
+ puts stdout if print_stdout
10
+ OpenStruct.new(
11
+ success?: $?.exitstatus.zero?,
12
+ exitstatus: $?.exitstatus,
13
+ stdout: stdout
14
+ )
15
+ end
16
+ end
17
+
18
+ module Bash
19
+ def self.escape_double_quotes(str)
20
+ str.gsub('"', '\\"')
21
+ end
22
+
23
+ def self.run_command(command, print_stdout = false)
24
+ stdout = `bash -c "#{escape_double_quotes(command)}"`
25
+ puts stdout if print_stdout
26
+ OpenStruct.new(
27
+ success?: $?.exitstatus.zero?,
28
+ exitstatus: $?.exitstatus,
29
+ stdout: stdout
30
+ )
31
+ end
32
+
33
+ def self.run_command_loud(command)
34
+ exitstatus = system("bash -c \"#{escape_double_quotes(command)}\"")
35
+ OpenStruct.new(
36
+ success?: exitstatus,
37
+ exitstatus: exitstatus ? '0' : '1', # we lose the true exit code
38
+ stdout: '' # we lose stdout too
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nexoform
4
+ def self.version
5
+ '0.0.1'
6
+ end
7
+
8
+ def self.date
9
+ '2018-11-09'
10
+ end
11
+ end
data/lib/nexoform.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem.find_files('nexoform/**/*.rb').each do |path|
4
+ require path.gsub(/\.rb$/, '') unless path =~ /bot.*cli/
5
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nexoform
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben Porter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rainbow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.20'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.20'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '12.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '12.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.59'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.59'
111
+ description: Nexoform wraps Terraform to provide awareness for multiple environments.
112
+ Nexoform also provides a more guided experience for using remote backends to track
113
+ your state. Without nexoform, there are several ways to accidentally lose or corrupt
114
+ your current state. Nexoform puts up guard rails to prevent accidents, and puts
115
+ you on firm ground with terraform.
116
+ email: bporter@simplenexus.com
117
+ executables:
118
+ - nexoform
119
+ extensions: []
120
+ extra_rdoc_files: []
121
+ files:
122
+ - bin/nexoform
123
+ - lib/nexoform.rb
124
+ - lib/nexoform/config.rb
125
+ - lib/nexoform/shell.rb
126
+ - lib/nexoform/version.rb
127
+ homepage: https://github.com/SimpleNexus/nexoform
128
+ licenses:
129
+ - MIT
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 2.3.0
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.5.2.3
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: Environment aware wrapping for Terraform
151
+ test_files: []