pact_broker 2.119.0 → 2.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +579 -479
  3. data/Gemfile +2 -6
  4. data/README.md +1 -2
  5. data/db/migrations/000015_create_pact_version_content.rb +1 -1
  6. data/db/migrations/000023_create_pact_versions_table.rb +1 -1
  7. data/db/migrations/000033_create_config_table.rb +1 -1
  8. data/db/migrations/000036_create_webhook_execution.rb +1 -1
  9. data/db/migrations/20180108_create_certificates_table.rb +1 -1
  10. data/db/migrations/migration_helper.rb +5 -1
  11. data/docs/CONFIGURATION.md +29 -2
  12. data/docs/matrix-optimisation/README.md +85 -0
  13. data/lib/pact/doc/interaction_view_model.rb +2 -1
  14. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +3 -1
  15. data/lib/pact/doc.rb +5 -0
  16. data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +2 -0
  17. data/lib/pact_broker/api/decorators/provider_states_decorator.rb +1 -1
  18. data/lib/pact_broker/api/decorators/triggered_webhook_logs_decorator.rb +4 -0
  19. data/lib/pact_broker/api/decorators/version_decorator.rb +29 -4
  20. data/lib/pact_broker/api/pact_broker_urls.rb +4 -0
  21. data/lib/pact_broker/api/resources/branch_versions.rb +5 -1
  22. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment.rb +4 -1
  23. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_badge.rb +1 -0
  24. data/lib/pact_broker/api/resources/clean.rb +9 -1
  25. data/lib/pact_broker/api/resources/deployed_or_released_versions.rb +78 -0
  26. data/lib/pact_broker/api/resources/environment_versions.rb +89 -0
  27. data/lib/pact_broker/api/resources/pacticipants_for_label.rb +29 -1
  28. data/lib/pact_broker/api/resources/tag_versions.rb +5 -1
  29. data/lib/pact_broker/api/resources/version.rb +5 -1
  30. data/lib/pact_broker/api/resources/versions.rb +5 -1
  31. data/lib/pact_broker/api.rb +2 -0
  32. data/lib/pact_broker/app.rb +0 -1
  33. data/lib/pact_broker/config/runtime_configuration.rb +19 -0
  34. data/lib/pact_broker/configuration.rb +15 -2
  35. data/lib/pact_broker/dataset.rb +4 -23
  36. data/lib/pact_broker/db/clean/branch_selector.rb +40 -0
  37. data/lib/pact_broker/db/clean.rb +45 -0
  38. data/lib/pact_broker/db/clean_incremental.rb +78 -14
  39. data/lib/pact_broker/db.rb +0 -7
  40. data/lib/pact_broker/deployments/released_version_service.rb +14 -0
  41. data/lib/pact_broker/domain/version.rb +6 -2
  42. data/lib/pact_broker/initializers/database_connection.rb +5 -0
  43. data/lib/pact_broker/integrations/repository.rb +1 -1
  44. data/lib/pact_broker/integrations/service.rb +1 -3
  45. data/lib/pact_broker/logging/open_telemetry_appender_setup.rb +73 -0
  46. data/lib/pact_broker/logging/trace_aware_json_formatter.rb +38 -0
  47. data/lib/pact_broker/matrix/matrix_row_dataset_module.rb +29 -11
  48. data/lib/pact_broker/matrix/matrix_row_verification_dataset_module.rb +23 -12
  49. data/lib/pact_broker/matrix/parse_can_i_deploy_query.rb +5 -0
  50. data/lib/pact_broker/matrix/selector_resolver.rb +30 -2
  51. data/lib/pact_broker/pacticipants/repository.rb +5 -2
  52. data/lib/pact_broker/pacts/service.rb +2 -2
  53. data/lib/pact_broker/tags/service.rb +4 -5
  54. data/lib/pact_broker/tasks/clean_task.rb +23 -1
  55. data/lib/pact_broker/verifications/repository.rb +1 -1
  56. data/lib/pact_broker/version.rb +1 -1
  57. data/lib/pact_broker/versions/branch_repository.rb +1 -1
  58. data/lib/pact_broker/versions/branch_service.rb +1 -1
  59. data/lib/pact_broker/versions/branch_version_repository.rb +3 -2
  60. data/lib/pact_broker/versions/repository.rb +4 -13
  61. data/lib/pact_broker/webhooks/render.rb +1 -1
  62. data/lib/sequel/plugins/upsert.rb +1 -8
  63. data/pact_broker.gemspec +8 -7
  64. metadata +38 -32
