diff --git a/README.md b/README.md index 10c92e8..423b382 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,6 @@ The AWS profile **must not** be specified for test and production accounts, as u ### A Minimal Rakefile ```ruby -ROOT_PATH = File.dirname(File.expand_path(__FILE__)) - spec = Gem::Specification.find_by_name 'TerraformDevKit' load "#{spec.gem_dir}/tasks/devkit.rake" @@ -97,8 +95,8 @@ It's possible to override the location of your config files by setting the varia ```ruby # %s will be substituted with the environment name. -# File is exected to live in /c/path/to/root/config/config-dev.yml -CONFIG_FILE = File.join(ROOT_PATH, 'config', 'config-%s.yml') +# File is exected to live in config/config-dev.yml +CONFIG_FILE = File.join('config', 'config-%s.yml') ``` ### Tasks and Hooks diff --git a/TerraformDevKit.gemspec b/TerraformDevKit.gemspec index 7246bcf..92ac289 100644 --- a/TerraformDevKit.gemspec +++ b/TerraformDevKit.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'webmock', '~> 3.0' - + spec.add_runtime_dependency 'aws-sdk-core', '~> 3' spec.add_runtime_dependency 'aws-sdk-dynamodb', '~> 1' spec.add_runtime_dependency 'aws-sdk-s3', '~> 1' diff --git a/lib/TerraformDevKit.rb b/lib/TerraformDevKit.rb index dd8da3a..f71d833 100644 --- a/lib/TerraformDevKit.rb +++ b/lib/TerraformDevKit.rb @@ -18,3 +18,4 @@ require 'TerraformDevKit/terraform_template_config_file' require 'TerraformDevKit/url' require 'TerraformDevKit/version' +require 'TerraformDevKit/warning' diff --git a/lib/TerraformDevKit/command.rb b/lib/TerraformDevKit/command.rb index c2f7622..d906f0f 100644 --- a/lib/TerraformDevKit/command.rb +++ b/lib/TerraformDevKit/command.rb @@ -1,26 +1,22 @@ -require 'open3' - module TerraformDevKit class Command def self.run(cmd, directory: Dir.pwd, print_output: true) - Open3.popen2e(cmd, chdir: directory) do |_, stdout_and_stderr, thread| - output = process_output(stdout_and_stderr, print_output) - - thread.join - raise "Error running command #{cmd}" unless thread.value.success? - return output + out = IO.popen(cmd, err: %i[child out], chdir: directory) do |io| + begin + out = '' + loop do + chunk = io.readpartial(4096) + print chunk if print_output + out += chunk + end + rescue EOFError; end + out end - end - private_class_method - def self.process_output(stream, print_output) - lines = [] + $?.exitstatus.zero? || (raise "Error running command #{cmd}") - until (line = stream.gets).nil? - print line if print_output - lines << line.strip - end - lines + out.split("\n") + .map { |line| line.tr("\r\n", '') } end end end diff --git a/lib/TerraformDevKit/environment.rb b/lib/TerraformDevKit/environment.rb index ac978cc..a678d06 100644 --- a/lib/TerraformDevKit/environment.rb +++ b/lib/TerraformDevKit/environment.rb @@ -30,7 +30,7 @@ def local_backend? end def working_dir - File.join(ROOT_PATH, 'envs', @name) + File.join('envs', @name) end def self.temp_name diff --git a/lib/TerraformDevKit/version.rb b/lib/TerraformDevKit/version.rb index 6d80cd6..0eac873 100644 --- a/lib/TerraformDevKit/version.rb +++ b/lib/TerraformDevKit/version.rb @@ -1,3 +1,3 @@ module TerraformDevKit - VERSION = '0.2.7'.freeze + VERSION = '0.2.8'.freeze end diff --git a/lib/TerraformDevKit/warning.rb b/lib/TerraformDevKit/warning.rb new file mode 100644 index 0000000..32a8686 --- /dev/null +++ b/lib/TerraformDevKit/warning.rb @@ -0,0 +1,14 @@ +module TerraformDevKit + WARNING_MESSAGE = <<-'EOF'.freeze +__ ___ ____ _ _ ___ _ _ ____ _ _ _ +\ \ / / \ | _ \| \ | |_ _| \ | |/ ___| | | | | + \ \ /\ / / _ \ | |_) | \| || || \| | | _ | | | | + \ V V / ___ \| _ <| |\ || || |\ | |_| | |_|_|_| + \_/\_/_/ \_\_| \_\_| \_|___|_| \_|\____| (_|_|_) +EOF + + def self.warning(env) + puts WARNING_MESSAGE + puts "YOU ARE OPERATING ON ENVIRONMENT #{env}" + end +end diff --git a/spec/environment_spec.rb b/spec/environment_spec.rb index 4457369..63da6f7 100644 --- a/spec/environment_spec.rb +++ b/spec/environment_spec.rb @@ -2,8 +2,6 @@ TDK = TerraformDevKit -ROOT_PATH = '/c/some/root/path' - RSpec.describe TerraformDevKit::Environment do describe '#initialize' do [nil, '', 'foo#bar', 'foo-bar', 'foo_bar', 'foo+bar', '#'].each do |name| @@ -43,7 +41,7 @@ describe '#working_dir' do it 'prefixes environment name correctly' do dir = TDK::Environment.new('foobar').working_dir - expect(dir).to eq(File.join(ROOT_PATH,'envs', 'foobar')) + expect(dir).to eq(File.join('envs', 'foobar')) end end diff --git a/spec/terraform_config_manager_spec.rb b/spec/terraform_config_manager_spec.rb index 03e0708..97d2b82 100644 --- a/spec/terraform_config_manager_spec.rb +++ b/spec/terraform_config_manager_spec.rb @@ -77,7 +77,6 @@ end it 'creates configuration files' do - ROOT_PATH = @tmpdir Dir.chdir(@tmpdir) do # TODO: find a way to make Configuration not a singleton TDK::Configuration.init('config.yml') diff --git a/tasks/devkit.rake b/tasks/devkit.rake index 4ae3b54..6a0767f 100644 --- a/tasks/devkit.rake +++ b/tasks/devkit.rake @@ -3,9 +3,8 @@ require 'TerraformDevKit' TDK = TerraformDevKit -raise 'ROOT_PATH is not defined' if defined?(ROOT_PATH).nil? -BIN_PATH = File.join(ROOT_PATH, 'bin') -CONFIG_FILE ||= File.join(ROOT_PATH, 'config', 'config-%s.yml') +BIN_PATH = File.expand_path('bin') +CONFIG_FILE ||= File.expand_path(File.join('config', 'config-%s.yml')) # Ensure terraform is in the PATH ENV['PATH'] = TDK::OS.join_env_path( @@ -13,8 +12,6 @@ ENV['PATH'] = TDK::OS.join_env_path( ENV['PATH'] ) -PLAN_FILE = 'plan.tfplan'.freeze - def destroy_if_fails(env, task) yield rescue Exception => e @@ -32,24 +29,24 @@ end def task_in_current_namespace(task_name, current_task) namespace = current_task.scope.path.to_s - if namespace.empty? - return task_name - end - - return "#{namespace}:#{task_name}" + namespace.empty? ? task_name : "#{namespace}:#{task_name}" end def remote_state aws_config = TDK::AwsConfig.new(TDK::Configuration.get('aws')) - dynamo_db = TDK::DynamoDB.new( - aws_config.credentials, - aws_config.region + TDK::TerraformRemoteState.new( + TDK::DynamoDB.new(aws_config.credentials, aws_config.region), + TDK::S3.new(aws_config.credentials, aws_config.region) ) - s3 = TDK::S3.new( - aws_config.credentials, - aws_config.region - ) - TDK::TerraformRemoteState.new(dynamo_db, s3) +end + +def prompt_for_confirmation(env, action) + TDK.warning(env.name) + puts Rainbow("You are about to execute action #{action}.\n" \ + "Are you sure you want to proceed?\n" \ + "Only 'yes' will be accepted.").red.bright + response = STDIN.gets.strip + response == 'yes' || (raise 'Action cancelled because response was not "yes"') end desc 'Prepares the environment to create the infrastructure' @@ -76,53 +73,35 @@ task :prepare, [:env] do |_, args| remote_state.init(env, project_config) end - invoke('custom_prepare', task, args.env, safe_invoke: true) - - if File.exist?(File.join(env.working_dir, '.terraform')) - get_cmd = 'terraform get' - get_cmd += ' -update=true' if TDK::TerraformConfigManager.update_modules? - TDK::Command.run(get_cmd, directory: env.working_dir) - else - init_cmd = 'terraform init' - init_cmd += ' -upgrade=false' unless TDK::TerraformConfigManager.update_modules? + invoke('custom_prepare', task, env.name, safe_invoke: true) - TDK::Command.run(init_cmd, directory: env.working_dir) - end + cmd = 'terraform init' + cmd += ' -upgrade=false' unless TDK::TerraformConfigManager.update_modules? + TDK::Command.run(cmd, directory: env.working_dir) end desc 'Shows the plan to create the infrastructure' task :plan, [:env] => :prepare do |_, args| env = TDK::Environment.new(args.env) - Dir.chdir(env.working_dir) do - system("terraform plan -out=#{PLAN_FILE}") - end + TDK::Command.run('terraform plan', directory: env.working_dir) end desc 'Creates the infrastructure' task :apply, [:env] => :prepare do |task, args| - invoke('pre_apply', task, args.env, safe_invoke: true) - env = TDK::Environment.new(args.env) - invoke('plan', task, env.name) + prompt_for_confirmation(env, 'apply') unless env.local_backend? - unless env.local_backend? - puts Rainbow("Are you sure you want to apply the above plan?\n" \ - "Only 'yes' will be accepted.").green - response = STDIN.gets.strip - unless response == 'yes' - raise "Apply cancelled because response was not 'yes'.\n" \ - "Response was: #{response}" - end - end + invoke('pre_apply', task, env.name, safe_invoke: true) destroy_if_fails(env, task) do - Dir.chdir(env.working_dir) do - system("terraform apply \"#{PLAN_FILE}\"") - end + # TODO: Shall we just use auto-approve? + cmd = 'terraform apply' + cmd += ' -auto-approve' if env.local_backend? + TDK::Command.run(cmd, directory: env.working_dir) end - invoke('post_apply', task, args.env, safe_invoke: true) + invoke('post_apply', task, env.name, safe_invoke: true) end desc 'Tests a local environment' @@ -133,7 +112,7 @@ task :test, [:env] do |task, args| invoke('apply', task, env.name) destroy_if_fails(env, task) do - invoke('custom_test', task, args.env, safe_invoke: true) + invoke('custom_test', task, env.name, safe_invoke: true) end end @@ -149,30 +128,19 @@ end desc 'Destroys the infrastructure' task :destroy, [:env] => :prepare do |task, args| - invoke('pre_destroy', task, args.env, safe_invoke: true) - env = TDK::Environment.new(args.env) - cmd = 'terraform destroy' - unless env.local_backend? - puts Rainbow("\n\n!!!! WARNING !!!!\n\n" \ - "You are about to destroy #{env.name} and its remote state.\n" \ - "Are you sure you want to proceed?\n" \ - "Only 'yes' will be accepted.").red.bright - response = STDIN.gets.strip - - unless response == 'yes' - raise "Destroy cancelled because response was not 'yes'.\n" \ - "Response was: #{response}" - end - end - - cmd += ' -force' + prompt_for_confirmation(env, 'destroy') unless env.local_backend? - Dir.chdir(env.working_dir) do - system(cmd) - end - invoke('pre_destroy', task, args.env, safe_invoke: true) + invoke('pre_destroy', task, env.name, safe_invoke: true) + + TDK.warning(env.name) unless env.local_backend? + + cmd = 'terraform destroy' + # TODO: Shall we just use force? + cmd += ' -force' if env.local_backend? + + TDK::Command.run(cmd, directory: env.working_dir) unless env.local_backend? project_config = TDK::TerraformProjectConfig.new( @@ -181,7 +149,7 @@ task :destroy, [:env] => :prepare do |task, args| remote_state.destroy(env, project_config) end - invoke('post_destroy', task, args.env, safe_invoke: true) + invoke('post_destroy', task, env.name, safe_invoke: true) end desc 'Cleans an environment (infrastructure is destroyed too)'