From 2f09ef350adf54c0d884feeaed924670435423c3 Mon Sep 17 00:00:00 2001 From: phanindra srungavarapu Date: Mon, 5 Aug 2024 18:59:48 +0530 Subject: [PATCH] feat/can-i-merge badge (#707) * Implementing the can-i-merge logic and can-i-merge build badge logic * Add badge resource * Fix test build failing due to inconsistent data setup Fixed rubocop warnings * Minor code cleanup * Trying to fix github build * Force installing docker compose, picked the current latest version But maybe it should be picking up based on latest available at runtime * Correctly using docker compose command --------- Co-authored-by: Phanindra Srungavarapu --- lib/pact_broker/api.rb | 1 + lib/pact_broker/api/paths.rb | 3 +- .../api/resources/can_i_merge_badge.rb | 39 ++++++++++++ lib/pact_broker/badges/service.rb | 14 +++++ lib/pact_broker/matrix/service.rb | 26 ++++++++ lib/pact_broker/test/test_data_builder.rb | 11 +++- .../test/run-rake-on-docker-compose-mysql.sh | 4 +- .../run-rake-on-docker-compose-postgres.sh | 4 +- .../api/resources/can_i_merge_badge_spec.rb | 61 +++++++++++++++++++ spec/lib/pact_broker/badges/service_spec.rb | 15 +++++ spec/lib/pact_broker/matrix/service_spec.rb | 40 ++++++++++++ 11 files changed, 211 insertions(+), 7 deletions(-) create mode 100644 lib/pact_broker/api/resources/can_i_merge_badge.rb create mode 100644 spec/lib/pact_broker/api/resources/can_i_merge_badge_spec.rb diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index 26a674a00..a9e5f9ef8 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -95,6 +95,7 @@ def self.build_api(application_context = PactBroker::ApplicationContext.default_ add ["pacticipants", :pacticipant_name, "latest-version", :tag], Api::Resources::LatestVersion, {resource_name: "latest_tagged_pacticipant_version"} add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to], Api::Resources::CanIDeployPacticipantVersionByTagToTag, { resource_name: "can_i_deploy_latest_tagged_version_to_tag" } add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to, "badge"], Api::Resources::CanIDeployPacticipantVersionByTagToTagBadge, { resource_name: "can_i_deploy_latest_tagged_version_to_tag_badge" } + add ["pacticipants", :pacticipant_name, "main-branch", "can-i-merge", "badge"], Api::Resources::CanIMergeBadge, { resource_name: "can_i_merge_badge" } add ["pacticipants", :pacticipant_name, "latest-version"], Api::Resources::LatestVersion, {resource_name: "latest_pacticipant_version"} add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number, "tags", :tag_name], Api::Resources::Tag, {resource_name: "pacticipant_version_tag"} add ["pacticipants", :pacticipant_name, "branches"], Api::Resources::PacticipantBranches, {resource_name: "pacticipant_branches"} diff --git a/lib/pact_broker/api/paths.rb b/lib/pact_broker/api/paths.rb index f1ab12e38..81ceb8443 100644 --- a/lib/pact_broker/api/paths.rb +++ b/lib/pact_broker/api/paths.rb @@ -3,11 +3,12 @@ module Api module Paths PACT_BADGE_PATH = %r{^/pacts/provider/[^/]+/consumer/.*/badge(?:\.[A-Za-z]+)?$}.freeze MATRIX_BADGE_PATH = %r{^/matrix/provider/[^/]+/latest/[^/]+/consumer/[^/]+/latest/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze + CAN_I_MERGE_BADGE_PATH = %r{^/pacticipants/[^/]+/main-branch/can-i-merge/badge(?:\.[A-Za-z]+)?$}.freeze CAN_I_DEPLOY_TAG_BADGE_PATH = %r{^/pacticipants/[^/]+/latest-version/[^/]+/can-i-deploy/to/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze CAN_I_DEPLOY_BRANCH_ENV_BADGE_PATH = %r{^/pacticipants/[^/]+/branches/[^/]+/latest-version/can-i-deploy/to-environment/[^/]+/badge(?:\.[A-Za-z]+)?$}.freeze VERIFICATION_RESULTS = %r{^/pacts/provider/[^/]+/consumer/[^/]+/pact-version/[^/]+/verification-results/[^/]+} - BADGE_PATHS = [PACT_BADGE_PATH, MATRIX_BADGE_PATH, CAN_I_DEPLOY_TAG_BADGE_PATH, CAN_I_DEPLOY_BRANCH_ENV_BADGE_PATH] + BADGE_PATHS = [PACT_BADGE_PATH, MATRIX_BADGE_PATH, CAN_I_DEPLOY_TAG_BADGE_PATH, CAN_I_DEPLOY_BRANCH_ENV_BADGE_PATH, CAN_I_MERGE_BADGE_PATH] extend self diff --git a/lib/pact_broker/api/resources/can_i_merge_badge.rb b/lib/pact_broker/api/resources/can_i_merge_badge.rb new file mode 100644 index 000000000..846724eda --- /dev/null +++ b/lib/pact_broker/api/resources/can_i_merge_badge.rb @@ -0,0 +1,39 @@ +require "pact_broker/api/resources/base_resource" +require "pact_broker/api/resources/badge_methods" + +module PactBroker + module Api + module Resources + class CanIMergeBadge < BaseResource + include BadgeMethods # This module contains all necessary webmachine methods for badge implementation + + def badge_url + if pacticipant.nil? + # if the pacticipant is nil, we return an error badge url + badge_service.error_badge_url("pacticipant", "not found") + elsif version.nil? + # when there is no main branch version, we return an error badge url + badge_service.error_badge_url("main branch version", "not found") + else + # we call badge_service to build the badge url + badge_service.can_i_merge_badge_url( + version_number: version.number, + deployable: results + ) + end + end + + private + + def results + # can_i_merge returns true or false if the main branch version is compatible with all the integrations + @results ||= matrix_service.can_i_merge(pacticipant: pacticipant, latest_main_branch_version: version) + end + + def version + @version ||= version_service.find_latest_version_from_main_branch(pacticipant) + end + end + end + end +end diff --git a/lib/pact_broker/badges/service.rb b/lib/pact_broker/badges/service.rb index 5a79a5e22..a835585bc 100644 --- a/lib/pact_broker/badges/service.rb +++ b/lib/pact_broker/badges/service.rb @@ -40,6 +40,20 @@ def can_i_deploy_badge_url(tag, environment_tag, label, deployable) build_shield_io_uri(title, status, color) end + def can_i_merge_badge_url(version_number: nil, deployable: nil) + title = "can-i-merge" + status = deployable ? "yes" : "no" + if deployable.nil? + color = "lightgrey" + status = "unknown" + else + color = deployable ? "brightgreen" : "red" + status = version_number + end + # left text is "can-i-merge", right text is the version number + build_shield_io_uri(title, status, color) + end + def error_badge_url(left_text, right_text) build_shield_io_uri(left_text, right_text, "lightgrey") end diff --git a/lib/pact_broker/matrix/service.rb b/lib/pact_broker/matrix/service.rb index 398b6c81c..687be95c5 100644 --- a/lib/pact_broker/matrix/service.rb +++ b/lib/pact_broker/matrix/service.rb @@ -22,6 +22,32 @@ def can_i_deploy(selectors, options = {}) QueryResultsWithDeploymentStatusSummary.new(query_results, DeploymentStatusSummary.new(query_results)) end + def can_i_merge(pacticipant_name: nil, pacticipant: nil, latest_main_branch_version: nil) + # first we find the pacticipant by name (or use the one passed in) if pacticipant is nil + if pacticipant.nil? + pacticipant = pacticipant_service.find_pacticipant_by_name(pacticipant_name) + raise PactBroker::Error.new("No pacticipant found with name '#{pacticipant_name}'") unless pacticipant + else + pacticipant_name = pacticipant.name + end + + # then we find the latest version from the main branch if not passed in + if latest_main_branch_version.nil? + latest_main_branch_version = version_service.find_latest_version_from_main_branch(pacticipant) + raise PactBroker::Error.new("No main branch version found for pacticipant '#{pacticipant_name}'") unless latest_main_branch_version + end + + selectors = PactBroker::Matrix::UnresolvedSelector.from_hash( + pacticipant_name: pacticipant_name, + pacticipant_version_number: latest_main_branch_version.number + ) + + options = { main_branch: true, latest: true, latestby: "cvp" } + query_results = can_i_deploy([selectors], options) + + query_results.deployable? + end + def find selectors, options = {} logger.info "Querying matrix", selectors: selectors, options: options matrix_repository.find(selectors, options) diff --git a/lib/pact_broker/test/test_data_builder.rb b/lib/pact_broker/test/test_data_builder.rb index 59a2270ea..b9126817d 100644 --- a/lib/pact_broker/test/test_data_builder.rb +++ b/lib/pact_broker/test/test_data_builder.rb @@ -165,9 +165,16 @@ def create_tag_with_hierarchy pacticipant_name, pacticipant_version, tag_name def create_pacticipant pacticipant_name, params = {} params.delete(:comment) + version_to_create = params.delete(:version) + repository_url = "https://github.com/#{params[:repository_namespace] || "example-organization"}/#{params[:repository_name] || pacticipant_name}" merged_params = { name: pacticipant_name, repository_url: repository_url }.merge(params) @pacticipant = PactBroker::Domain::Pacticipant.create(merged_params) + + version = create_pacticipant_version(version_to_create, @pacticipant) if version_to_create + main_branch = params[:main_branch] + PactBroker::Versions::BranchVersionRepository.new.add_branch(version, main_branch) if version && main_branch + self end @@ -639,8 +646,6 @@ def fixed_json_content(consumer_name, provider_name, differentiator) }.to_json end - private - def create_pacticipant_version(version_number, pacticipant, params = {}) params.delete(:comment) tag_names = [params.delete(:tag_names), params.delete(:tag_name)].flatten.compact @@ -665,6 +670,8 @@ def create_pacticipant_version(version_number, pacticipant, params = {}) version end + private + def create_deployed_version(uuid: , currently_deployed: , version:, environment_name: , target: nil, created_at: nil) env = find_environment(environment_name) @deployed_version = PactBroker::Deployments::DeployedVersionService.find_or_create(uuid, version, env, target) diff --git a/script/test/run-rake-on-docker-compose-mysql.sh b/script/test/run-rake-on-docker-compose-mysql.sh index e7a3e73c7..48d65faa5 100755 --- a/script/test/run-rake-on-docker-compose-mysql.sh +++ b/script/test/run-rake-on-docker-compose-mysql.sh @@ -1,8 +1,8 @@ #/bin/sh cleanup() { - docker-compose -f docker-compose-ci-mysql.yml down + docker compose -f docker-compose-ci-mysql.yml down } trap cleanup EXIT -docker-compose -f docker-compose-ci-mysql.yml up --exit-code-from tests --abort-on-container-exit --remove-orphans +docker compose -f docker-compose-ci-mysql.yml up --exit-code-from tests --abort-on-container-exit --remove-orphans diff --git a/script/test/run-rake-on-docker-compose-postgres.sh b/script/test/run-rake-on-docker-compose-postgres.sh index 6e88f9bf5..a7076f1ef 100755 --- a/script/test/run-rake-on-docker-compose-postgres.sh +++ b/script/test/run-rake-on-docker-compose-postgres.sh @@ -1,8 +1,8 @@ #/bin/sh cleanup() { - docker-compose -f docker-compose-ci-postgres.yml down + docker compose -f docker-compose-ci-postgres.yml down } trap cleanup EXIT -docker-compose -f docker-compose-ci-postgres.yml up --exit-code-from tests --abort-on-container-exit --remove-orphans +docker compose -f docker-compose-ci-postgres.yml up --exit-code-from tests --abort-on-container-exit --remove-orphans diff --git a/spec/lib/pact_broker/api/resources/can_i_merge_badge_spec.rb b/spec/lib/pact_broker/api/resources/can_i_merge_badge_spec.rb new file mode 100644 index 000000000..06faf32d4 --- /dev/null +++ b/spec/lib/pact_broker/api/resources/can_i_merge_badge_spec.rb @@ -0,0 +1,61 @@ +require "pact_broker/api/resources/can_i_merge_badge" +require "pact_broker/api/resources/base_resource" + +module PactBroker + module Api + module Resources + describe CanIMergeBadge do + before do + allow_any_instance_of(described_class).to receive(:badge_service).and_return(badge_service) + + allow(badge_service). to receive(:can_i_merge_badge_url).and_return("http://badge_url") + allow(badge_service). to receive(:error_badge_url).and_return("http://error_badge_url") + + allow_any_instance_of(CanIMergeBadge).to receive(:pacticipant).and_return(pacticipant) + allow_any_instance_of(CanIMergeBadge).to receive(:version).and_return(version) + allow_any_instance_of(CanIMergeBadge).to receive(:results).and_return(results) + end + + let(:branch_service) { class_double("PactBroker::Versions::BranchService").as_stubbed_const } + let(:badge_service) { class_double("PactBroker::Badges::Service").as_stubbed_const } + + let(:pacticipant) { double("pacticipant") } + let(:version) { double("version", number: "1") } + let(:results) { true } + + let(:path) { "/pacticipants/Foo/main-branch/can-i-merge/badge" } + + subject { get(path) } + + context "when everything is found" do + it "returns a 307" do + expect(subject.status).to eq 307 + end + + it "return the badge URL" do + expect(badge_service). to receive(:can_i_merge_badge_url).with(version_number: "1", deployable: true) + expect(subject.headers["Location"]).to eq "http://badge_url" + end + end + + context "when the pacticipant is not found" do + let(:pacticipant) { nil } + + it "returns an error badge URL" do + expect(badge_service).to receive(:error_badge_url).with("pacticipant", "not found") + expect(subject.headers["Location"]).to eq "http://error_badge_url" + end + end + + context "when the version is not found" do + let(:version) { nil } + + it "returns an error badge URL" do + expect(badge_service).to receive(:error_badge_url).with("main branch version", "not found") + expect(subject.headers["Location"]).to eq "http://error_badge_url" + end + end + end + end + end +end diff --git a/spec/lib/pact_broker/badges/service_spec.rb b/spec/lib/pact_broker/badges/service_spec.rb index f66d71152..209e88ad6 100644 --- a/spec/lib/pact_broker/badges/service_spec.rb +++ b/spec/lib/pact_broker/badges/service_spec.rb @@ -29,6 +29,21 @@ module Badges allow(Service).to receive(:logger).and_return(logger) end + describe "can_i_merge_badge_url" do + let(:version_number) { "abcd1234" } + let(:deployable) { true } + + subject { Service.can_i_merge_badge_url(version_number: version_number, deployable: deployable) } + context "when deployable is true" do + it { is_expected.to eq URI("https://img.shields.io/badge/can--i--merge-abcd1234-brightgreen.svg") } + end + + context "when deployable is false" do + let(:deployable) { false } + it { is_expected.to eq URI("https://img.shields.io/badge/can--i--merge-abcd1234-red.svg") } + end + end + describe "can_i_deploy_badge_url" do subject { Service.can_i_deploy_badge_url("main", "prod", nil, true) } diff --git a/spec/lib/pact_broker/matrix/service_spec.rb b/spec/lib/pact_broker/matrix/service_spec.rb index 277470bea..ac68c2597 100644 --- a/spec/lib/pact_broker/matrix/service_spec.rb +++ b/spec/lib/pact_broker/matrix/service_spec.rb @@ -4,6 +4,46 @@ module PactBroker module Matrix describe Service do + describe "can-i-merge" do + before do + td.create_consumer("A", main_branch: "main_branch", version: "1") + .create_provider("B", main_branch: "main_branch", version: "1") + .create_pact_with_hierarchy("A", "1", "B") + .create_verification(provider_version: "1", number: 1, success: false, branch: "main_branch") + .create_verification(provider_version: "1", number: 2, success: true, branch: "main_branch") + .create_verification(provider_version: "2", number: 3, success: true, branch: "dev") + end + + let(:pacticipant_name_param) { "B" } + + subject { Service.can_i_merge(pacticipant_name: pacticipant_name_param) } + + context "for pacticipant that has verification on it's main branch" do + let(:options) { + { + latest: true, + main_branch: true, + latestby: "cvp" + } + } + + let(:unresolved_selectors) { + [ + PactBroker::Matrix::UnresolvedSelector.new(pacticipant_name: "B", pacticipant_version_number: "1") + ] + } + + it "returns true because the mergeble status is true" do + expect(subject).to be_truthy + end + + it "calls the can_i_deploy method" do + expect(Service).to receive(:can_i_deploy).with(unresolved_selectors, options).and_call_original + subject + end + end + end + describe "validate_selectors" do before do allow(PactBroker::Deployments::EnvironmentService).to receive(:find_by_name).and_return(environment)