@@ -1,10 +1,12 @@
1
1
  require "pact_broker/api/resources/base_resource"
2
2
  require "pact_broker/api/decorators/pacticipants_decorator"
3
+ require "pact_broker/api/resources/pagination_methods"
3
4
 
4
5
  module PactBroker
5
6
  module Api
6
7
  module Resources
7
8
  class PacticipantsForLabel < BaseResource
9
+ include PaginationMethods
8
10
 
9
11
  def content_types_provided
10
12
  [["application/hal+json", :to_json]]
@@ -14,8 +16,18 @@ module PactBroker
14
16
  ["GET", "OPTIONS"]
15
17
  end
16
18
 
19
+ def malformed_request?
20
+ if super
21
+ true
22
+ elsif request.get? && validation_errors_for_schema?(schema, request.query)
23
+ true
24
+ else
25
+ false
26
+ end
27
+ end
28
+
17
29
  def to_json
18
- generate_json(pacticipant_service.find identifier_from_path )
30
+ generate_json(pacticipant_service.find_all_pacticipants(filter_options, pagination_options, eager_load_associations))
19
31
  end
20
32
 
21
33
  def generate_json pacticipants
@@ -25,6 +37,22 @@ module PactBroker
25
37
  def policy_name
26
38
  :'pacticipants::pacticipants'
27
39
  end
40
+
41
+ private
42
+
43
+ def schema
44
+ PactBroker::Api::Contracts::PaginationQueryParamsSchema
45
+ end
46
+
47
+ def eager_load_associations
48
+ decorator_class(:pacticipants_decorator).eager_load_associations
49
+ end
50
+
51
+ def filter_options
52
+ options = { label_name: identifier_from_path[:label_name] }
53
+ options[:query_string] = request.query["q"] if request.query.has_key?("q")
54
+ options
55
+ end
28
56
  end
29
57
  end
30
58
  end
@@ -30,7 +30,7 @@ module PactBroker
30
30
 
31
31
  def to_json
32
32
  decorator_class(:versions_decorator).new(tag_versions)
33
- .to_json(**decorator_options(deployed_versions: deployed_versions))
33
+ .to_json(**decorator_options(identifier_from_path.merge(deployed_versions: deployed_versions, released_versions: released_versions)))
34
34
  end
35
35
 
36
36
  private
@@ -39,6 +39,10 @@ module PactBroker
39
39
  @deployed_versions ||= deployed_version_service.find_deployed_versions_for_versions(tag_versions)
40
40
  end
41
41
 
42
+ def released_versions
43
+ @released_versions ||= released_version_service.find_released_versions_for_versions(tag_versions)
44
+ end
45
+
42
46
  def tag_versions
43
47
  @versions ||= version_service.find_by_ids_in_reverse_order(@tags.select_map(:version_id), pagination_options, decorator_class(:versions_decorator).eager_load_associations)
44
48
  end
@@ -54,7 +54,7 @@ module PactBroker
54
54
  end
55
55
 
56
56
  def to_json
57
- decorator_class(:version_decorator).new(version).to_json(**decorator_options(environments: environments, deployed_versions: deployed_versions))
57
+ decorator_class(:version_decorator).new(version).to_json(**decorator_options(environments: environments, deployed_versions: deployed_versions, released_versions: released_versions))
58
58
  end
59
59
 
60
60
  def delete_resource
@@ -87,6 +87,10 @@ module PactBroker
87
87
  @deployed_versions ||= deployed_version_service.find_deployed_versions_for_version(version)
88
88
  end
89
89
 
90
+ def released_versions
91
+ @released_versions ||= released_version_service.find_released_versions_for_version(version)
92
+ end
93
+
90
94
  def version
91
95
  @version ||= version_service.find_by_pacticipant_name_and_number(identifier_from_path)
