From 34b145e811b5aa4d205fef7a0894a582e54cf146 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Tue, 22 Feb 2022 09:12:28 +1100 Subject: [PATCH] feat: add can-i-deploy endpoint for checking if the latest version for a branch can be deployed to a particular environment --- lib/pact_broker/api.rb | 10 ++-- ...cipant_version_by_branch_to_environment.rb | 50 +++++++++++++++++++ ...ploy_pacticipant_version_by_tag_to_tag.rb} | 31 ++++++------ lib/pact_broker/matrix/unresolved_selector.rb | 1 + lib/pact_broker/versions/repository.rb | 8 +++ lib/pact_broker/versions/service.rb | 4 ++ spec/features/can_i_deploy_spec.rb | 21 +++++++- ...t_version_by_branch_to_environment_spec.rb | 49 ++++++++++++++++++ ...pacticipant_version_by_tag_to_tag_spec.rb} | 27 ++++++++-- .../pact_broker/versions/repository_spec.rb | 18 +++++++ 10 files changed, 196 insertions(+), 23 deletions(-) create mode 100644 lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb rename lib/pact_broker/api/resources/{can_i_deploy_pacticipant_version.rb => can_i_deploy_pacticipant_version_by_tag_to_tag.rb} (51%) create mode 100644 spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_spec.rb rename spec/lib/pact_broker/api/resources/{can_i_deploy_pacticipant_version_spec.rb => can_i_deploy_pacticipant_version_by_tag_to_tag_spec.rb} (56%) diff --git a/lib/pact_broker/api.rb b/lib/pact_broker/api.rb index 63269194b..6ded9178a 100644 --- a/lib/pact_broker/api.rb +++ b/lib/pact_broker/api.rb @@ -81,15 +81,19 @@ def self.build_api(application_context = PactBroker::ApplicationContext.default_ add ["pacticipants"], Api::Resources::Pacticipants, {resource_name: "pacticipants"} add ["pacticipants", "label", :label_name], PactBroker::Api::Resources::PacticipantsForLabel, {resource_name: "pacticipants_for_label"} add ["pacticipants", :pacticipant_name], Api::Resources::Pacticipant, {resource_name: "pacticipant"} + add ["pacticipants", :pacticipant_name, "labels", :label_name], Api::Resources::Label, {resource_name: "pacticipant_label"} + + # Versions 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::LatestVersion, {resource_name: "latest_tagged_pacticipant_version"} - add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to], Api::Resources::CanIDeployPacticipantVersion, { resource_name: "can_i_deploy_latest_tagged_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", :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::CanIDeployBadge, { resource_name: "can_i_deploy_latest_tagged_version_to_tag_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, "labels", :label_name], Api::Resources::Label, {resource_name: "pacticipant_label"} add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions", :version_number], Api::Resources::BranchVersion, { resource_name: "branch_version" } + add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version", "can-i-deploy", "to-environment", :environment_name], Api::Resources::CanIDeployPacticipantVersionByBranchToEnvironment, { resource_name: "can_i_deploy_latest_branch_version_to_environment" } + #add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version", "can-i-deploy", "to-environment", :environment_name, "badge"], Api::Resources::CanIDeployPacticipantVersionByBranchToEnvironment, { resource_name: "can_i_deploy_latest_branch_version_to_environment_badge" } # Webhooks add ["webhooks", "provider", :provider_name, "consumer", :consumer_name ], Api::Resources::PacticipantWebhooks, {resource_name: "pacticipant_webhooks"} diff --git a/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb b/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb new file mode 100644 index 000000000..0f143a104 --- /dev/null +++ b/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb @@ -0,0 +1,50 @@ +require "pact_broker/api/resources/matrix" +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 CanIDeployPacticipantVersionByBranchToEnvironment < Matrix + def resource_exists? + !!(version && environment) + end + + def malformed_request? + false + end + + def policy_name + :'matrix::can_i_deploy' + end + + private + + def selectors + @selectors ||= [ + PactBroker::Matrix::UnresolvedSelector.new( + pacticipant_name: pacticipant_name, + latest: true, + branch: identifier_from_path[:branch_name] + ) + ] + end + + def options + @options ||= { + latestby: "cvp", + environment_name: identifier_from_path[:environment_name] + } + end + + def version + @version ||= version_service.find_latest_by_pacticipant_name_and_branch_name(identifier_from_path[:pacticipant_name], identifier_from_path[:branch_name]) + end + + def environment + @environment ||= environment_service.find_by_name(identifier_from_path[:environment_name]) + end + end + end + end +end diff --git a/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version.rb b/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag.rb similarity index 51% rename from lib/pact_broker/api/resources/can_i_deploy_pacticipant_version.rb rename to lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag.rb index 4d4e32c76..7e7a140ce 100644 --- a/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version.rb +++ b/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag.rb @@ -5,7 +5,7 @@ module PactBroker module Api module Resources - class CanIDeployPacticipantVersion < Matrix + class CanIDeployPacticipantVersionByTagToTag < Matrix def resource_exists? !!version end @@ -14,26 +14,29 @@ def policy_name :'matrix::can_i_deploy' end + def malformed_request? + false + end + private def selectors - @selectors ||= begin - [ - PactBroker::Matrix::UnresolvedSelector.new( - pacticipant_name: pacticipant_name, - latest: true, - tag: identifier_from_path[:tag] - ) - ] - end + @selectors ||= [ + PactBroker::Matrix::UnresolvedSelector.new( + pacticipant_name: pacticipant_name, + latest: true, + tag: identifier_from_path[:tag], + ) + ] + end def options @options ||= { - latestby: "cvp", - latest: true, - tag: identifier_from_path[:to] - } + latestby: "cvp", + latest: true, + tag: identifier_from_path[:to] + } end def version diff --git a/lib/pact_broker/matrix/unresolved_selector.rb b/lib/pact_broker/matrix/unresolved_selector.rb index a938c0e24..1a9ee5427 100644 --- a/lib/pact_broker/matrix/unresolved_selector.rb +++ b/lib/pact_broker/matrix/unresolved_selector.rb @@ -9,6 +9,7 @@ def initialize(params = {}) merge!(params) end + # TODO rename branch to branch_name def self.from_hash(hash) new(hash.symbolize_keys.snakecase_keys.slice(:pacticipant_name, :pacticipant_version_number, :latest, :tag, :branch, :environment_name)) end diff --git a/lib/pact_broker/versions/repository.rb b/lib/pact_broker/versions/repository.rb index c9e870e3e..bf35c9b5e 100644 --- a/lib/pact_broker/versions/repository.rb +++ b/lib/pact_broker/versions/repository.rb @@ -27,6 +27,14 @@ def find_by_pacticipant_name_and_latest_tag pacticipant_name, tag .first end + def find_latest_by_pacticipant_name_and_branch_name(pacticipant_name, branch_name) + branch_heads_join = { Sequel[:versions][:id] => Sequel[:branch_heads][:version_id], Sequel[:branch_heads][:branch_name] => branch_name } + PactBroker::Domain::Version + .where_pacticipant_name(pacticipant_name) + .join(:branch_heads, branch_heads_join) + .single_record + end + def find_by_pacticipant_name_and_tag pacticipant_name, tag PactBroker::Domain::Version .select_all_qualified diff --git a/lib/pact_broker/versions/service.rb b/lib/pact_broker/versions/service.rb index 77bb7a2bd..1e97a3920 100644 --- a/lib/pact_broker/versions/service.rb +++ b/lib/pact_broker/versions/service.rb @@ -22,6 +22,10 @@ def self.find_by_pacticipant_name_and_latest_tag(pacticipant_name, tag) version_repository.find_by_pacticipant_name_and_latest_tag(pacticipant_name, tag) end + def self.find_latest_by_pacticipant_name_and_branch_name(pacticipant_name, branch_name) + version_repository.find_latest_by_pacticipant_name_and_branch_name(pacticipant_name, branch_name) + end + def self.create_or_overwrite(pacticipant_name, version_number, version) pacticipant = pacticipant_repository.find_by_name_or_create(pacticipant_name) version = version_repository.create_or_overwrite(pacticipant, version_number, version) diff --git a/spec/features/can_i_deploy_spec.rb b/spec/features/can_i_deploy_spec.rb index a05957882..e197f97dd 100644 --- a/spec/features/can_i_deploy_spec.rb +++ b/spec/features/can_i_deploy_spec.rb @@ -1,6 +1,7 @@ RSpec.describe "can i deploy" do before do - td.create_pact_with_hierarchy("Foo", "1.2.3", "Bar") + td.publish_pact(consumer_name: "Foo", provider_name: "Bar", consumer_version_number: "1.2.3", tags: ["dev"], branch: "main") + .create_environment("prod") end let(:query) do @@ -20,6 +21,24 @@ expect(response_body[:matrix]).to be_instance_of(Array) end + context "using the URL format for tags" do + subject { get("/pacticipants/Foo/latest-version/dev/can-i-deploy/to/prod", nil, { "HTTP_ACCEPT" => "application/hal+json"}) } + + it "returns the matrix response" do + expect(subject).to be_a_hal_json_success_response + expect(response_body[:matrix]).to be_instance_of(Array) + end + end + + context "using the URL format for branch/environment" do + subject { get("/pacticipants/Foo/branches/main/latest-version/can-i-deploy/to-environment/prod", nil, { "HTTP_ACCEPT" => "application/hal+json"}) } + + it "returns the matrix response" do + expect(subject).to be_a_hal_json_success_response + expect(response_body[:matrix]).to be_instance_of(Array) + end + end + context "with a validation error" do let(:query) { {} } diff --git a/spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_spec.rb b/spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_spec.rb new file mode 100644 index 000000000..04cd5f169 --- /dev/null +++ b/spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_spec.rb @@ -0,0 +1,49 @@ +require "pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag" +require "pact_broker/matrix/service" + +module PactBroker + module Api + module Resources + describe CanIDeployPacticipantVersionByBranchToEnvironment do + include_context "stubbed services" + + before do + allow(PactBroker::Deployments::EnvironmentService).to receive(:find_by_name).and_return(environment) + allow(PactBroker::Matrix::Service).to receive(:can_i_deploy).and_return([]) + allow(pacticipant_service).to receive(:find_pacticipant_by_name).and_return(pacticipant) + allow(PactBroker::Api::Decorators::MatrixDecorator).to receive(:new).and_return(decorator) + allow(version_service).to receive(:find_latest_by_pacticipant_name_and_branch_name).and_return(version) + end + + let(:pacticipant) { double("pacticipant") } + let(:version) { double("version") } + let(:json_response_body) { JSON.parse(subject.body, symbolize_names: true) } + let(:decorator) { double("decorator", to_json: "response_body") } + let(:selectors) { double("selectors") } + let(:options) { double("options") } + let(:environment) { double("environment") } + let(:path) { "/pacticipants/Foo/branches/main/latest-version/can-i-deploy/to-environment/dev" } + + subject { get(path, nil, "Content-Type" => "application/hal+json") } + + it "looks up the by branch" do + expect(version_service).to receive(:find_latest_by_pacticipant_name_and_branch_name).with("Foo", "main") + subject + end + + it "checks if the version can be deployed to the environment" do + expect(PactBroker::Matrix::Service).to receive(:can_i_deploy).with(anything, hash_including(environment_name: "dev")) + subject + end + + it { is_expected.to be_a_hal_json_success_response } + + context "when the environment does not exist" do + let(:environment) { nil } + + its(:status) { is_expected.to eq 404 } + end + end + end + end +end diff --git a/spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_spec.rb b/spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag_spec.rb similarity index 56% rename from spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_spec.rb rename to spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag_spec.rb index 214e59966..ee43dcea0 100644 --- a/spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_spec.rb +++ b/spec/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag_spec.rb @@ -1,22 +1,20 @@ -require "pact_broker/api/resources/can_i_deploy_pacticipant_version" +require "pact_broker/api/resources/can_i_deploy_pacticipant_version_by_tag_to_tag" require "pact_broker/matrix/service" module PactBroker module Api module Resources - describe CanIDeployPacticipantVersion do + describe CanIDeployPacticipantVersionByTagToTag do include_context "stubbed services" before do allow(PactBroker::Matrix::Service).to receive(:can_i_deploy).and_return([]) 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::Api::Decorators::MatrixDecorator).to receive(:new).and_return(decorator) end let(:pacticipant) { double("pacticipant") } let(:version) { double("version") } - let(:path) { "/pacticipants/Foo/latest-version/main/can-i-deploy/to/prod" } let(:json_response_body) { JSON.parse(subject.body, symbolize_names: true) } let(:decorator) { double("decorator", to_json: "response_body") } let(:selectors) { double("selectors") } @@ -24,7 +22,26 @@ module Resources subject { get(path, nil, "Content-Type" => "application/hal+json") } - it { is_expected.to be_a_hal_json_success_response } + context "with tags" do + before do + allow(version_service).to receive(:find_by_pacticipant_name_and_latest_tag).and_return(version) + end + + let(:path) { "/pacticipants/Foo/latest-version/main/can-i-deploy/to/prod" } + + it "looks up the by tag" do + expect(version_service).to receive(:find_by_pacticipant_name_and_latest_tag).with("Foo", "main") + subject + end + + it { is_expected.to be_a_hal_json_success_response } + + context "when the version does not exist" do + let(:version) { nil } + + its(:status) { is_expected.to eq 404 } + end + end end end end diff --git a/spec/lib/pact_broker/versions/repository_spec.rb b/spec/lib/pact_broker/versions/repository_spec.rb index e57bc401d..4ec974524 100644 --- a/spec/lib/pact_broker/versions/repository_spec.rb +++ b/spec/lib/pact_broker/versions/repository_spec.rb @@ -7,6 +7,24 @@ module Versions let(:pacticipant_name) { "test_pacticipant" } let(:version_number) { "1.2.3" } + describe "#find_latest_by_pacticipant_name_and_branch_name" do + before do + td.create_consumer("Bar") + .create_consumer_version("2.3.4", branch: "prod") + .create_consumer("Foo") + .create_consumer_version("1.2.3", branch: "prod") + .create_consumer_version("2.3.4", branch: "prod") + .create_consumer_version("5.6.7") + end + + subject { Repository.new.find_latest_by_pacticipant_name_and_branch_name("Foo", "prod") } + + it "returns the most recent version from the specified branch for the specified pacticipant" do + expect(subject.number).to eq "2.3.4" + expect(subject.pacticipant.name).to eq "Foo" + end + end + describe "#find_by_pacticipant_name_and_latest_tag" do before do td.create_consumer("Bar")