diff --git a/README.md b/README.md index 664aaae..e1719f4 100644 --- a/README.md +++ b/README.md @@ -859,6 +859,38 @@ Options: Test the execution of a webhook +### Branches + +#### delete-branch + +``` +Usage: + pact-broker delete-branch --branch=BRANCH -a, --pacticipant=PACTICIPANT -b, --broker-base-url=BROKER_BASE_URL + +Options: + -a, --pacticipant=PACTICIPANT + # The name of the pacticipant that the branch belongs to. + --branch=BRANCH + # The pacticipant branch name. + [--error-when-not-found], [--no-error-when-not-found] + # Raise an error if the branch that is to be deleted is not + found. + # Default: true + -b, --broker-base-url=BROKER_BASE_URL + # The base URL of the Pact Broker + -u, [--broker-username=BROKER_USERNAME] + # Pact Broker basic auth username + -p, [--broker-password=BROKER_PASSWORD] + # Pact Broker basic auth password + -k, [--broker-token=BROKER_TOKEN] + # Pact Broker bearer token + -v, [--verbose], [--no-verbose] + # Verbose output. + # Default: false +``` + +Deletes a pacticipant branch. Does not delete the versions or pacts/verifications associated with the branch, but does make the pacts inaccessible for verification via consumer versions selectors or WIP pacts. + ### Tags #### create-version-tag diff --git a/doc/pacts/markdown/Pact Broker Client - Pact Broker.md b/doc/pacts/markdown/Pact Broker Client - Pact Broker.md index 3580889..39cb014 100644 --- a/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +++ b/doc/pacts/markdown/Pact Broker Client - Pact Broker.md @@ -38,6 +38,8 @@ * [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:latest-version_relation_exists_in_the_index_resource) given the pb:latest-version relation exists in the index resource +* [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:pacticipant-branch_relation_exists_in_the_index_resource) given the pb:pacticipant-branch relation exists in the index resource + * [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:pacticipant-version_and_pb:environments_relations_exist_in_the_index_resource) given the pb:pacticipant-version and pb:environments relations exist in the index resource * [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:publish-contracts_relations_exists_in_the_index_resource) given the pb:publish-contracts relations exists in the index resource @@ -76,6 +78,8 @@ * [A request to create a webhook with every possible event type](#a_request_to_create_a_webhook_with_every_possible_event_type_given_the_'Pricing_Service'_and_'Condor'_already_exist_in_the_pact-broker) given the 'Pricing Service' and 'Condor' already exist in the pact-broker +* [A request to delete a pacticipant branch](#a_request_to_delete_a_pacticipant_branch_given_a_branch_named_main_exists_for_pacticipant_Foo) given a branch named main exists for pacticipant Foo + * [A request to determine if Bar can be deployed with all Foo tagged prod, ignoring the verification for Foo version 3.4.5](#a_request_to_determine_if_Bar_can_be_deployed_with_all_Foo_tagged_prod,_ignoring_the_verification_for_Foo_version_3.4.5_given_provider_Bar_version_4.5.6_has_a_successful_verification_for_Foo_version_1.2.3_tagged_prod_and_a_failed_verification_for_version_3.4.5_tagged_prod) given provider Bar version 4.5.6 has a successful verification for Foo version 1.2.3 tagged prod and a failed verification for version 3.4.5 tagged prod * [A request to get the Pricing Service](#a_request_to_get_the_Pricing_Service_given_the_'Pricing_Service'_already_exists_in_the_pact-broker) given the 'Pricing Service' already exists in the pact-broker @@ -827,6 +831,33 @@ Pact Broker will respond with: } } ``` + +Given **the pb:pacticipant-branch relation exists in the index resource**, upon receiving **a request for the index resource** from Pact Broker Client, with +```json +{ + "method": "GET", + "path": "/", + "headers": { + "Accept": "application/hal+json" + } +} +``` +Pact Broker will respond with: +```json +{ + "status": 200, + "headers": { + "Content-Type": "application/hal+json;charset=utf-8" + }, + "body": { + "_links": { + "pb:pacticipant-branch": { + "href": "http://localhost:1234/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-{pacticipant}-{branch}" + } + } + } +} +``` Given **the pb:pacticipant-version and pb:environments relations exist in the index resource**, upon receiving **a request for the index resource** from Pact Broker Client, with ```json @@ -1766,6 +1797,20 @@ Pact Broker will respond with: } } ``` + +Given **a branch named main exists for pacticipant Foo**, upon receiving **a request to delete a pacticipant branch** from Pact Broker Client, with +```json +{ + "method": "DELETE", + "path": "/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-Foo-main" +} +``` +Pact Broker will respond with: +```json +{ + "status": 204 +} +``` Given **provider Bar version 4.5.6 has a successful verification for Foo version 1.2.3 tagged prod and a failed verification for version 3.4.5 tagged prod**, upon receiving **a request to determine if Bar can be deployed with all Foo tagged prod, ignoring the verification for Foo version 3.4.5** from Pact Broker Client, with ```json diff --git a/lib/pact_broker/client/branches/delete_branch.rb b/lib/pact_broker/client/branches/delete_branch.rb new file mode 100644 index 0000000..a57c5a2 --- /dev/null +++ b/lib/pact_broker/client/branches/delete_branch.rb @@ -0,0 +1,64 @@ +require "pact_broker/client/base_command" + +module PactBroker + module Client + module Branches + class DeleteBranch < PactBroker::Client::BaseCommand + + NOT_SUPPORTED_MESSAGE_PACT_BROKER = "This version of the Pact Broker does not support deleting branches. Please upgrade to version 2.108.0 or later." + NOT_SUPPORTED_MESSAGE_PACTFLOW = "This version of PactFlow does not support deleting branches. Please upgrade to the latest version." + + def initialize(params, options, pact_broker_client_options) + super + @pacticipant_name = params.fetch(:pacticipant) + @branch_name = params.fetch(:branch) + @error_when_not_found = params.fetch(:error_when_not_found) + end + + def do_call + check_if_command_supported + @deleted_resource = branch_link.delete + PactBroker::Client::CommandResult.new(success?, result_message) + end + + private + + attr_reader :pacticipant_name, :branch_name, :error_when_not_found, :deleted_resource + + def branch_link + index_resource._link("pb:pacticipant-branch").expand(pacticipant: pacticipant_name, branch: branch_name) + end + + def check_if_command_supported + unless index_resource.can?("pb:pacticipant-branch") + raise PactBroker::Client::Error.new(is_pactflow? ? NOT_SUPPORTED_MESSAGE_PACTFLOW : NOT_SUPPORTED_MESSAGE_PACT_BROKER) + end + end + + def success? + if deleted_resource.success? + true + elsif deleted_resource.response.status == 404 && !error_when_not_found + true + else + false + end + end + + def result_message + if deleted_resource.success? + green("Successfully deleted branch #{branch_name} of pacticipant #{pacticipant_name}") + elsif deleted_resource.response.status == 404 + if error_when_not_found + red("Could not delete branch #{branch_name} of pacticipant #{pacticipant_name} as it was not found") + else + green("Branch #{branch_name} of pacticipant #{pacticipant_name} not found") + end + else + red(deleted_resource.response.raw_body) + end + end + end + end + end +end diff --git a/lib/pact_broker/client/cli/branch_commands.rb b/lib/pact_broker/client/cli/branch_commands.rb new file mode 100644 index 0000000..a0b17f6 --- /dev/null +++ b/lib/pact_broker/client/cli/branch_commands.rb @@ -0,0 +1,40 @@ +module PactBroker + module Client + module CLI + module BranchCommands + def self.included(thor) + thor.class_eval do + method_option :pacticipant, required: true, aliases: "-a", desc: "The name of the pacticipant that the branch belongs to." + method_option :branch, required: true, desc: "The pacticipant branch name." + method_option :error_when_not_found, type: :boolean, default: true, desc: "Raise an error if the branch that is to be deleted is not found." + shared_authentication_options + + desc "delete-branch", "Deletes a pacticipant branch. Does not delete the versions or pacts/verifications associated with the branch, but does make the pacts inaccessible for verification via consumer versions selectors or WIP pacts." + + def delete_branch + require "pact_broker/client/branches/delete_branch" + + validate_credentials + params = { + pacticipant: options.pacticipant, + branch: options.branch, + error_when_not_found: options.error_when_not_found + } + + result = PactBroker::Client::Branches::DeleteBranch.call(params, {}, pact_broker_client_options) + $stdout.puts result.message + exit(1) unless result.success + end + + no_commands do + def validate_delete_branch_params + raise ::Thor::RequiredArgumentMissingError, "Pacticipant name cannot be blank" if options.pacticipant.strip.size == 0 + raise ::Thor::RequiredArgumentMissingError, "Pacticipant branch name cannot be blank" if options.branch.strip.size == 0 + end + end + end + end + end + end + end +end diff --git a/lib/pact_broker/client/cli/broker.rb b/lib/pact_broker/client/cli/broker.rb index 827c4fc..962a784 100644 --- a/lib/pact_broker/client/cli/broker.rb +++ b/lib/pact_broker/client/cli/broker.rb @@ -8,7 +8,7 @@ require "pact_broker/client/cli/version_commands" require "pact_broker/client/cli/webhook_commands" require "pact_broker/client/cli/matrix_commands" - +require "pact_broker/client/cli/branch_commands" module PactBroker module Client module CLI @@ -19,6 +19,7 @@ class Broker < CustomThor include PactBroker::Client::CLI::MatrixCommands include PactBroker::Client::CLI::PacticipantCommands include PactBroker::Client::CLI::VersionCommands + include PactBroker::Client::CLI::BranchCommands include PactBroker::Client::CLI::WebhookCommands ignored_and_hidden_potential_options_from_environment_variables diff --git a/script/update-cli-usage-in-readme.rb b/script/update-cli-usage-in-readme.rb index b69b1c5..f074ef0 100755 --- a/script/update-cli-usage-in-readme.rb +++ b/script/update-cli-usage-in-readme.rb @@ -23,6 +23,7 @@ [PactBroker::Client::CLI::Broker, "Matrix", %w[can-i-deploy can-i-merge]], [PactBroker::Client::CLI::Broker, "Pacticipants", %w[create-or-update-pacticipant describe-pacticipant list-pacticipants]], [PactBroker::Client::CLI::Broker, "Webhooks", %w[create-webhook create-or-update-webhook test-webhook]], + [PactBroker::Client::CLI::Broker, "Branches", %w[delete-branch]], [PactBroker::Client::CLI::Broker, "Tags", %w[create-version-tag]], [PactBroker::Client::CLI::Broker, "Versions", %w[describe-version create-or-update-version]], [PactBroker::Client::CLI::Broker, "Miscellaneous", %w[generate-uuid]] diff --git a/spec/lib/pact_broker/client/branches/delete_branch_spec.rb b/spec/lib/pact_broker/client/branches/delete_branch_spec.rb new file mode 100644 index 0000000..c6b8008 --- /dev/null +++ b/spec/lib/pact_broker/client/branches/delete_branch_spec.rb @@ -0,0 +1,103 @@ +require "pact_broker/client/branches/delete_branch" + +module PactBroker + module Client + module Branches + describe DeleteBranch do + before do + allow_any_instance_of(PactBroker::Client::Hal::HttpClient).to receive(:sleep) + allow_any_instance_of(PactBroker::Client::Hal::HttpClient).to receive(:default_max_tries).and_return(1) + end + + let(:params) do + { + pacticipant: "Foo", + branch: "main", + error_when_not_found: error_when_not_found + } + end + let(:options) do + { + verbose: verbose + } + end + let(:error_when_not_found) { true } + let(:pact_broker_base_url) { "http://example.org" } + let(:pact_broker_client_options) { { pact_broker_base_url: pact_broker_base_url } } + let(:response_headers) { { "Content-Type" => "application/hal+json"} } + let(:verbose) { false } + + before do + stub_request(:get, "http://example.org/").to_return(status: 200, body: index_response_body, headers: response_headers) + stub_request(:delete, "http://example.org/pacticipants/Foo/branches/main").to_return(status: delete_response_status, body: delete_response_body, headers: response_headers) + end + let(:delete_response_status) { 200 } + + let(:index_response_body) do + { + "_links" => { + "pb:pacticipant-branch" => { + "href" => "http://example.org/pacticipants/{pacticipant}/branches/{branch}" + } + } + }.to_json + end + + let(:delete_response_body) do + { "some" => "error message" }.to_json + end + + subject { DeleteBranch.call(params, options, pact_broker_client_options) } + + context "when the branch is deleted" do + it "returns a success result" do + expect(subject.success).to be true + expect(subject.message).to include "Successfully deleted branch main of pacticipant Foo" + end + end + + context "when there is a non-404 error" do + let(:delete_response_status) { 403 } + + it "returns an error result with the response body" do + expect(subject.success).to be false + expect(subject.message).to include "error message" + end + end + + context "when the branch is not found" do + let(:delete_response_status) { 404 } + + context "when error_when_not_found is true" do + it "returns an error" do + expect(subject.success).to be false + expect(subject.message).to include "Could not delete branch main of pacticipant Foo as it was not found" + end + end + + context "when error_when_not_found is false" do + let(:error_when_not_found) { false } + + it "return a success" do + expect(subject.success).to be true + expect(subject.message).to include "Branch main of pacticipant Foo not found" + end + end + end + + context "when deleting branches is not supported" do + let(:index_response_body) do + { + _links: {} + }.to_json + end + + it "returns an error" do + expect(subject.success).to be false + expect(subject.message).to include "not support" + end + end + end + end + end +end diff --git a/spec/pacts/pact_broker_client-pact_broker.json b/spec/pacts/pact_broker_client-pact_broker.json index f569dca..768bc4f 100644 --- a/spec/pacts/pact_broker_client-pact_broker.json +++ b/spec/pacts/pact_broker_client-pact_broker.json @@ -6,6 +6,49 @@ "name": "Pact Broker" }, "interactions": [ + { + "description": "a request for the index resource", + "providerState": "the pb:pacticipant-branch relation exists in the index resource", + "request": { + "method": "GET", + "path": "/", + "headers": { + "Accept": "application/hal+json" + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/hal+json;charset=utf-8" + }, + "body": { + "_links": { + "pb:pacticipant-branch": { + "href": "http://localhost:1234/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-{pacticipant}-{branch}" + } + } + }, + "matchingRules": { + "$.body._links.pb:pacticipant-branch.href": { + "match": "regex", + "regex": "http:\\/\\/.*{pacticipant}.*{branch}" + } + } + } + }, + { + "description": "a request to delete a pacticipant branch", + "providerState": "a branch named main exists for pacticipant Foo", + "request": { + "method": "DELETE", + "path": "/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-Foo-main" + }, + "response": { + "status": 204, + "headers": { + } + } + }, { "description": "a request to list the latest pacts", "providerState": "a pact between Condor and the Pricing Service exists", diff --git a/spec/service_providers/delete_branch_spec.rb b/spec/service_providers/delete_branch_spec.rb new file mode 100644 index 0000000..d085d00 --- /dev/null +++ b/spec/service_providers/delete_branch_spec.rb @@ -0,0 +1,68 @@ +require "service_providers/pact_helper" +require "pact_broker/client/branches/delete_branch" + +RSpec.describe "delete a branch", pact: true do + include_context "pact broker" + include PactBrokerPactHelperMethods + + let(:params) do + { + pacticipant: "Foo", + branch: "main", + error_when_not_found: true + } + end + + let(:options) do + { + verbose: verbose + } + end + + let(:pact_broker_base_url) { pact_broker.mock_service_base_url } + let(:pact_broker_client_options) { { pact_broker_base_url: pact_broker_base_url } } + let(:response_headers) { { "Content-Type" => "application/hal+json"} } + let(:verbose) { false } + + subject { PactBroker::Client::Branches::DeleteBranch.call(params, options, pact_broker_client_options) } + + def mock_index + pact_broker + .given("the pb:pacticipant-branch relation exists in the index resource") + .upon_receiving("a request for the index resource") + .with( + method: "GET", + path: '/', + headers: get_request_headers). + will_respond_with( + status: 200, + headers: pact_broker_response_headers, + body: { + _links: { + :'pb:pacticipant-branch' => { + href: placeholder_url_term("pb:pacticipant-branch", ["pacticipant", "branch"], pact_broker) + } + } + } + ) + end + + def mock_branch_delete_request + pact_broker + .given("a branch named main exists for pacticipant Foo") + .upon_receiving("a request to delete a pacticipant branch") + .with( + method: "DELETE", + path: placeholder_path("pb:pacticipant-branch", ["Foo", "main"]) + ) + .will_respond_with( + status: 204 + ) + end + + it "returns a success result" do + mock_index + mock_branch_delete_request + expect(subject.success).to be true + end +end diff --git a/spec/service_providers/pact_helper.rb b/spec/service_providers/pact_helper.rb index eb3d710..587e136 100644 --- a/spec/service_providers/pact_helper.rb +++ b/spec/service_providers/pact_helper.rb @@ -25,6 +25,7 @@ module PactBrokerPactHelperMethods + # Use this for the path in the Pact request expectation. # @param [String] relation eg "pb:pacticipant" # @param [Array] params eg ["Foo"] def placeholder_path(relation, params = [])