diff --git a/README.md b/README.md index fd07ca88..3fb4689d 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ Options: # The pacticipant version. Must be entered after the --pacticipant that it relates to. [--ignore=IGNORE] # The pacticipant name to ignore. Use once for each pacticipant being ignored. - A specific version can be ignored by also specifying a --version after the pacticipant name option. + A specific version can be ignored by also specifying a --version after the + pacticipant name option. -l, [--latest=[TAG]] # Use the latest pacticipant version. Optionally specify a TAG to use the latest version with the specified tag. @@ -103,6 +104,9 @@ Options: # The time between retries in seconds. Use in conjuction with --retry-while-unknown # Default: 10 + [--dry-run], [--no-dry-run] + # When enabled, always exits process with a success code. Can also be enabled by setting + the environment variable PACT_BROKER_CAN_I_DEPLOY_DRY_RUN=true. -b, --broker-base-url=BROKER_BASE_URL # The base URL of the Pact Broker -u, [--broker-username=BROKER_USERNAME] @@ -113,14 +117,6 @@ Options: # Pact Broker bearer token -v, [--verbose], [--no-verbose] # Verbose output. Default: false - -Description: - Returns exit code 0 or 1, indicating whether or not the specified application (pacticipant) versions are - compatible (ie. safe to deploy). Prints out the relevant pact/verification details, indicating any - missing or failed verification results. - - The environment variables PACT_BROKER_BASE_URL, PACT_BROKER_USERNAME and PACT_BROKER_PASSWORD may be used - instead of their respective command line options. ``` Returns exit code 0 or 1, indicating whether or not the specified application (pacticipant) versions are compatible (ie. safe to deploy). Prints out the relevant pact/verification details, indicating any missing or failed verification results. diff --git a/lib/pact_broker/client/can_i_deploy.rb b/lib/pact_broker/client/can_i_deploy.rb index 0653d5d7..0ef0f08b 100644 --- a/lib/pact_broker/client/can_i_deploy.rb +++ b/lib/pact_broker/client/can_i_deploy.rb @@ -33,9 +33,9 @@ def initialize(pact_broker_base_url, version_selectors, matrix_options, options, def call create_result(fetch_matrix_with_retries) rescue PactBroker::Client::Error => e - Result.new(false, Term::ANSIColor.red(e.message)) + Result.new(dry_run_or_false, for_dry_run(Term::ANSIColor.red(e.message))) rescue StandardError => e - Result.new(false, "Error retrieving matrix. #{e.class} - #{e.message}\n#{e.backtrace.join("\n")}") + Result.new(dry_run_or_false, for_dry_run(Term::ANSIColor.red("Error retrieving matrix. #{e.class} - #{e.message}") + "\n#{e.backtrace.join("\n")}")) end private @@ -46,14 +46,15 @@ def create_result(matrix) if matrix.deployable? Result.new(true, success_message(matrix)) else - Result.new(false, failure_message(matrix)) + Result.new(dry_run_or_false, failure_message(matrix)) end end def success_message(matrix) message = format_matrix(matrix) if format != 'json' - message = warning(matrix) + Term::ANSIColor.green('Computer says yes \o/ ') + message + "\n\n" + notice_or_reason(matrix, :green) + message = warning(matrix) + computer_says(true) + message + "\n\n" + notice_or_reason(matrix, :green) + message = for_dry_run(message) end message end @@ -61,11 +62,28 @@ def success_message(matrix) def failure_message(matrix) message = format_matrix(matrix) if format != 'json' - message = warning(matrix) + Term::ANSIColor.red('Computer says no ¯\_(ツ)_/¯ ') + message + "\n\n" + notice_or_reason(matrix, :red) + message = warning(matrix) + computer_says(false) + message + "\n\n" + notice_or_reason(matrix, :red) + message = for_dry_run(message) end message end + def computer_says(success) + if success + if dry_run? + "Computer says yes \\o/ (and maybe you don't need to enable dry run)" + else + Term::ANSIColor.green('Computer says yes \o/ ') + end + else + if dry_run? + "Computer says no ¯\\_(ツ)_/¯ (but you're ignoring this by enabling dry run)" + else + Term::ANSIColor.red("Computer says no ¯\_(ツ)_/¯") + end + end + end + def notice_or_reason(matrix, reason_color) if matrix.notices PactBroker::Client::ColorizeNotices.call(matrix.notices).join("\n") @@ -114,6 +132,23 @@ def retry_while_unknown? options[:retry_while_unknown] > 0 end + def dry_run? + options[:dry_run] + end + + def dry_run_or_false + dry_run? || false + end + + def for_dry_run(lines) + if dry_run? + prefix = Term::ANSIColor.yellow("[dry-run] ") + lines.split("\n").collect { |line| prefix + Term::ANSIColor.uncolor(line) }.join("\n") + "\n" + prefix + "\n" + prefix + Term::ANSIColor.green("Dry run enabled - ignoring any failures") + else + lines + end + end + def retry_options { condition: lambda { |matrix| !matrix.any_unknown? }, diff --git a/lib/pact_broker/client/cli/broker.rb b/lib/pact_broker/client/cli/broker.rb index c5482d28..ccabb2ae 100644 --- a/lib/pact_broker/client/cli/broker.rb +++ b/lib/pact_broker/client/cli/broker.rb @@ -37,6 +37,7 @@ class Broker < CustomThor method_option :retry_interval, banner: 'SECONDS', type: :numeric, default: 10, required: false, desc: "The time between retries in seconds. Use in conjuction with --retry-while-unknown" # Allow limit to be set manually until https://github.com/pact-foundation/pact_broker-client/issues/53 is fixed method_option :limit, hide: true + method_option :dry_run, type: :boolean, default: false, desc: "When dry-run is enabled, always exit process with a success code. Can also be enabled by setting the environment variable PACT_BROKER_CAN_I_DEPLOY_DRY_RUN=true." shared_authentication_options def can_i_deploy(*ignored_but_necessary) @@ -51,7 +52,8 @@ def can_i_deploy(*ignored_but_necessary) [] end validate_can_i_deploy_selectors(selectors) - can_i_deploy_options = { output: options.output, retry_while_unknown: options.retry_while_unknown, retry_interval: options.retry_interval } + dry_run = options.dry_run || ENV["PACT_BROKER_CAN_I_DEPLOY_DRY_RUN"] == "true" + can_i_deploy_options = { output: options.output, retry_while_unknown: options.retry_while_unknown, retry_interval: options.retry_interval, dry_run: dry_run } result = CanIDeploy.call(options.broker_base_url, selectors, { to_tag: options.to, to_environment: options.to_environment, limit: options.limit, ignore_selectors: ignore_selectors }, can_i_deploy_options, pact_broker_client_options) $stdout.puts result.message $stdout.flush diff --git a/spec/fixtures/approvals/can_i_deploy_failure_dry_run.approved.txt b/spec/fixtures/approvals/can_i_deploy_failure_dry_run.approved.txt new file mode 100644 index 00000000..a13e6db9 --- /dev/null +++ b/spec/fixtures/approvals/can_i_deploy_failure_dry_run.approved.txt @@ -0,0 +1,7 @@ +[dry-run] Computer says no ¯\_(ツ)_/¯ (but you're ignoring this by enabling dry run) +[dry-run]  +[dry-run] text matrix +[dry-run]  +[dry-run] some reason +[dry-run]  +[dry-run] Dry run enabled - ignoring any failures \ No newline at end of file diff --git a/spec/fixtures/approvals/can_i_deploy_success_dry_run.approved.txt b/spec/fixtures/approvals/can_i_deploy_success_dry_run.approved.txt new file mode 100644 index 00000000..44bd325a --- /dev/null +++ b/spec/fixtures/approvals/can_i_deploy_success_dry_run.approved.txt @@ -0,0 +1,7 @@ +[dry-run] Computer says yes \o/ (and maybe you don't need to enable dry run) +[dry-run]  +[dry-run] text matrix +[dry-run]  +[dry-run] some reason +[dry-run]  +[dry-run] Dry run enabled - ignoring any failures \ No newline at end of file diff --git a/spec/lib/pact_broker/client/can_i_deploy_spec.rb b/spec/lib/pact_broker/client/can_i_deploy_spec.rb index a0d71c69..f5335cb7 100644 --- a/spec/lib/pact_broker/client/can_i_deploy_spec.rb +++ b/spec/lib/pact_broker/client/can_i_deploy_spec.rb @@ -8,6 +8,7 @@ module Client let(:version_selectors) { [{ pacticipant: "Foo", version: "1" }] } let(:matrix_options) { { } } let(:pact_broker_client_options) { { foo: 'bar' } } + let(:dry_run) { false } let(:matrix_client) { instance_double('PactBroker::Client::Matrix') } let(:matrix) do instance_double('Matrix::Resource', @@ -23,7 +24,7 @@ module Client let(:any_unknown) { unknown_count > 0 } let(:supports_unknown_count) { true } let(:retry_while_unknown) { 0 } - let(:options) { { output: 'text', retry_while_unknown: retry_while_unknown, retry_interval: 5 } } + let(:options) { { output: 'text', retry_while_unknown: retry_while_unknown, retry_interval: 5, dry_run: dry_run } } let(:notices) { nil } let(:supports_ignore) { true } let(:deployable) { true } @@ -69,6 +70,14 @@ module Client expect(subject.message).to include "some notice" end end + + context "when dry_run is enabled" do + let(:dry_run) { true } + + it "prefixes each line with [dry-run]" do + Approvals.verify(subject.message, :name => "can_i_deploy_success_dry_run", format: :txt) + end + end end context "when the versions are not deployable" do @@ -94,6 +103,18 @@ module Client expect(subject.message).to include "some notice" end end + + context "when dry_run is enabled" do + let(:dry_run) { true } + + it "returns a success response" do + expect(subject.success).to be true + end + + it "prefixes each line with [dry-run]" do + Approvals.verify(subject.message, :name => "can_i_deploy_failure_dry_run", format: :txt) + end + end end context "when retry_while_unknown is greater than 0" do @@ -135,6 +156,19 @@ module Client it "returns a failure message" do expect(subject.message).to match /does not provide a count/ end + + context "when dry_run is enabled" do + let(:dry_run) { true } + + it "returns a success response" do + expect(subject.success).to be true + end + + it "returns a failure message" do + expect(subject.message).to include "[dry-run]" + expect(subject.message).to match /does not provide a count/ + end + end end end @@ -169,6 +203,19 @@ module Client it "returns a failure message" do expect(subject.message).to include "error text" end + + context "when dry_run is enabled" do + let(:dry_run) { true } + + it "returns a success response" do + expect(subject.success).to be true + end + + it "returns a failure message" do + expect(subject.message).to include "[dry-run]" + expect(subject.message).to match /error text/ + end + end end context "when a StandardError is raised" do @@ -183,7 +230,20 @@ module Client end it "returns a failure message and backtrace" do - expect(subject.message).to include "Error retrieving matrix. StandardError - error text\n" + expect(subject.message).to include "Error retrieving matrix. StandardError - error text" + end + + context "when dry_run is enabled" do + let(:dry_run) { true } + + it "returns a success response" do + expect(subject.success).to be true + end + + it "returns a failure message" do + expect(subject.message).to include "[dry-run]" + expect(subject.message).to match /error text/ + end end end end diff --git a/spec/lib/pact_broker/client/cli/broker_can_i_deploy_spec.rb b/spec/lib/pact_broker/client/cli/broker_can_i_deploy_spec.rb index 98d7755d..2fcf56a5 100644 --- a/spec/lib/pact_broker/client/cli/broker_can_i_deploy_spec.rb +++ b/spec/lib/pact_broker/client/cli/broker_can_i_deploy_spec.rb @@ -25,7 +25,8 @@ module CLI verbose: 'verbose', retry_while_unknown: 1, retry_interval: 2, - limit: 1000 + limit: 1000, + dry_run: false } end @@ -37,7 +38,7 @@ module CLI end it "invokes the CanIDeploy service" do - expect(CanIDeploy).to receive(:call).with('http://pact-broker', version_selectors, { to_tag: nil, to_environment: nil, limit: 1000, ignore_selectors: []}, {output: 'table', retry_while_unknown: 1, retry_interval: 2}, { pact_broker_base_url: 'http://pact-broker', verbose: 'verbose' }) + expect(CanIDeploy).to receive(:call).with('http://pact-broker', version_selectors, { to_tag: nil, to_environment: nil, limit: 1000, ignore_selectors: []}, {output: 'table', retry_while_unknown: 1, retry_interval: 2, dry_run: false}, { pact_broker_base_url: 'http://pact-broker', verbose: 'verbose' }) invoke_can_i_deploy end @@ -94,6 +95,18 @@ module CLI end end + context "when PACT_BROKER_CAN_I_DEPLOY_DRY_RUN=true" do + before do + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]).with("PACT_BROKER_CAN_I_DEPLOY_DRY_RUN").and_return("true") + end + + it "invokes the CanIDeploy service with dry_run set to true" do + expect(CanIDeploy).to receive(:call).with(anything, anything, anything, hash_including(dry_run: true), anything) + invoke_can_i_deploy + end + end + context "when successful" do it "prints the message to stdout" do expect($stdout).to receive(:puts).with(message)