Skip to content

Commit

Permalink
feat: can-i-merge (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque authored Aug 29, 2023
1 parent 212b983 commit badb030
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 8 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,16 +444,24 @@ Options:
-l, [--latest=[TAG]]
# Use the latest pacticipant version. Optionally specify a TAG
to use the latest version with the specified tag.
[--to-environment=ENVIRONMENT]
# The environment into which the pacticipant(s) are to be
deployed
[--branch=BRANCH]
# The branch of the version for which you want to check the
verification results.
[--main-branch], [--no-main-branch]
# Use the latest version of the configured main branch of the
pacticipant as the version for which you want to check the
verification results
[--to-environment=ENVIRONMENT]
# The environment into which the pacticipant(s) are to be
deployed
[--to=TAG]
# The tag that represents the branch or environment of the
integrated applications for which you want to check the
verification result status.
[--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.
-o, [--output=OUTPUT]
# json or table
# Default: table
Expand Down
38 changes: 34 additions & 4 deletions lib/pact_broker/client/cli/matrix_commands.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
require "pact_broker/client/string_refinements"
module PactBroker
module Client
module CLI
module MatrixCommands
using PactBroker::Client::StringRefinements

def self.included(thor)
thor.class_eval do

desc "can-i-deploy", ""
desc "can-i-deploy", "Checks if the specified pacticipant version is safe to be deployed."
long_desc File.read(File.join(__dir__, "can_i_deploy_long_desc.txt"))

method_option :pacticipant, required: true, aliases: "-a", desc: "The pacticipant name. Use once for each pacticipant being checked."
method_option :version, required: false, aliases: "-e", desc: "The pacticipant version. Must be entered after the --pacticipant that it relates to."
method_option :ignore, required: false, desc: "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. The environment variable PACT_BROKER_CAN_I_DEPLOY_IGNORE may also be used to specify a pacticipant name to ignore, with commas to separate multiple pacticipant names if necessary."
method_option :latest, required: false, aliases: "-l", banner: "[TAG]", desc: "Use the latest pacticipant version. Optionally specify a TAG to use the latest version with the specified tag."
method_option :branch, required: false, desc: "The branch of the version for which you want to check the verification results.", default: nil
method_option :main_branch, required: false, type: :boolean, desc: "Use the latest version of the configured main branch of the pacticipant as the version for which you want to check the verification results", default: false
method_option :to_environment, required: false, banner: "ENVIRONMENT", desc: "The environment into which the pacticipant(s) are to be deployed", default: nil
method_option :branch, required: false, desc: "The branch of the version for which you want to check the verification results", default: nil
method_option :to, required: false, banner: "TAG", desc: "The tag that represents the branch or environment of the integrated applications for which you want to check the verification result status.", default: nil
method_option :ignore, required: false, desc: "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."
method_option :output, aliases: "-o", desc: "json or table", default: "table"
method_option :retry_while_unknown, banner: "TIMES", type: :numeric, default: 0, required: false, desc: "The number of times to retry while there is an unknown verification result (ie. the provider verification is likely still running)"
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"
Expand All @@ -40,6 +45,31 @@ def can_i_deploy(*ignored_but_necessary)
exit(can_i_deploy_exit_status) unless result.success
end

desc "can-i-merge", "Checks if the specified pacticipant version is safe to merge into the main branch."
long_desc "Checks if the specified pacticipant version is compatible with the configured main branch of each of the pacticipants with which it is integrated."
method_option :pacticipant, required: true, aliases: "-a", desc: "The pacticipant name. Use once for each pacticipant being checked."
method_option :version, required: false, aliases: "-e", desc: "The pacticipant version. Must be entered after the --pacticipant that it relates to."
method_option :output, aliases: "-o", desc: "json or table", default: "table"
method_option :retry_while_unknown, banner: "TIMES", type: :numeric, default: 0, required: false, desc: "The number of times to retry while there is an unknown verification result (ie. the provider verification is likely still running)"
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"
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_MERGE_DRY_RUN=true. This mode is useful when setting up your CI/CD pipeline for the first time, or in a 'break glass' situation where you need to knowingly deploy what Pact considers a breaking change. For the second scenario, it is recommended to use the environment variable and just set it for the build required to deploy that particular version, so you don't accidentally leave the dry run mode enabled."
shared_authentication_options

def can_i_merge(*ignored_but_necessary)
require "pact_broker/client/cli/version_selector_options_parser"
require "pact_broker/client/can_i_deploy"

validate_credentials
selectors = VersionSelectorOptionsParser.call(ARGV)
validate_can_i_deploy_selectors(selectors)
dry_run = options.dry_run || ENV["PACT_BROKER_CAN_I_MERGE_DRY_RUN"] == "true"
can_i_merge_options = { output: options.output, retry_while_unknown: options.retry_while_unknown, retry_interval: options.retry_interval, dry_run: dry_run, verbose: options.verbose }
result = CanIDeploy.call(selectors, { with_main_branches: true }, can_i_merge_options, pact_broker_client_options)
$stdout.puts result.message
$stdout.flush
exit(1) unless result.success
end

if ENV.fetch("PACT_BROKER_FEATURES", "").include?("verification_required")

method_option :pacticipant, required: true, aliases: "-a", desc: "The pacticipant name. Use once for each pacticipant being checked."
Expand All @@ -50,7 +80,7 @@ def can_i_deploy(*ignored_but_necessary)
method_option :output, aliases: "-o", desc: "json or table", default: "table"

shared_authentication_options
desc "verification-required", "Checks if there is a verification required between the given pacticipant versions"
desc "verification-required", "Checks if there is a verification required between the given pacticipant versions."
def verification_required(*ignored_but_necessary)
require "pact_broker/client/cli/version_selector_options_parser"
require "pact_broker/client/verification_required"
Expand Down Expand Up @@ -78,7 +108,7 @@ def can_i_deploy_exit_status
end
end

def validate_can_i_deploy_selectors selectors
def validate_can_i_deploy_selectors(selectors)
pacticipants_without_versions = selectors.select{ |s| s[:version].nil? && s[:latest].nil? && s[:tag].nil? && s[:branch].nil? }.collect{ |s| s[:pacticipant] }
raise ::Thor::RequiredArgumentMissingError, "The version must be specified using `--version VERSION`, `--branch BRANCH` `--latest`, `--latest TAG`, or `--all TAG` for pacticipant #{pacticipants_without_versions.join(", ")}" if pacticipants_without_versions.any?
end
Expand Down
8 changes: 8 additions & 0 deletions lib/pact_broker/client/cli/version_selector_options_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ def self.call words
when "--latest", "-l"
selectors << { pacticipant: nil } if selectors.empty?
selectors.last[:latest] = true
when "--main-branch"
selectors << { pacticipant: nil } if selectors.empty?
selectors.last[:main_branch] = true
selectors.last[:latest] = true
when /^\-/
nil
else
Expand All @@ -35,6 +39,10 @@ def self.call words
selectors << { pacticipant: nil } if selectors.empty?
selectors.last[:branch] = word
selectors.last[:latest] = true
when "--main-branch"
selectors << { pacticipant: nil } if selectors.empty?
selectors.last[:main_branch] = true
selectors.last[:latest] = true
when "--all"
selectors << { pacticipant: nil } if selectors.empty?
selectors.last[:tag] = word
Expand Down
5 changes: 4 additions & 1 deletion lib/pact_broker/client/matrix/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def query_options
if matrix_options[:to_tag]
opts[:latest] = 'true'
opts[:tag] = matrix_options[:to_tag]
elsif matrix_options[:with_main_branches]
opts[:latest] = 'true'
opts[:mainBranch] = 'true'
elsif selectors.size == 1 && !matrix_options[:to_environment]
opts[:latest] = 'true'
end
Expand All @@ -59,11 +62,11 @@ def convert_selector_hashes_to_params(selectors)
hash[:latest] = 'true' if selector[:latest]
hash[:tag] = selector[:tag] if selector[:tag]
hash[:branch] = selector[:branch] if selector[:branch]
hash[:mainBranch] = 'true' if selector[:main_branch]
end
end
end


def result_message
if json_output?
response_entity.response.raw_body
Expand Down
43 changes: 43 additions & 0 deletions spec/integration/can_i_merge_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require "pact_broker/client/cli/broker"

module PactBroker
module Client
module CLI
describe Broker do
before do
subject.options = OpenStruct.new(minimum_valid_options)
allow($stdout).to receive(:puts)
allow($stderr).to receive(:puts)
allow(Retry).to receive(:sleep)

stub_const("ARGV", %w[--pacticipant Foo --version 1])
stub_request(:get, "http://pact-broker/matrix?latest=true&latestby=cvp&mainBranch=true&q%5B%5D%5Bpacticipant%5D=Foo&q%5B%5D%5Bversion%5D=1").
with(
headers: {
'Accept'=>'application/hal+json',
}).
to_return(status: 200, body: File.read("spec/support/matrix.json"), headers: { "Content-Type" => "application/hal+json" })
end

let(:minimum_valid_options) do
{
broker_base_url: 'http://pact-broker',
output: 'table',
verbose: 'verbose',
retry_while_unknown: 1,
retry_interval: 2,
limit: 1000,
dry_run: false
}
end

let(:invoke_can_i_merge) { subject.can_i_merge }

it "sends a matrix query" do
expect($stdout).to receive(:puts).with(/Computer says yes/)
invoke_can_i_merge
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ module CLI
],[
["--branch", "main"],
[{ pacticipant: nil, branch: "main", latest: true }]
],[
["--pacticipant", "Foo", "--main-branch", "--pacticipant", "Bar", "--version", "1"],
[{ pacticipant: "Foo", main_branch: true, latest: true }, { pacticipant: "Bar", version: "1" }]
],[
["--main-branch"],
[{ pacticipant: nil, main_branch: true, latest: true }]
]
]

Expand Down

0 comments on commit badb030

Please sign in to comment.