92
96
  end
@@ -27,7 +27,7 @@ module PactBroker
27
27
  end
28
28
 
29
29
  def to_json
30
- decorator_class(:versions_decorator).new(versions).to_json(**decorator_options(identifier_from_path.merge(deployed_versions: deployed_versions)))
30
+ decorator_class(:versions_decorator).new(versions).to_json(**decorator_options(identifier_from_path.merge(deployed_versions: deployed_versions, released_versions: released_versions)))
31
31
  end
32
32
 
33
33
  def versions
@@ -38,6 +38,10 @@ module PactBroker
38
38
  @deployed_versions ||= deployed_version_service.find_deployed_versions_for_versions(versions)
39
39
  end
40
40
 
41
+ def released_versions
42
+ @released_versions ||= released_version_service.find_released_versions_for_versions(versions)
43
+ end
44
+
41
45
  def policy_name
42
46
  :'versions::versions'
43
47
  end
@@ -104,7 +104,9 @@ module PactBroker
104
104
 
105
105
  # Versions
106
106
  add ["pacticipants", :pacticipant_name, "versions"], Api::Resources::Versions, {resource_name: "pacticipant_versions"}
107
+ add ["pacticipants", :pacticipant_name, "deployed-or-released", "versions"], Api::Resources::DeployedOrReleasedVersions, {resource_name: "pacticipant_deployed_or_released_versions"}
107
108
  add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions"], Api::Resources::BranchVersions, {resource_name: "pacticipant_branch_versions"}
109
+ add ["pacticipants", :pacticipant_name, "environments", :environment_uuid, "versions"], Api::Resources::EnvironmentVersions, {resource_name: "pacticipant_environment_versions"}
108
110
  add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number], Api::Resources::Version, {resource_name: "pacticipant_version"}
109
111
  add ["pacticipants", :pacticipant_name, "latest-version", :tag], Api::Resources::LatestVersion, {resource_name: "latest_tagged_pacticipant_version"}
110
112
  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" }
@@ -148,7 +148,6 @@ module PactBroker
148
148
  configuration.database_connection ||= PactBroker.create_database_connection(configuration.database_configuration, configuration.logger)
149
149
  PactBroker::DB.connection = configuration.database_connection
150
150
  PactBroker::DB.validate_connection_config if configuration.validate_database_connection_config
151
- PactBroker::DB.set_mysql_strict_mode_if_mysql
152
151
  PactBroker::DB.connection.extension(:pagination)
153
152
  PactBroker::DB.connection.extension(:statement_timeout)
154
153
  PactBroker::DB.connection.extension(:any_not_empty)
@@ -40,6 +40,7 @@ module PactBroker
40
40
  log_stream: :file,
41
41
  log_level: :info,
42
42
  log_format: nil,
43
+ log_otel_enabled: :auto,
43
44
  warning_error_class_names: ["Sequel::ForeignKeyConstraintViolation"],
44
45
  hide_pactflow_messages: false,
45
46
  log_configuration_on_startup: true,
@@ -129,6 +130,10 @@ module PactBroker
129
130
  super(log_format&.to_sym)
130
131
  end
131
132
 
133
+ def log_otel_enabled= value
134
+ super(coerce_log_otel_enabled(value))
135
+ end
136
+
132
137
  def custom_log_formatters= custom_log_formatters
133
138
  super(custom_log_formatters&.symbolize_keys)
134
139
  end
@@ -215,12 +220,26 @@ module PactBroker
215
220
  if log_stream == :file && log_dir.blank?
216
221
  raise_validation_error("Must specify log_dir if log_stream is set to file")
217
222
  end
223
+
224
+ unless [:auto, true, false].include?(log_otel_enabled)
225
+ raise_validation_error("log_otel_enabled must be one of: auto, true, false")
226
+ end
218
227
  end
219
228
 
220
229
  def raise_validation_error(msg)
221
230
  raise PactBroker::ConfigurationError, msg
222
231
  end
223
232
 
