Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion TerraformDevKit.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions lib/TerraformDevKit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
require 'TerraformDevKit/terraform_template_config_file'
require 'TerraformDevKit/url'
require 'TerraformDevKit/version'
require 'TerraformDevKit/warning'
30 changes: 13 additions & 17 deletions lib/TerraformDevKit/command.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/TerraformDevKit/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/TerraformDevKit/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module TerraformDevKit
VERSION = '0.2.7'.freeze
VERSION = '0.2.8'.freeze
end
14 changes: 14 additions & 0 deletions lib/TerraformDevKit/warning.rb
Original file line number Diff line number Diff line change
@@ -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
4 changes: 1 addition & 3 deletions spec/environment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion spec/terraform_config_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
110 changes: 39 additions & 71 deletions tasks/devkit.rake
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ 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(
TDK::OS.convert_to_local_path(BIN_PATH),
ENV['PATH']
)

PLAN_FILE = 'plan.tfplan'.freeze

def destroy_if_fails(env, task)
yield
rescue Exception => e
Expand All @@ -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'
Expand All @@ -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'
Expand All @@ -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

Expand All @@ -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(
Expand All @@ -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)'
Expand Down