Skip to content

Commit

Permalink
Merge pull request #27 from unifio/yl-wip2
Browse files Browse the repository at this point in the history
0.5.0 (October 5, 2016)
  • Loading branch information
yuhunglin authored Oct 5, 2016
2 parents 8e66a4b + f2e97dd commit 30e0a50
Show file tree
Hide file tree
Showing 16 changed files with 308 additions and 145 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
## (Unreleased)

BACKWARDS INCOMPATIBILITIES:
- In-line shell outputs now have a different format

FEATURES:
- Terraform vars are now fed in via `-var-file` instead of individual `-var` commands. Should be able to leverage the new terraform data types to avoid intermediate serialization of array/hash data.
- Docker env variables can now be set by pointing at a DOCKER_ENV_FILE, which is fed to underlying docker commands via `-env-file`

IMPROVEMENTS:
- Terraform: Nil vars should default to empty hash?

FIXES:

## 0.5.0 (October 5, 2016)

FEATURES:
- Better control of running terraform/packer subprocesses:
- Ctrl-C breaks out of Terraform/Packer commands (also suppresses the normal debugging output from packer/terraform when they're natively sent a SIGINT)
- Debug capability built into the rake task runs to manually confirm each step

IMPROVEMENTS:
- Allow packer stacks to standalone
- Finer grain control around terraform exit-codes and when to abort the rake task

FIXES:
- Non-zero error codes now return properly from rake tasks (if the
underlying CLI command did not choose to ignore error codes)
- Packer runs generate temp JSON files in the packer module directory,
allows the use of `{{ template_dir }}` for script locations.

## 0.4.3 (September 27, 2016)
FIXES:
- Handle new terraform API output formats for remote inputs
Expand Down
15 changes: 8 additions & 7 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
PATH
remote: .
specs:
covalence (0.4.3)
covalence (0.5.0)
activemodel (~> 4.2.6)
activesupport (~> 4.2.6)
aws-sdk (~> 2.3.8)
deep_merge (~> 1.0.1)
hiera (~> 3.2.0)
highline (~> 1.6.0)
json (~> 1.8.3)
rake (>= 11.1.2)
rest-client (~> 2.0.0.rc3)
Expand Down Expand Up @@ -39,7 +40,7 @@ GEM
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
builder (3.2.2)
byebug (9.0.5)
byebug (9.0.6)
ci_reporter (2.0.0)
builder (>= 2.1.2)
ci_reporter_rspec (1.0.0)
Expand Down Expand Up @@ -70,7 +71,7 @@ GEM
hashdiff (0.3.0)
hiera (3.2.1)
highline (1.6.21)
http-cookie (1.0.2)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (0.7.0)
ice_nine (0.11.2)
Expand All @@ -84,7 +85,7 @@ GEM
multipart-post (2.0.0)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (3.1.1)
net-ssh (3.2.0)
net-telnet (0.1.1)
netrc (0.10.3)
rake (11.3.0)
Expand All @@ -110,19 +111,19 @@ GEM
rspec-support (3.4.1)
safe_yaml (1.0.4)
semantic (1.4.1)
serverspec (2.36.0)
serverspec (2.36.1)
multi_json
rspec (~> 3.0)
rspec-its
specinfra (~> 2.53)
sfl (2.2)
sfl (2.3)
simplecov (0.12.0)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
slop (4.4.1)
specinfra (2.57.5)
specinfra (2.63.1)
net-scp
net-ssh (>= 2.7, < 4.0)
net-telnet
Expand Down
6 changes: 3 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
- individual state-stores: would be nice if it had something to describe the backing input types
- stack caching opportunities if they're called multiple times in the rake tasks
- consider httparty vs rest-client
- Use the invalid yaml syntax to figure out more things in rake tasks that needs to be lazy loaded
- Like the idea of maybe splitting out the syntax elements to a different gem. Gem installation & management would become easier
- missing spec around reports?
- Look into muting the reporter as dev default: https://github.com/ci-reporter/ci_reporter
- Look into getting HieraDB wrapper to read .yml files
- Terraform: Nil vars should default to empty hash?
- Terraform CLI in docker probably isn't going anywhere, might need to figure out a nicer way of handling this
- Consider automatically feeding in env values into docker as -env-file
- Use covalence to do verification on the open source stacks:
- terraform-aws-openvpn
- Plugin architecture
- rollup tasks in terraform need to check terraform only tasks
- stack_repository: backfill packer related tests
1 change: 1 addition & 0 deletions covalence.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "activemodel", "~> 4.2.6"
spec.add_dependency "semantic", "~> 1.4.1"
spec.add_dependency "slop", "~> 4.4.1"
spec.add_dependency "highline", "~> 1.6.0"

Covalence::Helpers::SpecDependencies.dependencies.each do |name, requirement|
spec.add_development_dependency name, requirement
Expand Down
12 changes: 9 additions & 3 deletions lib/covalence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ module Covalence
TEST_ENVS = (ENV['COVALENCE_TEST_ENVS'] || ENV['PROMETHEUS_TEST_ENVS'] || "").split(',')

# should be able to deprecate this with covalence bundled inside the container
TF_IMG = ENV['TERRAFORM_IMG'] || ""
TF_CMD = ENV['TERRAFORM_CMD'] || "terraform"
TERRAFORM_VERSION = ENV['TERRAFORM_VERSION'] || `#{TF_CMD} #{TF_IMG} version`.split("\n", 2)[0].gsub('Terraform v','')
TERRAFORM_IMG = ENV['TERRAFORM_IMG'] || ""
TERRAFORM_CMD = ENV['TERRAFORM_CMD'] || "terraform"
TERRAFORM_VERSION = ENV['TERRAFORM_VERSION'] || `#{TERRAFORM_CMD} #{TERRAFORM_IMG} version`.split("\n", 2)[0].gsub('Terraform v','')

# No-op shell command. Should not need to modify for most unix shells.
DRY_RUN_CMD = (ENV['COVALENCE_DRY_RUN_CMD'] || ":")
DEBUG_CLI = (ENV['COVALENCE_DEBUG'] || 'false') =~ (/(true|t|yes|y|1)$/i)

#DOCKER_ENV_FILE

# Internal constants
GEM_ROOT = File.expand_path('covalence', File.dirname(__FILE__)).freeze
Expand Down
146 changes: 115 additions & 31 deletions lib/covalence/core/cli_wrappers/popen_wrapper.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,122 @@
# A WIP, this will eventually wrap Open3#popen3 to provide better cli control
#
#require 'open3'
require 'open3'
require 'highline'

require_relative '../../../covalence'

module Covalence
class PopenWrapper
def self.run(cmds, path, args, dry_run: false)
# TODO: terraform cmd env switch
#cmd = "terraform #{[*cmds].join(' ')}"

# TODO: implement path prefix for the docker runs, see @tf_cmd
cmd_string = [*cmds]
cmd_string += [*args] unless args.blank?
cmd_string << path unless path.blank?

#TODO debug command string maybe
#TODO debug command args maybe

# TODO: read through http://ruby-doc.org/stdlib-2.0.0/libdoc/tmpdir/rdoc/Dir.html to see how to make this optionally persist
#stdout, stderr, status = Open3.capture3(ENV, "terraform", *cmd_string, chdir: tmp_dir)
# TODO: cmd escape issues with -var.
#stdout, stderr, status = Open3.capture3(ENV, cmd_string.join(' '), chdir: path)
#if status != 0
#raise RuntimeError, "Command failed with status (#{status}): \n#{stderr}\n#{stdout}"
#else
#puts stdout
#stdout.strip
#end

run_cmd = cmd_string.join(' ')
Covalence::LOGGER.warn "---"
Covalence::LOGGER.warn run_cmd

Kernel.system(ENV.to_h, run_cmd) unless dry_run
class << self

def run(cmds, path, args,
stdin_io: STDIN,
stdout_io: STDOUT,
stderr_io: STDERR,
debug: Covalence::DEBUG_CLI,
dry_run: false,
ignore_exitcode: false)

# TODO: implement path prefix for the docker runs, see @tf_cmd
cmd_string = [*cmds]
# TODO: cmd escape issues with -var.
cmd_string += [*args] unless args.blank?
cmd_string << path unless path.blank?

#TODO debug command string maybe
#TODO debug command args maybe

run_cmd = cmd_string.join(' ')
print_cmd_string(run_cmd)
if dry_run
run_cmd = Covalence::DRY_RUN_CMD
end

if debug
return unless HighLine.new.agree('Execute? [y/n]')
end

spawn_subprocess(ENV, run_cmd, {
stdin_io: stdin_io,
stdout_io: stdout_io,
stderr_io: stderr_io,
ignore_exitcode: ignore_exitcode
})
end

private
def print_cmd_string(cmd_string)
Covalence::LOGGER.warn "---"
Covalence::LOGGER.warn cmd_string
end

def spawn_subprocess(env, run_cmd,
stdin_io: STDIN,
stdout_io: STDOUT,
stderr_io: STDERR,
ignore_exitcode: false)
Signal.trap("INT") {} #Disable parent process from exiting, orphaning the child fork below
wait_thread = nil

Open3.popen3(env, run_cmd) do |stdin, stdout, stderr, wait_thr|
mappings = { stdin_io => stdin, stdout => stdout_io, stderr => stderr_io }
wait_thread = wait_thr

Signal.trap("INT") {
Process.kill("INT", wait_thr.pid)
Process.wait(wait_thr.pid, Process::WNOHANG)

exit(wait_thr.value.exitstatus)
} # let SIGINT drop into the child process

handle_io_streams(mappings, stdin_io)
end

Signal.trap("INT") { exit } #Restore parent SIGINT

return if ignore_exitcode
exit(wait_thread.value.exitstatus) unless wait_thread.value.success?
end

def handle_io_streams(mappings, stdin_io)
inputs = mappings.keys
streams_ready_for_eof_check = []

until inputs.empty? || (inputs.size == 1 && inputs.first == stdin_io) do

readable_inputs, _ = IO.select(inputs, [], [])
streams_ready_for_eof_check = readable_inputs

streams_ready_for_eof_check.select(&:eof).each do |src|
Covalence::LOGGER.debug "Stopping redirection from an IO in EOF: " + src.inspect
# `select`ing an IO which has reached EOF blocks forever.
# So you have to delete such IO from the array of IOs to `select`.
inputs.delete src

# You must close the child process' STDIN immeditely after the parent's STDIN reached EOF,
# or some kinds of child processes never exit.
# e.g.) echo foobar | joumae run -- cat
# After the `echo` finished outputting `foobar`, you have to tell `cat` about that or `cat` will wait for more inputs forever.
mappings[src].close if src == stdin_io
end

break if inputs.empty? || (inputs.size == 1 && inputs.first == stdin_io)

readable_inputs.each do |input|
begin
data = input.read_nonblock(1024)
output = mappings[input]
output.write(data)
output.flush
rescue EOFError => e
Covalence::LOGGER.debug "Reached EOF: #{e}"
inputs.delete input
rescue Errno::EPIPE => e
Covalence::LOGGER.debug "Handled error: #{e}: io: #{input.inspect}"
inputs.delete input
end
end #readable_inputs
end #until inputs.empty?
end #handle_io_streams

end
end
end
24 changes: 12 additions & 12 deletions lib/covalence/core/cli_wrappers/terraform_cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,21 @@ def self.terraform_clean(path, dry_run: false, verbose: true)
end

def self.terraform_check_style(path)
if Covalence::TF_IMG.blank?
output, status = Open3.capture2e(ENV, Covalence::TF_CMD, "fmt", "-write=false", path)
if Covalence::TERRAFORM_IMG.blank?
output, status = Open3.capture2e(ENV, Covalence::TERRAFORM_CMD, "fmt", "-write=false", path)
else
output, status = Open3.capture2e(ENV, "#{Covalence::TF_CMD} -v #{path}:/path -w /path #{Covalence::TF_IMG} fmt -write=false")
output, status = Open3.capture2e(ENV, "#{Covalence::TERRAFORM_CMD} -v #{path}:/path -w /path #{Covalence::TERRAFORM_IMG} fmt -write=false")
end
return false unless status.success?
output = output.split("\n")
(output.size == 0)
end

def self.terraform_remote_config(path=Dir.pwd(), args: '')
if Covalence::TF_IMG.blank?
PopenWrapper.run([Covalence::TF_CMD, "remote", "config"], path, args)
if Covalence::TERRAFORM_IMG.blank?
PopenWrapper.run([Covalence::TERRAFORM_CMD, "remote", "config"], path, args)
else
PopenWrapper.run([Covalence::TF_CMD, "-v #{path}:/path -w /path #{Covalence::TF_IMG}", "remote", "config"], '', args)
PopenWrapper.run([Covalence::TERRAFORM_CMD, "-v #{path}:/path -w /path #{Covalence::TERRAFORM_IMG}", "remote", "config"], '', args)
end
end

Expand Down Expand Up @@ -79,11 +79,11 @@ def init_terraform_cmds(file)

next if respond_to?(terraform_cmd.to_sym)
define_singleton_method(terraform_cmd) do |path=Dir.pwd(), args: ''|
if Covalence::TF_IMG.blank?
PopenWrapper.run([Covalence::TF_CMD, cmd], path, args)
if Covalence::TERRAFORM_IMG.blank?
PopenWrapper.run([Covalence::TERRAFORM_CMD, cmd], path, args)
else
parent, base = docker_scope_path(path)
PopenWrapper.run([Covalence::TF_CMD, "-v #{parent}:/tf_base -w #{File.join('/tf_base', base)} #{Covalence::TF_IMG}", cmd], '', args)
PopenWrapper.run([Covalence::TERRAFORM_CMD, "-v #{parent}:/tf_base -w #{File.join('/tf_base', base)} #{Covalence::TERRAFORM_IMG}", cmd], '', args)
end
end
elsif sub_hash.is_a?(Hash)
Expand All @@ -92,11 +92,11 @@ def init_terraform_cmds(file)

next if respond_to?(terraform_cmd.to_sym)
define_singleton_method(terraform_cmd) do |path=Dir.pwd(), args: ''|
if Covalence::TF_IMG.blank?
PopenWrapper.run([Covalence::TF_CMD, cmd, sub_command], path, args)
if Covalence::TERRAFORM_IMG.blank?
PopenWrapper.run([Covalence::TERRAFORM_CMD, cmd, sub_command], path, args)
else
parent, base = docker_scope_path(path)
PopenWrapper.run([Covalence::TF_CMD, "-v #{parent}:/tf_base -w #{File.join('/tf_base', base)} #{Covalence::TF_IMG}", cmd, sub_command], '', args)
PopenWrapper.run([Covalence::TERRAFORM_CMD, "-v #{parent}:/tf_base -w #{File.join('/tf_base', base)} #{Covalence::TERRAFORM_IMG}", cmd, sub_command], '', args)
end
end
end
Expand Down
Loading

0 comments on commit 30e0a50

Please sign in to comment.