233
+ def coerce_log_otel_enabled(value)
234
+ case value
235
+ when nil, "", "auto", :auto then :auto
236
+ when true, "true", "1", 1 then true
237
+ when false, "false", "0", 0 then false
238
+ else value
239
+ end
240
+ end
241
+ private :coerce_log_otel_enabled
242
+
224
243
  def set_webhook_attribute_defaults
225
244
  # can't set a default on this, or anyway config blows up when trying to merge the
226
245
  # hash from the env vars into an array/nil.
@@ -3,6 +3,8 @@ require "pact_broker/error"
3
3
  require "semantic_logger"
4
4
  require "forwardable"
5
5
  require "pact_broker/config/runtime_configuration"
6
+ require "pact_broker/logging/open_telemetry_appender_setup"
7
+ require "pact_broker/logging/trace_aware_json_formatter"
6
8
  require "anyway/auto_cast"
7
9
  require "request_store"
8
10
 
@@ -98,17 +100,28 @@ module PactBroker
98
100
  def logger_from_runtime_configuration
99
101
  @logger_from_runtime_configuration ||= begin
100
102
  SemanticLogger.default_level = runtime_configuration.log_level
103
+ formatter = resolve_log_formatter(runtime_configuration.log_format)
101
104
  if runtime_configuration.log_stream == :file
102
105
  path = runtime_configuration.log_dir + "/pact_broker.log"
103
106
  FileUtils.mkdir_p(runtime_configuration.log_dir)
104
- @default_appender = SemanticLogger.add_appender(file_name: path, formatter: runtime_configuration.log_format)
107
+ @default_appender = SemanticLogger.add_appender(file_name: path, formatter: formatter)
105
108
  else
106
- @default_appender = SemanticLogger.add_appender(io: $stdout, formatter: runtime_configuration.log_format)
109
+ @default_appender = SemanticLogger.add_appender(io: $stdout, formatter: formatter)
107
110
  end
111
+ PactBroker::Logging::OpenTelemetryAppenderSetup.call(runtime_configuration)
108
112
  @logger_from_runtime_configuration = SemanticLogger["pact-broker"]
109
113
  end
110
114
  end
111
115
 
116
+ def resolve_log_formatter(log_format)
117
+ if log_format == :json
118
+ PactBroker::Logging::TraceAwareJsonFormatter.new
119
+ else
120
+ log_format
121
+ end
122
+ end
123
+ private :resolve_log_formatter
124
+
112
125
  def logger
113
126
  custom_logger || logger_from_runtime_configuration
114
127
  end
@@ -8,10 +8,6 @@ module PactBroker
8
8
  module Helpers
9
9
  extend self
10
10
 
11
- def mysql?
12
- Sequel::Model.db.adapter_scheme.to_s =~ /mysql/
13
- end
14
-
15
11
  def postgres?
16
12
  Sequel::Model.db.adapter_scheme.to_s =~ /postgres/
17
13
  end
@@ -32,14 +28,9 @@ module PactBroker
32
28
 
33
29
  def name_like column_name, value
34
30
  if PactBroker.configuration.use_case_sensitive_resource_names
35
- if mysql?
36
- # sigh, mysql, this is the only way to perform a case sensitive search
37
- Sequel.like(column_name, value.gsub("_", "\\_"), { case_insensitive: false })
38
- else
39
- { column_name => value }
40
- end
31
+ { column_name => value }
41
32
  else
42
- Sequel.like(column_name, value.gsub("_", "\\_"), { case_insensitive: true })
33
+ Sequel.like(column_name, escape_wildcards(value), { case_insensitive: true })
43
34
  end
44
35
  end
45
36
 
@@ -82,12 +73,7 @@ module PactBroker
82
73
  end
83
74
 
84
75
  def select_for_subquery column
85
- if mysql? #stoopid mysql doesn't allow you to modify datasets with subqueries
86
- column_name = column.respond_to?(:alias) ? column.alias : column
87
- select(column).collect{ | it | it[column_name] }
88
- else
89
- select(column)
90
- end
76
+ select(column)
91
77
  end
92
78
 
93
79
  def no_columns_selected?
@@ -109,12 +95,7 @@ module Sequel
109
95
  # This has been used inconsistently, and in the next major version, support for case insensitive names will be dropped.
