From 887a9ca31435caf2a9992ff635e6dc3d2c14c8e5 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Fri, 25 Sep 2020 10:23:31 +1000 Subject: [PATCH] feat: add badges for can-i-deploy --- lib/pact_broker/api.rb | 1 + .../api/decorators/pacticipant_decorator.rb | 16 ++++ lib/pact_broker/api/pact_broker_urls.rb | 8 +- .../api/resources/can_i_deploy_badge.rb | 80 +++++++++++++++++++ .../api/resources/error_handler.rb | 8 +- lib/pact_broker/badges/service.rb | 11 +++ .../pacticipant/can-i-deploy-badge.markdown | 7 ++ spec/features/get_can_i_deploy_badge_spec.rb | 13 +++ spec/features/get_matrix_badge_spec.rb | 2 +- .../api/resources/can_i_deploy_badge_spec.rb | 71 ++++++++++++++++ 10 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 lib/pact_broker/api/resources/can_i_deploy_badge.rb create mode 100644 lib/pact_broker/doc/views/pacticipant/can-i-deploy-badge.markdown create mode 100644 spec/features/get_can_i_deploy_badge_spec.rb create mode 100644 spec/lib/pact_broker/api/resources/can_i_deploy_badge_spec.rb diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index fc391b08f..61c73b0c3 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -71,6 +71,7 @@ module PactBroker add ['pacticipants', :pacticipant_name, 'versions'], Api::Resources::Versions, {resource_name: "pacticipant_versions"} add ['pacticipants', :pacticipant_name, 'versions', :pacticipant_version_number], Api::Resources::Version, {resource_name: "pacticipant_version"} add ['pacticipants', :pacticipant_name, 'latest-version', :tag], Api::Resources::Version, {resource_name: "latest_tagged_pacticipant_version"} + add ['pacticipants', :pacticipant_name, 'latest-version', :tag, 'can-i-deploy', 'to', :to, 'badge'], Api::Resources::CanIDeployBadge, { resource_name: "can_i_deploy_badge" } add ['pacticipants', :pacticipant_name, 'latest-version'], Api::Resources::Version, {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, 'labels', :label_name], Api::Resources::Label, {resource_name: "pacticipant_label"} diff --git a/lib/pact_broker/api/decorators/pacticipant_decorator.rb b/lib/pact_broker/api/decorators/pacticipant_decorator.rb index f6361f992..4345941f6 100644 --- a/lib/pact_broker/api/decorators/pacticipant_decorator.rb +++ b/lib/pact_broker/api/decorators/pacticipant_decorator.rb @@ -58,6 +58,22 @@ class PacticipantDecorator < BaseDecorator } end + link :'pb:can-i-deploy-badge' do | options | + { + title: "Can I Deploy #{represented.name} badge", + href: templated_can_i_deploy_badge_url(represented.name, options[:base_url]), + templated: true + } + end + + curies do | options | + [{ + name: :pb, + href: options[:base_url] + '/doc/{rel}?context=pacticipant', + templated: true + }] + end + def to_hash options h = super dasherized = DasherizedVersionDecorator.new(represented).to_hash(options) diff --git a/lib/pact_broker/api/pact_broker_urls.rb b/lib/pact_broker/api/pact_broker_urls.rb index b5462b571..917c9bb9b 100644 --- a/lib/pact_broker/api/pact_broker_urls.rb +++ b/lib/pact_broker/api/pact_broker_urls.rb @@ -197,11 +197,15 @@ def tag_url base_url, tag end def templated_tag_url_for_pacticipant pacticipant_name, base_url = "" - pacticipant_url_from_params({pacticipant_name: pacticipant_name}, base_url) + "/versions/{version}/tags/{tag}" + pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/versions/{version}/tags/{tag}" end def templated_label_url_for_pacticipant pacticipant_name, base_url = "" - pacticipant_url_from_params({pacticipant_name: pacticipant_name}, base_url) + "/labels/{label}" + pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/labels/{label}" + end + + def templated_can_i_deploy_badge_url pacticipant_name, base_url = "" + pacticipant_url_from_params({ pacticipant_name: pacticipant_name }, base_url) + "/latest-version/{tag}/can-i-deploy/to/{environmentTag}/badge" end def label_url label, base_url diff --git a/lib/pact_broker/api/resources/can_i_deploy_badge.rb b/lib/pact_broker/api/resources/can_i_deploy_badge.rb new file mode 100644 index 000000000..f949a501d --- /dev/null +++ b/lib/pact_broker/api/resources/can_i_deploy_badge.rb @@ -0,0 +1,80 @@ +require 'pact_broker/matrix/can_i_deploy_query_schema' +require 'pact_broker/matrix/parse_can_i_deploy_query' + +module PactBroker + module Api + module Resources + class CanIDeployBadge < BaseResource + def initialize + super + selector = PactBroker::Matrix::UnresolvedSelector.new(pacticipant_name: pacticipant_name, latest: true, tag: identifier_from_path[:tag]) + @options = { + latestby: 'cvp', + latest: true, + tag: identifier_from_path[:to] + } + @selectors = [selector] + end + + def allowed_methods + ["GET", "OPTIONS"] + end + + def content_types_provided + [['image/svg+xml', :to_svg]] + end + + def resource_exists? + false + end + + # Only called if resource_exists? returns false + def previously_existed? + true + end + + def is_authorized?(authorization_header) + super || PactBroker.configuration.enable_public_badge_access + end + + def forbidden? + false + end + + def moved_temporarily? + response.headers['Cache-Control'] = 'no-cache' + begin + if pacticipant + if version + badge_service.can_i_deploy_badge_url(selectors.first.pacticipant_name, options[:tag], results.deployable?) + else + badge_service.error_badge_url("version", "not found") + end + else + badge_service.error_badge_url(selectors.first.pacticipant_name, "not found") + end + rescue StandardError => e + # Want to render a badge, even if there's an error + badge_service.error_badge_url("error", ErrorHandler.display_message(e, "reference: #{ErrorHandler.generate_error_reference}")) + end + end + + def policy_name + :'badges::badge' + end + + private + + attr_reader :selectors, :options + + def results + @results ||= matrix_service.find(selectors, options) + end + + def version + @version ||= version_service.find_by_pacticipant_name_and_latest_tag(identifier_from_path[:pacticipant_name], identifier_from_path[:tag]) + end + end + end + end +end diff --git a/lib/pact_broker/api/resources/error_handler.rb b/lib/pact_broker/api/resources/error_handler.rb index f69e8cad4..78417f35a 100644 --- a/lib/pact_broker/api/resources/error_handler.rb +++ b/lib/pact_broker/api/resources/error_handler.rb @@ -32,11 +32,11 @@ def self.log_as_warning?(e) PactBroker.configuration.warning_error_classes.any? { |clazz| e.is_a?(clazz) } end - def self.display_message(e, error_reference) + def self.display_message(e, obfuscated_message) if PactBroker.configuration.show_backtrace_in_error_response? - e.message || obfuscated_error_message(error_reference) + e.message || obfuscated_message else - reportable?(e) ? obfuscated_error_message(error_reference) : e.message + reportable?(e) ? obfuscated_message : e.message end end @@ -47,7 +47,7 @@ def self.obfuscated_error_message error_reference def self.response_body_hash e, error_reference response_body = { error: { - message: display_message(e, error_reference), + message: display_message(e, obfuscated_error_message(error_reference)), reference: error_reference } } diff --git a/lib/pact_broker/badges/service.rb b/lib/pact_broker/badges/service.rb index d09a30359..3bee1b6ec 100644 --- a/lib/pact_broker/badges/service.rb +++ b/lib/pact_broker/badges/service.rb @@ -33,6 +33,17 @@ def pact_verification_badge_url(pact, label, initials, pseudo_branch_verificatio build_shield_io_uri(title, status, color) end + def can_i_deploy_badge_url(pacticipant_name, environment_tag, deployable) + title = "Can I deploy #{pacticipant_name} to #{environment_tag}?" + status = deployable ? "yes" : "no" + color = deployable ? "brightgreen" : "red" + 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 + def clear_cache CACHE.clear end diff --git a/lib/pact_broker/doc/views/pacticipant/can-i-deploy-badge.markdown b/lib/pact_broker/doc/views/pacticipant/can-i-deploy-badge.markdown new file mode 100644 index 000000000..f5dce47a7 --- /dev/null +++ b/lib/pact_broker/doc/views/pacticipant/can-i-deploy-badge.markdown @@ -0,0 +1,7 @@ +# Can I Deploy Badge + +Allowed methods: `GET` + +Path: `/pacticipants/{pacticipant}/latest-version/{tag}/can-i-deploy/to/{environmentTag}/badge` + +Returns a status badge that can be displayed in a README file that indicates whether the specified version of a pacticipant can be deployed to the specified environment. diff --git a/spec/features/get_can_i_deploy_badge_spec.rb b/spec/features/get_can_i_deploy_badge_spec.rb new file mode 100644 index 000000000..40084844e --- /dev/null +++ b/spec/features/get_can_i_deploy_badge_spec.rb @@ -0,0 +1,13 @@ +RSpec.describe "can i deploy badge" do + before do + td.create_pact_with_hierarchy("Foo", "1.2.3", "Bar") + .create_consumer_version_tag("main") + end + + subject { get("/pacticipants/Foo/latest-version/main/can-i-deploy/to/prod/badge", nil, { 'HTTP_ACCEPT' => 'image/svg+xml'}) } + + it "returns a redirect response" do + expect(subject.status).to eq 307 + expect(subject.headers['Location']).to start_with "https://img.shields.io/badge" + end +end diff --git a/spec/features/get_matrix_badge_spec.rb b/spec/features/get_matrix_badge_spec.rb index 0c6806204..54f02aea0 100644 --- a/spec/features/get_matrix_badge_spec.rb +++ b/spec/features/get_matrix_badge_spec.rb @@ -24,7 +24,7 @@ # In the full app, the .svg extension is turned into an Accept header # by ConvertFileExtensionToAcceptHeader - subject { get path, nil, {'HTTP_ACCEPT' => "image/svg+xml"}; last_response } + subject { get(path, nil, {'HTTP_ACCEPT' => "image/svg+xml"}) } it "returns a 200 status" do expect(subject.status).to eq 200 diff --git a/spec/lib/pact_broker/api/resources/can_i_deploy_badge_spec.rb b/spec/lib/pact_broker/api/resources/can_i_deploy_badge_spec.rb new file mode 100644 index 000000000..ca8d1b16d --- /dev/null +++ b/spec/lib/pact_broker/api/resources/can_i_deploy_badge_spec.rb @@ -0,0 +1,71 @@ +require 'pact_broker/api/resources/can_i_deploy_badge' + +module PactBroker + module Api + module Resources + describe CanIDeployBadge do + before do + allow_any_instance_of(described_class).to receive(:badge_service).and_return(badge_service) + allow_any_instance_of(described_class).to receive(:matrix_service).and_return(matrix_service) + allow_any_instance_of(described_class).to receive(:pacticipant_service).and_return(pacticipant_service) + allow_any_instance_of(described_class).to receive(:version_service).and_return(version_service) + allow(badge_service).to receive(:can_i_deploy_badge_url).and_return("http://badge") + allow(badge_service).to receive(:error_badge_url).and_return("http://error") + allow(matrix_service).to receive(:find).and_return(results) + allow(pacticipant_service).to receive(:find_pacticipant_by_name).and_return(pacticipant) + allow(version_service).to receive(:find_by_pacticipant_name_and_latest_tag).and_return(version) + allow(PactBroker.configuration).to receive(:show_backtrace_in_error_response?).and_return(false) + allow(ErrorHandler).to receive(:generate_error_reference).and_return("abcd") + end + + let(:pacticipant_service) { class_double("PactBroker::Pacticipant::Service").as_stubbed_const } + let(:badge_service) { class_double("PactBroker::Badges::Service").as_stubbed_const } + let(:matrix_service) { class_double("PactBroker::Matrix::Service").as_stubbed_const } + let(:version_service) { class_double("PactBroker::Version::Service").as_stubbed_const } + let(:results) { double('results', deployable?: true) } + let(:pacticipant) { double('pacticipant') } + let(:version) { double('version') } + + let(:path) { "/pacticipants/Foo/latest-version/main/can-i-deploy/to/prod/badge"} + + subject { get(path) } + + context "when the pacticipant exists" do + it "returns a redirect to the badge" do + expect(badge_service).to receive(:can_i_deploy_badge_url).with("Foo", "prod", true) + expect(subject.status).to eq 307 + end + end + + context "when the pacticipant does not exist" do + let(:pacticipant) { nil } + + it "returns a redirect to a 'not found' badge" do + expect(badge_service).to receive(:error_badge_url).with("Foo", "not found") + expect(subject.status).to eq 307 + end + end + + context "when the version does not exist" do + let(:version) { nil } + + it "returns a redirect to a 'not found' badge" do + expect(badge_service).to receive(:error_badge_url).with("version", "not found") + expect(subject.status).to eq 307 + end + end + + context "when there is an error" do + before do + allow(matrix_service).to receive(:find).and_raise("foo error") + end + + it "returns a redirect to a badge with an error message" do + expect(badge_service).to receive(:error_badge_url).with("error", "reference: abcd") + expect(subject.status).to eq 307 + end + end + end + end + end +end