From e4fbd7663fca306b43c0ca25684e1f1aa450931a Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Tue, 17 Aug 2021 12:06:03 +1000 Subject: [PATCH] feat(clean): support keeping currently deployed and released versions, and the latest version for each branch --- lib/pact_broker/db/clean.rb | 8 +- lib/pact_broker/db/clean/selector.rb | 54 ++++++++ lib/pact_broker/domain/version.rb | 31 ++++- lib/pact_broker/matrix/unresolved_selector.rb | 5 +- spec/lib/pact_broker/db/clean_spec.rb | 4 +- spec/lib/pact_broker/domain/version_spec.rb | 128 +++++++++++++++++- 6 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 lib/pact_broker/db/clean/selector.rb diff --git a/lib/pact_broker/db/clean.rb b/lib/pact_broker/db/clean.rb index a2bd306b6..df3dd9496 100644 --- a/lib/pact_broker/db/clean.rb +++ b/lib/pact_broker/db/clean.rb @@ -2,6 +2,7 @@ require "pact_broker/project_root" require "pact_broker/pacts/latest_tagged_pact_publications" require "pact_broker/logging" +require "pact_broker/db/clean/selector" module PactBroker module DB @@ -24,7 +25,12 @@ def initialize database_connection, options = {} end def keep - options[:keep] || [PactBroker::Matrix::UnresolvedSelector.new(tag: true, latest: true), PactBroker::Matrix::UnresolvedSelector.new(latest: true)] + @keep ||= if options[:keep] + # Could be a Matrix::UnresolvedSelector from the docker image, convert it + options[:keep].collect { | unknown_thing | Selector.from_hash(unknown_thing.to_hash) } + else + [Selector.new(tag: true, latest: true), Selector.new(branch: true, latest: true), Selector.new(latest: true), Selector.new(deployed: true), Selector.new(released: true)] + end end def resolve_ids(query, column_name = :id) diff --git a/lib/pact_broker/db/clean/selector.rb b/lib/pact_broker/db/clean/selector.rb new file mode 100644 index 000000000..735ea5385 --- /dev/null +++ b/lib/pact_broker/db/clean/selector.rb @@ -0,0 +1,54 @@ +require "pact_broker/hash_refinements" + +module PactBroker + module DB + class Clean + class Selector + using PactBroker::HashRefinements + + ATTRIBUTES = [:pacticipant_name, :latest, :tag, :branch, :environment_name, :max_age, :deployed, :released, :main_branch] + + attr_accessor *ATTRIBUTES + + def initialize(attributes = {}) + attributes.each do | (name, value) | + instance_variable_set("@#{name}", value) if respond_to?(name) + end + @source_hash = attributes[:source_hash] + end + + def self.from_hash(hash) + standard_hash = hash.symbolize_keys.snakecase_keys + new_hash = standard_hash.slice(*ATTRIBUTES) + new_hash[:pacticipant_name] ||= standard_hash[:pacticipant] if standard_hash[:pacticipant] + new_hash[:environment_name] ||= standard_hash[:environment] if standard_hash[:environment] + new_hash[:source_hash] = hash + new(new_hash.compact) + end + + def to_hash + ATTRIBUTES.each_with_object({}) do | key, hash | + hash[key] = send(key) + end.compact + end + alias_method :to_h, :to_hash + + def to_json + (@source_hash || to_hash).to_json + end + + def currently_deployed? + !!deployed + end + + def currently_supported? + !!released + end + + def latest? + !!latest + end + end + end + end +end diff --git a/lib/pact_broker/domain/version.rb b/lib/pact_broker/domain/version.rb index 0e12b2f44..30eb38c21 100644 --- a/lib/pact_broker/domain/version.rb +++ b/lib/pact_broker/domain/version.rb @@ -115,6 +115,16 @@ def currently_supported_in_environment(environment_name, pacticipant_name) where(id: supported_version_query.select(:version_id)) end + def currently_deployed + deployed_version_query = PactBroker::Deployments::DeployedVersion.currently_deployed + where(id: deployed_version_query.select(:version_id)) + end + + def currently_supported + supported_version_query = PactBroker::Deployments::ReleasedVersion.currently_supported + where(id: supported_version_query.select(:version_id)) + end + def where_tag(tag) if tag == true join(:tags, Sequel[:tags][:version_id] => Sequel[first_source_alias][:id]) @@ -129,7 +139,19 @@ def where_tag(tag) end def where_branch(branch) - where(branch: branch) + if branch == true + exclude(branch: nil) + else + where(branch: branch) + end + end + + def for_main_branches + pacticipants_join = { + Sequel[:versions][:pacticipant_id] => Sequel[:pacticipants][:id], + Sequel[:pacticipants][:main_branch] => Sequel[:versions][:branch] + } + where(id: select(Sequel[:versions][:id]).join(:pacticipants, pacticipants_join)) end def where_number(number) @@ -161,10 +183,13 @@ def for_selector(selector) query = self query = query.where_pacticipant_name(selector.pacticipant_name) if selector.pacticipant_name query = query.currently_in_environment(selector.environment_name, selector.pacticipant_name) if selector.environment_name + query = query.currently_deployed if selector.respond_to?(:currently_deployed?) && selector.currently_deployed? + query = query.currently_supported if selector.respond_to?(:currently_supported?) && selector.currently_supported? query = query.where_tag(selector.tag) if selector.tag query = query.where_branch(selector.branch) if selector.branch - query = query.where_number(selector.pacticipant_version_number) if selector.pacticipant_version_number - query = query.where_age_less_than(selector.max_age) if selector.max_age + query = query.for_main_branches if selector.respond_to?(:main_branch) && selector.main_branch + query = query.where_number(selector.pacticipant_version_number) if selector.respond_to?(:pacticipant_version_number) && selector.pacticipant_version_number + query = query.where_age_less_than(selector.max_age) if selector.respond_to?(:max_age) && selector.max_age if selector.latest calculate_max_version_order_and_join_back_to_versions(query, selector) diff --git a/lib/pact_broker/matrix/unresolved_selector.rb b/lib/pact_broker/matrix/unresolved_selector.rb index e10af3b35..a938c0e24 100644 --- a/lib/pact_broker/matrix/unresolved_selector.rb +++ b/lib/pact_broker/matrix/unresolved_selector.rb @@ -10,7 +10,7 @@ def initialize(params = {}) end def self.from_hash(hash) - new(hash.symbolize_keys.snakecase_keys.slice(:pacticipant_name, :pacticipant_version_number, :latest, :tag, :branch, :environment_name, :max_age)) + new(hash.symbolize_keys.snakecase_keys.slice(:pacticipant_name, :pacticipant_version_number, :latest, :tag, :branch, :environment_name)) end def pacticipant_name @@ -26,7 +26,7 @@ def latest? end def overall_latest? - latest? && !tag && !max_age + latest? && !tag && !branch end def latest @@ -69,6 +69,7 @@ def pacticipant_version_number= pacticipant_version_number self[:pacticipant_version_number] = pacticipant_version_number end + # TODO delete this once docker image uses new selector class for clean def max_age= max_age self[:max_age] = max_age end diff --git a/spec/lib/pact_broker/db/clean_spec.rb b/spec/lib/pact_broker/db/clean_spec.rb index 47dfb9b27..4895ca183 100644 --- a/spec/lib/pact_broker/db/clean_spec.rb +++ b/spec/lib/pact_broker/db/clean_spec.rb @@ -14,8 +14,8 @@ def pact_publication_count_for(consumer_name, version_number) let(:db) { PactBroker::DB.connection } subject { Clean.call(PactBroker::DB.connection, options) } - let(:latest_dev_selector) { PactBroker::Matrix::UnresolvedSelector.new(tag: "dev", latest: true) } - let(:all_prod_selector) { PactBroker::Matrix::UnresolvedSelector.new(tag: "prod") } + let(:latest_dev_selector) { { tag: "dev", latest: true } } + let(:all_prod_selector) { { tag: "prod" } } describe ".call"do context "when there are specified versions to keep" do diff --git a/spec/lib/pact_broker/domain/version_spec.rb b/spec/lib/pact_broker/domain/version_spec.rb index 1468df41c..2d4765ffb 100644 --- a/spec/lib/pact_broker/domain/version_spec.rb +++ b/spec/lib/pact_broker/domain/version_spec.rb @@ -1,4 +1,5 @@ require "pact_broker/domain/version" +require "pact_broker/db/clean/selector" module PactBroker module Domain @@ -131,16 +132,87 @@ def version_numbers .create_consumer_version("3", tag_names: %w{master}) end - let(:selector) { PactBroker::Matrix::UnresolvedSelector.new(tag: "master", max_age: max_age) } + let(:selector) { PactBroker::DB::Clean::Selector.new(tag: "master", max_age: max_age) } let(:max_age) { 3 } let(:four_days_ago) { Date.today - 4 } it "selects the consumer versions younger than the max age" do + require 'pry'; pry(binding); expect(version_numbers).to eq %w{2 3} end end + context "when selecting the latest version for each branch" do + before do + td.create_consumer("Foo") + .create_consumer_version("1", branch: "main") + .create_consumer_version("2", branch: "main") + .create_consumer("Bar") + .create_consumer_version("3", branch: "main") + end + + let(:selector) { PactBroker::DB::Clean::Selector.new(branch: true, latest: true) } + + it "selects the consumer versions that are the latest for their branches" do + expect(version_numbers).to eq %w{2 3} + end + end + + context "when selecting all versions with a branch" do + before do + td.create_consumer("Foo") + .create_consumer_version("1", branch: "main") + .create_consumer_version("2") + .create_consumer("Bar") + .create_consumer_version("3", branch: "main") + end + + let(:selector) { PactBroker::DB::Clean::Selector.new(branch: true, latest: true) } + + it "selects the consumer versions that are the latest for their branches" do + expect(version_numbers).to eq %w{1 3} + end + end + + context "when selecting the latest versions from the main branches" do + before do + td.create_consumer("Foo", main_branch: "main") + .create_consumer_version("1", branch: "main") + .create_consumer_version("2", branch: "main") + .create_consumer_version("3", branch: "not-main") + .create_consumer("Bar", main_branch: "develop") + .create_consumer_version("4", branch: "develop") + .create_consumer_version("5", branch: "develop") + .create_consumer_version("6", branch: "main") + end + + let(:selector) { PactBroker::DB::Clean::Selector.new(main_branch: true, latest: true) } + + it "selects the consumer versions that are the latest for their branches" do + expect(version_numbers).to eq %w{2 5} + end + end + + context "when selecting all versions from the main branches" do + before do + td.create_consumer("Foo", main_branch: "main") + .create_consumer_version("1", branch: "main") + .create_consumer_version("2", branch: "main") + .create_consumer_version("3", branch: "not-main") + .create_consumer("Bar", main_branch: "develop") + .create_consumer_version("4", branch: "develop") + .create_consumer_version("5", branch: "develop") + .create_consumer_version("6", branch: "main") + end + + let(:selector) { PactBroker::DB::Clean::Selector.new(main_branch: true) } + + it "selects the consumer versions that are the latest for their branches" do + expect(version_numbers).to eq %w{1 2 4 5} + end + end + context "when selecting all versions of a pacticipant currently deployed to an environment" do let(:selector) { PactBroker::Matrix::UnresolvedSelector.new(environment_name: "prod", pacticipant_name: "Foo") } @@ -194,6 +266,60 @@ def version_numbers end end + context "when selecting all currently deployed versions" do + let(:selector) { PactBroker::DB::Clean::Selector.new(deployed: true) } + + before do + td.create_environment("test") + .create_consumer("Foo") + .create_consumer_version("1") + .create_deployed_version_for_consumer_version(target: "1") + .create_consumer_version("2") + .create_environment("prod") + .create_deployed_version_for_consumer_version(target: "2") + .create_consumer_version("3") + .create_consumer_version("5") + .create_consumer("Bar") + .create_consumer_version("10") + .create_deployed_version_for_consumer_version(target: "3") + .create_consumer_version("11") + .create_deployed_version_for_consumer_version(currently_deployed: false) + .create_consumer_version("12") + .create_released_version_for_consumer_version + end + + it "returns the versions that are currently deployed" do + expect(version_numbers).to eq %w{1 2 10} + end + end + + context "when selecting all currently released+supported versions" do + let(:selector) { PactBroker::DB::Clean::Selector.new(released: true) } + + before do + td.create_environment("test") + .create_consumer("Foo") + .create_consumer_version("1") + .create_released_version_for_consumer_version + .create_consumer_version("2") + .create_environment("prod") + .create_released_version_for_consumer_version + .create_consumer_version("3") + .create_consumer_version("5") + .create_consumer("Bar") + .create_consumer_version("10") + .create_released_version_for_consumer_version + .create_consumer_version("11") + .create_released_version_for_consumer_version(currently_supported: false) + .create_consumer_version("12") + .create_deployed_version_for_consumer_version + end + + it "returns the versions that are currently released+supported" do + expect(version_numbers).to eq %w{1 2 10} + end + end + context "selecting versions for a branch" do before do td.create_consumer("Foo")