110
96
  def self.name_like(column_name, value)
111
97
  if PactBroker.configuration.use_case_sensitive_resource_names
112
- if PactBroker::Dataset::Helpers.mysql?
113
- # sigh, mysql, this is the only way to perform a case sensitive search
114
- Sequel.like(column_name, PactBroker::Dataset::Helpers.escape_wildcards(value), { case_insensitive: false })
115
- else
116
- { column_name => value }
117
- end
98
+ { column_name => value }
118
99
  else
119
100
  Sequel.like(column_name, PactBroker::Dataset::Helpers.escape_wildcards(value), { case_insensitive: true })
120
101
  end
@@ -0,0 +1,40 @@
1
+ require "pact_broker/hash_refinements"
2
+
3
+ module PactBroker
4
+ module DB
5
+ class Clean
6
+ class BranchSelector
7
+ using PactBroker::HashRefinements
8
+
9
+ ATTRIBUTES = [:max_age, :branch]
10
+
11
+ attr_accessor(*ATTRIBUTES)
12
+
13
+ def initialize(attributes = {})
14
+ attributes.each do | (name, value) |
15
+ instance_variable_set("@#{name}", value) if respond_to?(name)
16
+ end
17
+ @source_hash = attributes[:source_hash]
18
+ end
19
+
20
+ def self.from_hash(hash)
21
+ standard_hash = hash.symbolize_keys.snakecase_keys
22
+ new_hash = standard_hash.slice(*ATTRIBUTES)
23
+ new_hash[:source_hash] = hash
24
+ new(new_hash.compact)
25
+ end
26
+
27
+ def to_hash
28
+ ATTRIBUTES.each_with_object({}) do | key, hash |
29
+ hash[key] = send(key)
30
+ end.compact
31
+ end
32
+ alias_method :to_h, :to_hash
33
+
34
+ def to_json(_opts = nil)
35
+ (@source_hash || to_hash).to_json
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,6 +2,7 @@ require "sequel"
2
2
  require "pact_broker/project_root"
3
3
  require "pact_broker/logging"
4
4
  require "pact_broker/db/clean/selector"
5
+ require "pact_broker/db/clean/branch_selector"
5
6
 
6
7
  module PactBroker
7
8
  module DB
@@ -130,6 +131,9 @@ module PactBroker
130
131
  delete_orphan_tags
131
132
  delete_orphan_versions
132
133
 
134
+ deleted_counts[:stale_branches] = delete_stale_branches
135
+ kept_counts[:stale_branches] = db[:branches].count
136
+
133
137
  { kept: kept_counts, deleted: deleted_counts }
134
138
  end
135
139
 
@@ -180,6 +184,47 @@ module PactBroker
180
184
  db[:versions].where(id: referenced_version_ids).invert.delete
181
185
  end
182
186
 
187
+ def delete_stale_branches
188
+ return 0 unless keep_branches && !keep_branches.empty?
189
+
190
+ db[:branches].where(id: stale_branch_ids_to_delete.from_self.select(:id)).delete
191
+ end
192
+
193
+ def stale_branch_ids_to_delete
194
+ db[:branches]
195
+ .select(Sequel[:branches][:id])
196
+ .left_outer_join(stale_branch_ids_to_keep, { Sequel[:branches][:id] => Sequel[:keep_branches][:id] }, table_alias: :keep_branches)
197
+ .where(Sequel[:keep_branches][:id] => nil)
198
+ end
199
+
200
+ def stale_branch_ids_to_keep
201
+ queries = [main_branch_ids_to_keep]
202
+
203
+ keep_branches.each do | selector |
204
+ if selector.max_age
205
+ cutoff = Date.today - selector.max_age
206
+ queries << db[:branches].where { updated_at >= cutoff }.select(:id)
207
+ end
208
+ if selector.branch
209
+ queries << db[:branches].where(name: Array(selector.branch)).select(:id)
210
+ end
211
+ end
212
+
213
+ queries.reduce(:union)
214
+ end
215
+
216
+ def main_branch_ids_to_keep
217
+ db[:branches]
218
+ .join(:pacticipants, id: Sequel[:branches][:pacticipant_id])
219
+ .exclude(Sequel[:pacticipants][:main_branch] => nil)
220
+ .where(Sequel[:branches][:name] => Sequel[:pacticipants][:main_branch])
221
+ .select(Sequel[:branches][:id])
222
+ end
223
+
224
+ def keep_branches
225
+ @keep_branches ||= options[:keep_branches]&.collect { | selector_hash | BranchSelector.from_hash(selector_hash.to_hash) }
226
+ end
227
+
183
228
  def delete_overwritten_verifications
