From 018a1294207fbf2939ab17b2c8556da76ea24a7d Mon Sep 17 00:00:00 2001 From: Flavio Auciello Date: Thu, 27 Apr 2017 12:04:28 +0200 Subject: [PATCH] Return non-zero exit codes when something goes wrong --- lib/pulsar.rb | 1 + lib/pulsar/cli.rb | 13 +++++++++ lib/pulsar/context_error.rb | 6 +++++ lib/pulsar/executor.rb | 18 +++++++++++++ lib/pulsar/validator.rb | 2 +- spec/features/deploy_spec.rb | 51 ++++++++++++++++++++++------------- spec/features/install_spec.rb | 18 ++++++++----- spec/features/list_spec.rb | 34 ++++++++++++++--------- spec/features/version_spec.rb | 8 +++--- 9 files changed, 111 insertions(+), 40 deletions(-) create mode 100644 lib/pulsar/executor.rb diff --git a/lib/pulsar.rb b/lib/pulsar.rb index 9b96218..9106df2 100644 --- a/lib/pulsar.rb +++ b/lib/pulsar.rb @@ -9,6 +9,7 @@ module Pulsar require 'fileutils' require 'pulsar/context_error' + require 'pulsar/executor' require 'pulsar/validator' require 'pulsar/interactors/cleanup' require 'pulsar/interactors/create_run_dirs' diff --git a/lib/pulsar/cli.rb b/lib/pulsar/cli.rb index e66e07c..7866493 100644 --- a/lib/pulsar/cli.rb +++ b/lib/pulsar/cli.rb @@ -5,6 +5,8 @@ module Pulsar class CLI < Thor + def self.exit_on_failure?; true; end + map %w[--version -v] => :__print_version desc 'install [DIRECTORY]', 'Install initial repository in DIRECTORY' @@ -20,9 +22,11 @@ def install(directory = './pulsar-conf') if result.success? puts 'Successfully created intial repo!' + exit_with_status 0 else puts 'Failed to create intial repo.' puts result.error + exit_with_status result.error.is_a?(Pulsar::ContextError) ? result.error.exit_code : 1 end end @@ -40,9 +44,11 @@ def list result.applications.each do |app, stages| puts "#{app}: #{stages.join(', ')}" end + exit_with_status 0 else puts 'Failed to list application and environments.' puts result.error + exit_with_status result.error.is_a?(Pulsar::ContextError) ? result.error.exit_code : 1 end end @@ -62,19 +68,26 @@ def deploy(application, environment) if result.success? puts "Deployed #{application} on #{environment}!" + exit_with_status 0 else puts "Failed to deploy #{application} on #{environment}." puts result.error + exit_with_status result.error.is_a?(Pulsar::ContextError) ? result.error.exit_code : 1 end end desc "--version, -v", "print the version" def __print_version puts Pulsar::VERSION + exit_with_status 0 end private + def exit_with_status(status) + exit status + end + def load_config Dotenv.load(PULSAR_CONF) # Load configurations for Pulsar end diff --git a/lib/pulsar/context_error.rb b/lib/pulsar/context_error.rb index 6cb1346..1ceb808 100644 --- a/lib/pulsar/context_error.rb +++ b/lib/pulsar/context_error.rb @@ -1,5 +1,11 @@ module Pulsar class ContextError < StandardError + attr_reader :exit_code + def initialize(obj, exit_code) + super obj + @exit_code = exit_code + end + def to_s backtrace ? backtrace.unshift(super).join("\n") : super end diff --git a/lib/pulsar/executor.rb b/lib/pulsar/executor.rb new file mode 100644 index 0000000..c439400 --- /dev/null +++ b/lib/pulsar/executor.rb @@ -0,0 +1,18 @@ +require 'open3' + +module Pulsar + module Executor + def self.sh(cmd) + stdin_stream, stdout_stream, stderr_stream, wait_thr = Open3.popen3(cmd) + stdout = stdout_stream.gets(nil) || "" + stderr = stderr_stream.gets(nil) || "" + stdout_stream.close + stderr_stream.gets(nil) + stderr_stream.close + stdin_stream.close + exit_code = wait_thr.value + yield exit_code, stdout, stderr if block_given? + { status: exit_code.exitstatus, output: stdout + stderr } + end + end +end diff --git a/lib/pulsar/validator.rb b/lib/pulsar/validator.rb index 532ad5f..f9666d4 100644 --- a/lib/pulsar/validator.rb +++ b/lib/pulsar/validator.rb @@ -13,7 +13,7 @@ def validate_context! end def context_fail!(msg) - context.fail! error: Pulsar::ContextError.new(msg) + context.fail! error: Pulsar::ContextError.new(msg, 1) end def validable_properties diff --git a/spec/features/deploy_spec.rb b/spec/features/deploy_spec.rb index b8f41f8..7021a12 100644 --- a/spec/features/deploy_spec.rb +++ b/spec/features/deploy_spec.rb @@ -1,40 +1,44 @@ require 'spec_helper' RSpec.describe 'Deploy' do - subject { -> { command } } + subject { command_output } let(:command) do - `DRY_RUN=true #{RSpec.configuration.pulsar_command} deploy #{options} #{arguments}` + Pulsar::Executor.sh("DRY_RUN=true #{RSpec.configuration.pulsar_command} deploy #{options} #{arguments}") end - - let(:repo) { RSpec.configuration.pulsar_conf_path } - let(:options) { "--conf-repo #{repo}" } - let(:app) { 'blog' } - let(:environment) { 'production' } - let(:arguments) { "#{app} #{environment}" } + let(:command_output) { command[:output] } + let(:exit_status) { command[:status] } + let(:repo) { RSpec.configuration.pulsar_conf_path } + let(:options) { "--conf-repo #{repo}" } + let(:app) { 'blog' } + let(:environment) { 'production' } + let(:arguments) { "#{app} #{environment}" } context 'via a subcommand named deploy' do let(:error) { /Could not find command/ } - it { is_expected.not_to output(error).to_stderr_from_any_process } + it { is_expected.not_to match(error) } + it { expect(exit_status).to eq(0) } end context 'requires a --conf-repo option' do let(:options) { nil } let(:error) { /No value provided for required options '--conf-repo'/ } - it { is_expected.to output(error).to_stderr_from_any_process } + it { is_expected.to match(error) } context 'can be specified via the alias -c' do let(:options) { "-c #{repo}" } - it { is_expected.not_to output(error).to_stderr_from_any_process } + it { is_expected.not_to match(error) } + it { expect(exit_status).to eq(0) } end context 'can be specified via the environment variable PULSAR_CONF_REPO' do before { ENV['PULSAR_CONF_REPO'] = repo } - it { is_expected.not_to output(error).to_stderr_from_any_process } + it { is_expected.not_to match(error) } + it { expect(exit_status).to eq(0) } end end @@ -43,11 +47,12 @@ let(:environment) { nil } let(:error) { /Usage: "pulsar deploy APPLICATION ENVIRONMENT"/ } - it { is_expected.to output(error).to_stderr_from_any_process } + it { is_expected.to match(error) } + it { expect(exit_status).to eq(1) } end context 'when succeeds' do - subject { command } + subject { command_output } context 'deploys an application on a environment in the pulsar configuration' do let(:output) { "Deployed blog on production!\n" } @@ -60,11 +65,12 @@ end it { is_expected.to match(output) } + it { expect(exit_status).to eq(0) } context 'leaves the tmp folder empty' do subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") } - before { command } + before { command_output } it { is_expected.to be_empty } end @@ -77,11 +83,12 @@ let(:output) { "Deployed your_app on staging!\n" } it { is_expected.to match(output) } + it { expect(exit_status).to eq(0) } context 'leaves the tmp folder empty' do subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") } - before { command } + before { command_output } it { is_expected.to be_empty } end @@ -94,11 +101,12 @@ let(:output) { "Deployed your_app on staging!\n" } it { is_expected.to match(output) } + it { expect(exit_status).to eq(0) } context 'leaves the tmp folder empty' do subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") } - before { command } + before { command_output } it { is_expected.to be_empty } end @@ -107,12 +115,13 @@ end context 'when fails' do - subject { command } + subject { command_output } context 'because of wrong directory' do let(:repo) { './some-wrong-directory' } it { is_expected.to match("Failed to deploy blog on production.\n") } + it { expect(exit_status).to eq(1) } end context 'because of empty directory' do @@ -120,18 +129,21 @@ it { is_expected.to match("Failed to deploy blog on production.\n") } it { is_expected.to match "No application found on repository #{RSpec.configuration.pulsar_empty_conf_path}\n" } + it { expect(exit_status).to eq(1) } end context 'because Bundler failed' do let(:repo) { RSpec.configuration.pulsar_wrong_bundle_conf_path } it { is_expected.to match("Failed to deploy blog on production.\n") } + it { expect(exit_status).to eq(1) } end context 'because Capistrano failed' do let(:repo) { RSpec.configuration.pulsar_wrong_cap_conf_path } it { is_expected.to match("Failed to deploy blog on production.\n") } + it { expect(exit_status).to eq(1) } end context 'because the application does not exists in the repository' do @@ -140,6 +152,7 @@ let(:environment) { 'staging' } it { is_expected.to match("The application foobuzz does not exist in your repository") } + it { expect(exit_status).to eq(1) } end context 'because the environment does not exists for the application' do @@ -148,11 +161,13 @@ let(:environment) { 'foobuzz' } it { is_expected.to match("The application blog does not have an environment called foobuzz") } + it { expect(exit_status).to eq(1) } context 'but \'no application error\' message takes precedence' do let(:app) { 'foobuzz' } it { is_expected.to match("The application foobuzz does not exist in your repository") } + it { expect(exit_status).to eq(1) } end end end diff --git a/spec/features/install_spec.rb b/spec/features/install_spec.rb index b2bb5d4..2bb1167 100644 --- a/spec/features/install_spec.rb +++ b/spec/features/install_spec.rb @@ -1,34 +1,39 @@ require 'spec_helper' RSpec.describe 'Install' do - subject { command } + subject { command_output } let(:command) do - `#{RSpec.configuration.pulsar_command} install #{arguments}` + Pulsar::Executor.sh("#{RSpec.configuration.pulsar_command} install #{arguments}") end + let(:command_output) { command[:output] } + let(:exit_status) { command[:status] } let(:arguments) { nil } context 'via a subcommand named install' do - subject { -> { command } } + subject { -> { command_output } } let(:error) { /Could not find command/ } it { is_expected.not_to output(error).to_stderr_from_any_process } + it { expect(exit_status).to eq(0) } end context 'when succeeds' do it { is_expected.to eql "Successfully created intial repo!\n" } context 'creates a directory' do - subject { -> { command } } + subject { -> { command_output } } + + it { expect(exit_status).to eq(0) } context 'with the basic configuration' do subject(:initial_pulsar_repo) do Dir.entries('./../../../lib/pulsar/generators/initial_repo/') end - before { command } + before { command_output } it 'contains the initial directory structure' do is_expected.to eql Dir.entries('./pulsar-conf') @@ -58,9 +63,10 @@ it { is_expected.to match "Failed to create intial repo.\n" } context 'does not create a directory' do - subject { -> { command } } + subject { -> { command_output } } it { is_expected.not_to change { File.exist?('./my-dir') }.from(false) } + it { expect(exit_status).to eq(1) } end end end diff --git a/spec/features/list_spec.rb b/spec/features/list_spec.rb index 4604ca9..974a67c 100644 --- a/spec/features/list_spec.rb +++ b/spec/features/list_spec.rb @@ -1,42 +1,47 @@ require 'spec_helper' RSpec.describe 'List' do - subject { -> { command } } + subject { command_output } let(:command) do - `#{RSpec.configuration.pulsar_command} list #{arguments}` + Pulsar::Executor.sh("#{RSpec.configuration.pulsar_command} list #{arguments}") end - + let(:command_output) { command[:output] } + let(:exit_status) { command[:status] } let(:repo) { RSpec.configuration.pulsar_conf_path } let(:arguments) { "--conf-repo #{repo}" } context 'via a subcommand named list' do let(:error) { /Could not find command/ } - it { is_expected.not_to output(error).to_stderr_from_any_process } + it { is_expected.not_to match(error) } + it { expect(exit_status).to eq(0) } end context 'requires a --conf-repo option' do let(:arguments) { nil } let(:error) { /No value provided for required options '--conf-repo'/ } - it { is_expected.to output(error).to_stderr_from_any_process } + it { is_expected.to match(error) } + it { expect(exit_status).to eq(1) } context 'can be specified via the alias -c' do let(:arguments) { "-c #{repo}" } - it { is_expected.not_to output(error).to_stderr_from_any_process } + it { is_expected.not_to match(error) } + it { expect(exit_status).to eq(0) } end context 'can be specified via the environment variable PULSAR_CONF_REPO' do before { ENV['PULSAR_CONF_REPO'] = repo } - it { is_expected.not_to output(error).to_stderr_from_any_process } + it { is_expected.not_to match(error) } + it { expect(exit_status).to eq(0) } end end context 'when succeeds' do - subject { command } + subject { command_output } context 'lists applications in the pulsar configuration' do let(:output) { "blog: production, staging\necommerce: staging\n" } @@ -49,11 +54,12 @@ end it { is_expected.to eql(output) } + it { expect(exit_status).to eq(0) } context 'leaves the tmp folder empty' do subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") } - before { command } + before { command_output } it { is_expected.to be_empty } end @@ -64,11 +70,12 @@ let(:output) { "your_app: production, staging\n" } it { is_expected.to eql output } + it { expect(exit_status).to eq(0) } context 'leaves the tmp folder empty' do subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") } - before { command } + before { command_output } it { is_expected.to be_empty } end @@ -79,11 +86,12 @@ let(:output) { "your_app: production, staging\n" } it { is_expected.to eql output } + it { expect(exit_status).to eq(0) } context 'leaves the tmp folder empty' do subject { Dir.glob("#{Pulsar::PULSAR_TMP}/*") } - before { command } + before { command_output } it { is_expected.to be_empty } end @@ -92,12 +100,13 @@ end context 'when fails' do - subject { command } + subject { command_output } context 'because of wrong directory' do let(:repo) { './some-wrong-directory' } it { is_expected.to match "Failed to list application and environments.\n" } + it { expect(exit_status).to eq(1) } end context 'because of empty directory' do @@ -105,6 +114,7 @@ it { is_expected.to match "Failed to list application and environments.\n" } it { is_expected.to match "No application found on repository #{RSpec.configuration.pulsar_empty_conf_path}\n" } + it { expect(exit_status).to eq(1) } end end end diff --git a/spec/features/version_spec.rb b/spec/features/version_spec.rb index 951929e..59816fd 100644 --- a/spec/features/version_spec.rb +++ b/spec/features/version_spec.rb @@ -1,17 +1,19 @@ require 'spec_helper' RSpec.describe 'Version' do - subject { command } + subject { command_output } let(:command) do - `#{RSpec.configuration.pulsar_command} #{arguments}` + Pulsar::Executor.sh("#{RSpec.configuration.pulsar_command} #{arguments}") end - + let(:command_output) { command[:output] } + let(:exit_status) { command[:status] } let(:arguments) { "--version" } context 'via a --version flag' do let(:version) { "#{Pulsar::VERSION}\n" } it { is_expected.to eql version } + it { expect(exit_status).to eq(0) } end end