184
229
  verification_ids = db[:verifications].select(:id).where(id: db[:latest_verification_id_for_pact_version_and_provider_version].select(:verification_id)).invert
185
230
  deleted_counts = { verification_results: verification_ids.count }
@@ -2,6 +2,7 @@ require "pact_broker/logging"
2
2
  require "pact_broker/matrix/unresolved_selector"
3
3
  require "pact_broker/date_helper"
4
4
  require "pact_broker/db/clean/selector"
5
+ require "pact_broker/db/clean/branch_selector"
5
6
 
6
7
  module PactBroker
7
8
  module DB
@@ -14,7 +15,10 @@ module PactBroker
14
15
  PactBroker::DB::Clean::Selector.new(released: true),
15
16
  PactBroker::DB::Clean::Selector.new(max_age: 90)
16
17
  ]
17
- TABLES = [:versions, :pact_publications, :pact_versions, :verifications, :triggered_webhooks, :webhook_executions]
18
+ DEFAULT_KEEP_BRANCH_SELECTORS = [
19
+ PactBroker::DB::Clean::BranchSelector.new(max_age: 90)
20
+ ]
21
+ TABLES = [:versions, :pact_publications, :pact_versions, :verifications, :triggered_webhooks, :webhook_executions, :branches]
18
22
 
19
23
  def self.call database_connection, options = {}
20
24
  new(database_connection, options).call
@@ -42,6 +46,7 @@ module PactBroker
42
46
  def execute_clean
43
47
  db.transaction do
44
48
  before_counts = current_counts
49
+ delete_stale_branches
45
50
  PactBroker::Domain::Version.where(id: versions_to_delete.from_self.select_map(:id)).delete
46
51
  delete_orphan_pact_versions
47
52
  after_counts = current_counts
@@ -68,10 +73,22 @@ module PactBroker
68
73
  end
69
74
  end
70
75
 
76
+ def keep_branches
77
+ @keep_branches ||= if options.key?(:keep_branches)
78
+ options[:keep_branches]&.collect { | selector_hash | PactBroker::DB::Clean::BranchSelector.from_hash(selector_hash.to_hash) }
79
+ else
80
+ DEFAULT_KEEP_BRANCH_SELECTORS
81
+ end
82
+ end
83
+
71
84
  def limit
72
85
  options[:limit] || 1000
73
86
  end
74
87
 
88
+ def branch_limit
89
+ options[:branch_limit] || limit
90
+ end
91
+
75
92
  def versions_to_delete(columns = [:id])
76
93
  fully_qualified_columns = columns.collect { |col| Sequel[:versions][col] }
77
94
  PactBroker::Domain::Version
@@ -96,13 +113,50 @@ module PactBroker
96
113
  options[:dry_run]
97
114
  end
98
115
 
116
+ def delete_stale_branches
117
+ return unless keep_branches && !keep_branches.empty?
118
+
119
+ # Wrap the dataset in a derived table with `from_self` before selecting
120
+ # the ids for deletion, to avoid referencing the target table directly
121
+ # in the subquery.
122
+ PactBroker::Versions::Branch.where(id: stale_branch_ids_to_delete.from_self.select(:id)).delete
123
+ end
124
+
125
+ def stale_branch_ids_to_delete
126
+ PactBroker::Versions::Branch
127
+ .select(Sequel[:branches][:id])
128
+ .left_outer_join(stale_branch_ids_to_keep, { Sequel[:branches][:id] => Sequel[:keep_branches][:id] }, table_alias: :keep_branches)
129
+ .where(Sequel[:keep_branches][:id] => nil)
130
+ .limit(branch_limit)
131
+ end
132
+
133
+ def stale_branch_ids_to_keep
134
+ # Always keep main branches for each pacticipant
135
+ queries = [main_branch_ids_to_keep]
136
+
137
+ keep_branches.each do | selector |
138
+ if selector.max_age
139
+ cutoff = Date.today - selector.max_age
140
+ queries << PactBroker::Versions::Branch.where { updated_at >= cutoff }.select(Sequel[:branches][:id])
141
+ end
142
+ if selector.branch
143
+ queries << PactBroker::Versions::Branch.where(name: Array(selector.branch)).select(Sequel[:branches][:id])
144
+ end
145
+ end
146
+
147
+ queries.reduce(&:union)
148
+ end
149
+
150
+ def main_branch_ids_to_keep
151
+ PactBroker::Versions::Branch
152
+ .join(:pacticipants, id: Sequel[:branches][:pacticipant_id])
153
+ .exclude(Sequel[:pacticipants][:main_branch] => nil)
154
+ .where(Sequel[:branches][:name] => Sequel[:pacticipants][:main_branch])
155
+ .select(Sequel[:branches][:id])
156
+ end
157
+
99
158
  def delete_orphan_pact_versions
100
159
  db[:pact_versions].where(id: orphan_pact_versions).delete
101
- rescue Sequel::DatabaseError => e
102
- raise unless e.cause.class.name == "Mysql2::Error"
103
-
104
- ids = orphan_pact_versions.map { |row| row[:id] }
105
- db[:pact_versions].where(id: ids).delete
106
160
  end
107
161
 
108
162
  def orphan_pact_versions
@@ -142,19 +196,29 @@ module PactBroker
142
196
  }
143
197
  end
144
198
 
199
+ {
200
+ "counts" => dry_run_counts(kept_per_selector),
201
+ "versionSummary" => pacticipant_results
202
+ }
203
+ end
204
+
205
+ def dry_run_counts(kept_per_selector)
145
206
  total_versions_count = PactBroker::Domain::Version.count
146
207
  versions_to_keep_count = version_ids_to_keep.count
147
208
  versions_to_delete_count = versions_to_delete.count
148
209
 
210
+ total_branches_count = PactBroker::Versions::Branch.count
211
+ branches_to_delete_count = keep_branches && !keep_branches.empty? ? stale_branch_ids_to_delete.count : 0
212
+
149
213
  {
150
- "counts" => {
151
- "totalVersions" => total_versions_count,
152
- "versionsToDelete" => versions_to_delete_count,
153
- "versionsNotToKeep" => total_versions_count - versions_to_keep_count,
154
- "versionsToKeep" => versions_to_keep_count,
155
- "versionsToKeepBySelector" => kept_per_selector,
156
- },
157
- "versionSummary" => pacticipant_results
214
+ "totalVersions" => total_versions_count,
215
+ "versionsToDelete" => versions_to_delete_count,
216
+ "versionsNotToKeep" => total_versions_count - versions_to_keep_count,
217
+ "versionsToKeep" => versions_to_keep_count,
218
+ "versionsToKeepBySelector" => kept_per_selector,
219
+ "totalBranches" => total_branches_count,
220
+ "branchesToDelete" => branches_to_delete_count,
221
+ "branchesToKeep" => total_branches_count - branches_to_delete_count
158
222
  }
159
223
  end
160
224
 
@@ -57,12 +57,5 @@ module PactBroker
57
57
  PactBroker::DB::ValidateEncoding.(connection)
58
58
  end
59
59
 
60
- def self.set_mysql_strict_mode_if_mysql
61
- connection.run("SET sql_mode='STRICT_TRANS_TABLES';") if mysql?
62
- end
63
-
64
- def self.mysql?
65
- connection.adapter_scheme =~ /mysql/
66
- end
67
60
  end
68
61
  end
@@ -46,6 +46,20 @@ module PactBroker
46
46
  .single_record
47
47
  end
48
48
 
49
+ def self.find_released_versions_for_version(version)
50
+ scope_for(ReleasedVersion)
51
+ .where(version_id: version.id)
52
+ .eager(:environment)
53
+ .all
54
+ end
55
+
56
+ def self.find_released_versions_for_versions(versions_array)
57
+ scope_for(ReleasedVersion)
58
+ .where(version_id: versions_array.map(&:id))
59
+ .eager(:environment)
60
+ .to_hash_groups(:version_id)
61
+ end
62
+
49
63
  def self.record_version_support_ended(released_version)
50
64
  released_version.record_support_ended
51
65
  end
@@ -12,6 +12,7 @@ module PactBroker
12
12
 
13
13
  plugin :timestamps, update_on_create: true
14
14
  plugin :age
15
+ plugin :insert_ignore, identifying_columns: [:pacticipant_id, :number]
15
16
  plugin :upsert, { identifying_columns: [:pacticipant_id, :number], ignore_columns_on_update: [:id, :created_at, :order] }
16
17
 
17
18
  one_to_many :pact_publications, order: :revision_number, class: "PactBroker::Pacts::PactPublication", key: :consumer_version_id
@@ -241,9 +242,11 @@ module PactBroker
241
242
  # @return Sequel::Dataset<PactBroker::Domain::Version>
242
243
  def ids_for_selectors(unresolved_selectors)
243
244
  # Need the select at the start and at the end to stop extra columns being returned (eg. branch name, environment name)
245
+ # from_self: false keeps this a single flat UNION. Sequel's default would wrap the
246
+ # accumulated dataset in a new subquery for every selector, nesting it N-1 deep.
244
247
  unresolved_selectors
245
248
  .collect{ |selector| self.select(Sequel[:versions][:id]).for_selector(selector).select(:id) }
246
- .reduce(&:union)
249
+ .reduce { |all_ids, ids| all_ids.union(ids, from_self: false) }
247
250
  end
248
251
 
249
252
  def pacticipants_set
@@ -272,10 +275,11 @@ module PactBroker
272
275
  end
273
276
 
274
277
  # Isn't called on upsert when the record is updated with Sqlite
275
- # Is called with Postgres/MySQL
278
+ # Is called with Postgres
276
279
  # Haven't had time to dig into why
277
280
  def after_create
278
281
  super
282
+ return unless id
279
283
  OrderVersions.(self) unless self.order
280
284
  refresh
281
285
  end
@@ -1,9 +1,14 @@
1
1
  require "sequel"
2
2
  require "pact_broker/db/log_quietener"
3
3
  require "fileutils"
4
+ require "pact_broker/error"
4
5
 
5
6
  module PactBroker
6
7
  def self.create_database_connection(config, logger = nil)
8
+ if config[:adapter].to_s =~ /mysql/
9
+ raise PactBroker::Error.new("MySQL is no longer supported. Please use PostgreSQL.")
10
+ end
11
+
7
12
  logger&.info("Connecting to database:", config.merge(password: "*****"))
8
13
 
9
14
  sequel_config = config.dup
@@ -74,7 +74,7 @@ module PactBroker
74
74
  def set_contract_data_updated_at_for_multiple_integrations(objects_with_consumer_and_provider)
75
75
  consumer_and_provider_ids = objects_with_consumer_and_provider.collect{ | object | { consumer_id: object.consumer&.id, provider_id: object.provider.id }.compact }.uniq
76
76
 
77
- # MySQL doesn't support an UPDATE with a subquery. FFS. Really need to do a major version release and delete the support code.
77
+ # Not all supported databases handle an UPDATE with a subquery, so branch on SKIP LOCKED support.
78
78
  criteria = if Integration.dataset.supports_skip_locked?
79
79
  integration_ids_to_update = Integration
80
80
  .select(:id)
@@ -55,9 +55,7 @@ module PactBroker
55
55
  model.truncate(cascade: true)
56
56
  else
57
57
  logger.info("Deleting all from ", model.table_name)
58
- # Mysql adapter needs to support cascade truncate
59
- # https://travis-ci.org/pact-foundation/pact_broker/jobs/633050220#L841
60
- # https://travis-ci.org/pact-foundation/pact_broker/jobs/633053228#L849
58
+ # SQLite does not support cascade truncate, so delete instead
61
59
  model.dataset.delete
62
60
  end
63
61
  end