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
@@ -0,0 +1,73 @@
1
+ require "semantic_logger"
2
+ require "pact_broker/error"
3
+
4
+ module PactBroker
5
+ module Logging
6
+ # Adds semantic_logger's :open_telemetry appender when configured, without
7
+ # taking a hard dependency on the OpenTelemetry gems.
8
+ class OpenTelemetryAppenderSetup
9
+ def self.call(runtime_configuration)
10
+ new(runtime_configuration).call
11
+ end
12
+
13
+ def initialize(runtime_configuration)
14
+ @runtime_configuration = runtime_configuration
15
+ end
16
+
17
+ # Returns the added (or already-present) appender, or nil if none was added.
18
+ def call
19
+ case @runtime_configuration.log_otel_enabled
20
+ when false
21
+ nil
22
+ when true
23
+ unless otel_available?
24
+ raise PactBroker::ConfigurationError,
25
+ "log_otel_enabled is set to true but the opentelemetry-logs-sdk gem could not be loaded. " \
26
+ "Add it to your Gemfile, or set log_otel_enabled to auto/false."
27
+ end
28
+ add_appender(warn_if_no_provider: true)
29
+ else # :auto
30
+ add_appender(warn_if_no_provider: false) if otel_available? && otel_provider_configured?
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def add_appender(warn_if_no_provider:)
37
+ existing = existing_otel_appender
38
+ return existing if existing
39
+
40
+ if warn_if_no_provider && !otel_provider_configured?
41
+ SemanticLogger["pact-broker"].warn(
42
+ "log_otel_enabled is true but no OpenTelemetry LoggerProvider is configured; " \
43
+ "OTel log records may be dropped."
44
+ )
45
+ end
46
+
47
+ SemanticLogger.add_appender(appender: :open_telemetry)
48
+ end
49
+
50
+ def existing_otel_appender
51
+ return nil unless defined?(SemanticLogger::Appender::OpenTelemetry)
52
+
53
+ SemanticLogger.appenders.find { |appender| appender.is_a?(SemanticLogger::Appender::OpenTelemetry) }
54
+ end
55
+
56
+ def otel_available?
57
+ require "opentelemetry-logs-sdk"
58
+ defined?(OpenTelemetry::Logs) ? true : false
59
+ rescue LoadError
60
+ false
61
+ end
62
+
63
+ def otel_provider_configured?
64
+ return false unless defined?(OpenTelemetry)
65
+
66
+ # A no-op / proxy provider means no real logs pipeline is configured.
67
+ OpenTelemetry.logger_provider.class.name.to_s.start_with?("OpenTelemetry::SDK::Logs")
68
+ rescue StandardError
69
+ false
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,38 @@
1
+ require "semantic_logger"
2
+ require "semantic_logger/formatters/json"
3
+
4
+ module PactBroker
5
+ module Logging
6
+ # A JSON log formatter that adds the standard OpenTelemetry log-correlation
7
+ # fields (trace_id, span_id, trace_flags) when a valid span is active.
8
+ # Degrades to plain JSON when OTel is not loaded or there is no active span,
9
+ # so it is safe for the open-source Pact Broker.
10
+ class TraceAwareJsonFormatter < SemanticLogger::Formatters::Json
11
+ def call(log, logger)
12
+ json = super
13
+ context = otel_trace_context
14
+ return json if context.empty?
15
+
16
+ # `hash` was populated by Raw#call during `super`; reuse it.
17
+ SemanticLogger::Utils.to_json(hash.merge(context))
18
+ end
19
+
20
+ private
21
+
22
+ def otel_trace_context
23
+ return {} unless defined?(OpenTelemetry::Trace)
24
+
25
+ span_context = OpenTelemetry::Trace.current_span.context
26
+ return {} unless span_context.valid?
27
+
28
+ {
29
+ trace_id: span_context.hex_trace_id,
30
+ span_id: span_context.hex_span_id,
31
+ trace_flags: span_context.trace_flags.sampled? ? "01" : "00"
32
+ }
33
+ rescue StandardError
34
+ {}
35
+ end
36
+ end
37
+ end
38
+ end
@@ -61,8 +61,13 @@ module PactBroker
61
61
  end
62
62
 
63
63
  # consumer
64
- pact_publication_matching_consumer = select_pact_columns_with_aliases.most_recent(limit).from_self(alias: :p).inner_join_versions_for_selectors_as_consumer(resolved_selectors)
65
- rows_where_selector_matches_consumer = pact_publication_matching_consumer.left_outer_join_verifications.select_all_columns_after_join
64
+ pact_publication_matching_consumer = select_pact_columns_with_aliases
65
+ .most_recent(limit)
66
+ .from_self(alias: :p)
67
+ .where_consumer_version_matches_selectors(resolved_selectors)
68
+ rows_where_selector_matches_consumer = pact_publication_matching_consumer
69
+ .left_outer_join_verifications
70
+ .select_all_columns_after_join
66
71
 
67
72
  # provider
68
73
  verifications_matching_provider = verification_dataset.matching_selectors_as_provider_for_any_consumer(resolved_selectors)
@@ -143,24 +148,37 @@ module PactBroker
143
148
 
144
149
  if resolved_selectors_with_versions_specified.any?
145
150
  select_pact_columns_with_aliases
146
- .inner_join_versions_for_selectors_as_consumer(resolved_selectors_with_versions_specified)
151
+ .where_consumer_version_matches_selectors(resolved_selectors_with_versions_specified)
147
152
  .where(provider_id: all_pacticipant_ids)
148
153
  .most_recent(limit)
149
154
  end
150
155
  end
151
156
 
157
+ # Name-only selectors carry no version ID — matching_one_selector_for_either_consumer_or_provider
158
+ # passes its selectors unfiltered — so they filter on the pacticipant instead. That
159
+ # identifies the same rows, because a pact publication's consumer_id is the
160
+ # pacticipant_id of its consumer_version.
152
161
  # @private
153
- def inner_join_versions_for_selectors_as_consumer(resolved_selectors)
154
- # get the UnresolvedSelector objects back out of the resolved_selectors because the Version.for_selector() method uses the UnresolvedSelector
155
- unresolved_selectors = resolved_selectors.collect(&:original_selector).uniq
156
- versions = PactBroker::Domain::Version.ids_for_selectors(unresolved_selectors)
157
- inner_join_versions_dataset(versions)
162
+ # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
163
+ def where_consumer_version_matches_selectors(resolved_selectors)
164
+ if resolved_selectors.empty?
165
+ raise ArgumentError.new("resolved_selectors must not be empty")
166
+ end
167
+
168
+ criteria = consumer_criteria_for_selectors(resolved_selectors)
169
+ where(criteria.size > 1 ? Sequel.|(*criteria) : criteria.first)
158
170
  end
159
171
 
160
172
  # @private
161
- def inner_join_versions_dataset(versions)
162
- versions_join = { Sequel[:p][:consumer_version_id] => Sequel[:versions][:id] }
163
- join(versions, versions_join, table_alias: :versions)
173
+ # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
174
+ # @return [Array<Hash>]
175
+ def consumer_criteria_for_selectors(resolved_selectors)
176
+ name_only, with_versions = resolved_selectors.partition(&:only_pacticipant_name_specified?)
177
+
178
+ criteria = []
179
+ criteria << { Sequel[:p][:consumer_id] => name_only.collect(&:pacticipant_id).uniq } if name_only.any?
180
+ criteria << { Sequel[:p][:consumer_version_id] => with_versions.collect(&:pacticipant_version_id).uniq } if with_versions.any?
181
+ criteria
164
182
  end
165
183
 
166
184
  # @private
@@ -21,7 +21,7 @@ module PactBroker
21
21
  # @return [Sequel::Dataset<Verification>, nil]
22
22
  def matching_selectors_as_provider_for_any_consumer(resolved_selectors)
23
23
  select_verification_columns_with_aliases
24
- .inner_join_versions_for_selectors_as_provider(resolved_selectors)
24
+ .where_provider_version_matches_selectors(resolved_selectors)
25
25
  end
26
26
 
27
27
  # Return verifications where the provider is described by any of the resolved_selectors *that only specify the pacticipant NAME*,
@@ -51,32 +51,43 @@ module PactBroker
51
51
  # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
52
52
  # @return [Sequel::Dataset<Verification>, nil]
53
53
  def matching_only_selectors_as_provider_where_not_only_pacticipant_name_in_selector(resolved_selectors)
54
- # get the UnresolvedSelector objects back out of the resolved_selectors because the Version.for_selector() method uses the UnresolvedSelector
55
54
  all_pacticipant_ids = resolved_selectors.collect(&:pacticipant_id).uniq
56
55
  resolved_selectors_with_versions_specified = resolved_selectors.reject(&:only_pacticipant_name_specified?)
57
56
 
58
57
  if resolved_selectors_with_versions_specified.any?
59
58
  select_verification_columns_with_aliases
60
- .inner_join_versions_for_selectors_as_provider(resolved_selectors_with_versions_specified)
59
+ .where_provider_version_matches_selectors(resolved_selectors_with_versions_specified)
61
60
  .where(consumer_id: all_pacticipant_ids)
62
61
  end
63
62
  end
64
63
 
65
- # Don't think it's worth splitting this into 2 different queries for selectors with only pacticipant name/selectors with version properties,
66
- # as it's unlikely for there ever to be a query through the UI or CLI that results in 1 selector which only has a pacticipant name in it.
64
+ # Name-only selectors carry no version id — matching_selectors_as_provider_for_any_consumer
65
+ # passes its selectors unfiltered — so they filter on the provider pacticipant instead. That
66
+ # identifies the same rows, because a verification's provider_id is the
67
+ # pacticipant_id of its provider_version.
67
68
  # @private
68
69
  # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
69
70
  # @return [Sequel::Dataset<Verification>]
70
- def inner_join_versions_for_selectors_as_provider(resolved_selectors)
71
- # get the UnresolvedSelector objects back out of the resolved_selectors because the Version.for_selector() method uses the UnresolvedSelector
72
- unresolved_selectors = resolved_selectors.collect(&:original_selector).uniq
73
- versions = PactBroker::Domain::Version.ids_for_selectors(unresolved_selectors)
74
- join_versions_dataset(versions)
71
+ def where_provider_version_matches_selectors(resolved_selectors)
72
+ if resolved_selectors.empty?
73
+ raise ArgumentError.new("resolved_selectors must not be empty")
74
+ end
75
+
76
+ criteria = provider_criteria_for_selectors(resolved_selectors)
77
+ where(criteria.size > 1 ? Sequel.|(*criteria) : criteria.first)
75
78
  end
76
79
 
77
80
  # @private
78
- def join_versions_dataset(versions_dataset)
79
- join(versions_dataset, { Sequel[self.model.table_name][:provider_version_id] => Sequel[:versions][:id] }, table_alias: :versions)
81
+ # @param [Array<PactBroker::Matrix::ResolvedSelector>] resolved_selectors
82
+ # @return [Array<Hash>]
83
+ def provider_criteria_for_selectors(resolved_selectors)
84
+ name_only, with_versions = resolved_selectors.partition(&:only_pacticipant_name_specified?)
85
+ table = self.model.table_name
86
+
87
+ criteria = []
88
+ criteria << { Sequel[table][:provider_id] => name_only.collect(&:pacticipant_id).uniq } if name_only.any?
89
+ criteria << { Sequel[table][:provider_version_id] => with_versions.collect(&:pacticipant_version_id).uniq } if with_versions.any?
90
+ criteria
80
91
  end
81
92
  end
82
93
  end
@@ -26,6 +26,11 @@ module PactBroker
26
26
 
27
27
  if params[:environment].is_a?(String)
28
28
  options[:environment_name] = params[:environment]
29
+ # Multiple versions of an integrated application can be simultaneously released to an
30
+ # environment (record-release), or deployed to it on different targets (record-deployment).
31
+ # "cvp" collapses these down to the latest provider version, hiding incompatible versions
32
+ # that are still live. "cvpv" evaluates every co-resident version independently. See issue #903.
33
+ options[:latestby] = "cvpv"
29
34
  end
30
35
 
31
36
  if params[:ignore].is_a?(Array)
@@ -73,15 +73,43 @@ module PactBroker
73
73
 
74
74
  private :resolve_versions_and_add_ids
75
75
 
76
- # Return a Hash of the pacticipant names used in the selectors, where the key is the name and the value is the pacticipant
76
+ # Return a Hash of the pacticipant names used in the selectors, where the key is the name
77
+ # as specified in the selector (not necessarily the pacticipant's stored name, when
78
+ # use_case_sensitive_resource_names is false), and the value is the pacticipant.
77
79
  # @return [Hash<String, PactBroker::Domain::Pacticipant>]
78
80
  def find_pacticipants_for_selectors(unresolved_selectors)
79
81
  names = unresolved_selectors.collect(&:pacticipant_name)
80
- PactBroker::Domain::Pacticipant.where(name: names).all.group_by(&:name).transform_values(&:first)
82
+ pacticipants = pacticipants_matching_names(names)
83
+
84
+ names.uniq.each_with_object({}) do | name, hash |
85
+ matching = pacticipants.select { | candidate | pacticipant_name_matches?(candidate.name, name) }
86
+ pacticipant_repository.handle_multiple_pacticipants_found(name, matching) if matching.size > 1
87
+ hash[name] = matching.first if matching.any?
88
+ end
81
89
  end
82
90
 
83
91
  private :find_pacticipants_for_selectors
84
92
 
93
+ # @param [Array<String>] names
94
+ # @return [Array<PactBroker::Domain::Pacticipant>]
95
+ def pacticipants_matching_names(names)
96
+ return [] if names.empty?
97
+
98
+ PactBroker::Domain::Pacticipant.where(Sequel.|(*names.collect { | name | Sequel.name_like(:name, name) })).all
99
+ end
100
+
101
+ private :pacticipants_matching_names
102
+
103
+ def pacticipant_name_matches?(stored_name, requested_name)
104
+ if PactBroker.configuration.use_case_sensitive_resource_names
105
+ stored_name == requested_name
106
+ else
107
+ stored_name.casecmp?(requested_name)
108
+ end
109
+ end
110
+
111
+ private :pacticipant_name_matches?
112
+
85
113
  def build_selectors(pacticipants_hash, unresolved_selector, selector_type, selector_ignorer)
86
114
  selector_builder = ResolvedSelectorBuilder.new(unresolved_selector, selector_type, selector_ignorer)
87
115
  selector_builder.pacticipant = pacticipants_hash[unresolved_selector.pacticipant_name]
@@ -1,3 +1,4 @@
1
+ require "pact_broker/dataset"
1
2
  require "pact_broker/domain/pacticipant"
2
3
  require "pact_broker/error"
3
4
  require "pact_broker/repositories/scopes"
@@ -41,12 +42,14 @@ module PactBroker
41
42
  return [] if query.empty?
42
43
 
43
44
  query = query.select_all_qualified
44
- query = query.filter(:name, options[:query_string]) if options[:query_string]
45
+ query = query.filter(Sequel[:pacticipants][:name], options[:query_string]) if options[:query_string]
45
46
  query = query.label(options[:label_name]) if options[:label_name]
46
47
  query.order_ignore_case(Sequel[:pacticipants][:name]).eager(*eager_load_associations).all_with_pagination_options(pagination_options)
47
48
  end
48
49
 
49
50
  def find_by_name_or_create name
51
+ raise PactBroker::Error, "Cannot create a pacticipant with a blank name" if name.to_s.strip.empty?
52
+
50
53
  pacticipant = find_by_name(name)
51
54
  pacticipant ? pacticipant : create(name: name)
52
55
  end
@@ -104,7 +107,7 @@ module PactBroker
104
107
  end
105
108
 
106
109
  def search_by_name(pacticipant_name)
107
- terms = pacticipant_name.split.map { |v| v.gsub("_", "\\_") }
110
+ terms = pacticipant_name.split.map { |v| PactBroker::Dataset::Helpers.escape_wildcards(v) }
108
111
  columns = [:name, :display_name]
109
112
  string_match_query = Sequel.|(
110
113
  *terms.map do |term|
@@ -182,7 +182,7 @@ module PactBroker
182
182
  # Modifing pacts is strongly discouraged now, and support for it will be dropped in the next major version of the Pact Broker
183
183
  def create_pact_revision params, existing_pact
184
184
  logger.info("Updating existing pact publication", params.without(:json_content))
185
- logger.debug("Content #{params[:json_content]}")
185
+ logger.debug { "Content #{params[:json_content]}" }
186
186
  pact_version_sha = params.fetch(:pact_version_sha)
187
187
  json_content = add_interaction_ids(params[:json_content])
188
188
  update_params = { pact_version_sha: pact_version_sha, json_content: json_content }
@@ -211,7 +211,7 @@ module PactBroker
211
211
  # When no publication for the given consumer/provider/consumer version number exists
212
212
  def create_pact(params, version, provider)
213
213
  logger.debug("Creating new pact publication", params.without(:json_content))
214
- logger.debug("Content #{params[:json_content]}")
214
+ logger.debug { "Content #{params[:json_content]}" }
215
215
  json_content = add_interaction_ids(params[:json_content])
216
216
  pact = pact_repository.create(
217
217
  version_id: version.id,
@@ -24,17 +24,16 @@ module PactBroker
24
24
  end
25
25
 
26
26
  def delete args
27
- version = version_repository.find_by_pacticipant_name_and_number args.fetch(:pacticipant_name), args.fetch(:pacticipant_version_number)
28
- connection = PactBroker::Domain::Tag.new.db
29
- connection.run("delete from tags where name = '#{args.fetch(:tag_name)}' and version_id = '#{version.id}'")
27
+ version = version_repository.find_by_pacticipant_name_and_number(args.fetch(:pacticipant_name), args.fetch(:pacticipant_version_number))
28
+ PactBroker::Domain::Tag.where(name: args.fetch(:tag_name), version_id: version.id).delete
30
29
  end
31
30
 
32
31
  def find_all_tag_names_for_pacticipant pacticipant_name
33
- tag_repository.find_all_tag_names_for_pacticipant pacticipant_name
32
+ tag_repository.find_all_tag_names_for_pacticipant(pacticipant_name)
34
33
  end
35
34
 
36
35
  def find_all_by_pacticipant_name_and_tag pacticipant_name:, tag_name:
37
- tag_repository.find_all_by_pacticipant_name_and_tag pacticipant_name, tag_name
36
+ tag_repository.find_all_by_pacticipant_name_and_tag(pacticipant_name, tag_name)
38
37
  end
39
38
  end
40
39
  end
@@ -10,7 +10,9 @@ module PactBroker
10
10
 
11
11
  attr_accessor :database_connection
12
12
  attr_reader :keep_version_selectors
13
+ attr_reader :keep_branch_selectors
13
14
  attr_accessor :version_deletion_limit
15
+ attr_accessor :branch_deletion_limit
14
16
  attr_accessor :logger
15
17
  attr_accessor :dry_run
16
18
  attr_accessor :use_lock # allow disabling of postgres lock if it is causing problems
@@ -21,6 +23,7 @@ module PactBroker
21
23
  @dry_run = false
22
24
  @use_lock = true
23
25
  @keep_version_selectors = PactBroker::DB::CleanIncremental::DEFAULT_KEEP_SELECTORS
26
+ @keep_branch_selectors = PactBroker::DB::CleanIncremental::DEFAULT_KEEP_BRANCH_SELECTORS
24
27
  rake_task(&block)
25
28
  end
26
29
 
@@ -31,10 +34,17 @@ module PactBroker
31
34
  end
32
35
  end
33
36
 
37
+ def keep_branch_selectors=(keep_branch_selectors)
38
+ require "pact_broker/db/clean/branch_selector"
39
+ @keep_branch_selectors = keep_branch_selectors.nil? ? nil : [*keep_branch_selectors].collect do | hash |
40
+ PactBroker::DB::Clean::BranchSelector.from_hash(hash)
41
+ end
42
+ end
43
+
34
44
  def rake_task &block
35
45
  namespace :pact_broker do
36
46
  namespace :db do
37
- desc "Clean unnecessary pacts and verifications from database"
47
+ desc "Clean unnecessary pacts, verifications and stale branches from database"
38
48
  task :clean do | _t, _args |
39
49
  instance_eval(&block)
40
50
 
@@ -61,10 +71,14 @@ module PactBroker
61
71
  output "Deleting oldest #{version_deletion_limit} versions, keeping versions that match the configured selectors", keep_version_selectors.collect(&:to_hash)
62
72
  end
63
73
 
74
+ output_keep_branch_selectors
75
+
64
76
  start_time = Time.now
65
77
  results = PactBroker::DB::CleanIncremental.call(database_connection,
66
78
  keep: keep_version_selectors,
79
+ keep_branches: keep_branch_selectors,
67
80
  limit: version_deletion_limit,
81
+ branch_limit: branch_deletion_limit,
68
82
  logger: logger,
69
83
  dry_run: dry_run
70
84
  )
@@ -109,6 +123,14 @@ module PactBroker
109
123
  logger ? logger.info("#{prefix}#{string}", payload) : puts("#{prefix}#{string} #{payload.to_json}")
110
124
  end
111
125
 
126
+ def output_keep_branch_selectors
127
+ if keep_branch_selectors.nil? || keep_branch_selectors.empty?
128
+ output "Stale branch cleanup is disabled (no keep_branch_selectors configured)"
129
+ else
130
+ output "Deleting stale branches that do not match the configured selectors (main branches are always kept)", keep_branch_selectors.collect(&:to_hash)
131
+ end
132
+ end
133
+
112
134
  def add_defaults_to_keep_selectors
113
135
  if keep_version_selectors.none?(&:currently_deployed?)
114
136
  selector = PactBroker::DB::Clean::Selector.new(deployed: true)
@@ -10,7 +10,7 @@ module PactBroker
10
10
  include PactBroker::Repositories
11
11
  include PactBroker::Repositories::Scopes
12
12
 
13
- # Ideally this would just be a sequence, but Sqlite and MySQL don't support sequences
13
+ # Ideally this would just be a sequence, but Sqlite doesn't support sequences
14
14
  # in the way we need to use them ie. determining what the next number will be before we
15
15
  # create the record, because Webmachine wants to set the URL of the resource that is about
16
16
  # to be created *before* we actually create it.
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = "2.119.0"
2
+ VERSION = "2.121.0"
3
3
  end
@@ -13,7 +13,7 @@ module PactBroker
13
13
  query = scope_for(Branch).where(pacticipant_id: pacticipant.id).select_all_qualified
14
14
  query = query.filter(:name, filter_options[:query_string]) if filter_options[:query_string]
15
15
  query
16
- .order(Sequel.desc(:created_at), Sequel.desc(:id))
16
+ .order(Sequel.desc(:updated_at), Sequel.desc(:id))
17
17
  .eager(*eager_load_associations)
18
18
  .all_with_pagination_options(pagination_options)
19
19
  end
@@ -21,7 +21,7 @@ module PactBroker
21
21
  # @return [Array<PactBroker::Contracts::Notice>]
22
22
  def branch_deletion_notices(pacticipant, exclude:)
23
23
  count = branch_repository.count_branches_to_delete(pacticipant, exclude: exclude)
24
- remaining = branch_repository.remaining_branches_after_future_deletion(pacticipant, exclude: exclude).sort_by(&:created_at).collect(&:name).join(", ")
24
+ remaining = branch_repository.remaining_branches_after_future_deletion(pacticipant, exclude: exclude).sort_by(&:updated_at).collect(&:name).join(", ")
25
25
  [PactBroker::Contracts::Notice.success(message("messages.branch.bulk_delete", count: count, pacticipant_name: pacticipant.name, remaining: remaining))]
26
26
  end
27
27
  end
@@ -27,13 +27,14 @@ module PactBroker
27
27
  branch = find_or_create_branch(version.pacticipant, branch_name)
28
28
  branch_version = version.branch_version_for_branch(branch)
29
29
  if branch_version
30
- PactBroker.logger.info("Updating branch version #{branch_version.inspect}, time: #{Time.now}")
31
- branch_version.update(updated_at: Sequel.datetime_class.now)
30
+ PactBroker.logger.debug("Branch version already exists #{branch_version.inspect}")
32
31
  else
33
32
  PactBroker.logger.info("Creating branch version time: #{Time.now}")
34
33
  branch_version = PactBroker::Versions::BranchVersion.new(version: version, branch: branch, auto_created: auto_created).insert_ignore
35
34
  PactBroker::Versions::BranchHead.new(branch: branch, branch_version: branch_version).upsert
36
35
  end
36
+ now = Sequel.datetime_class.now
37
+ Branch.where(id: branch.id).where(Sequel[:updated_at] < now - Rational(60, 86400)).update(updated_at: now)
37
38
  pacticipant_service.maybe_set_main_branch(version.pacticipant, branch_name)
38
39
  branch_version
39
40
  end
@@ -89,22 +89,13 @@ module PactBroker
89
89
  end
90
90
 
91
91
  def create_or_update(pacticipant, version_number, open_struct_version)
92
- saved_version = PactBroker::Domain::Version.where(pacticipant_id: pacticipant.id, number: version_number).single_record
93
92
  params = open_struct_version.to_h
94
93
  tags = params.delete(:tags)
95
94
  branch_name = params.delete(:branch)
96
- if saved_version
97
- saved_version.update(params)
98
- else
99
- # Upsert is only for race conditions
100
- # Upsert blanks out any fields that are not provided
101
- saved_version = PactBroker::Domain::Version.new(
102
- params.merge(
103
- pacticipant_id: pacticipant.id,
104
- number: version_number
105
- ).compact
106
- ).upsert
107
- end
95
+ insert_params = params.merge(pacticipant_id: pacticipant.id, number: version_number).compact
96
+ saved_version = PactBroker::Domain::Version.new(insert_params).insert_ignore
97
+ update_params = params.reject { |k, _| [:created_at, :order].include?(k) }
98
+ saved_version.update(update_params) if update_params.any?
108
99
 
109
100
  branch_version_repository.add_branch(saved_version, branch_name) if branch_name
110
101
  replace_tags(saved_version, tags) if tags
@@ -3,7 +3,7 @@ require "pact_broker/webhooks/pact_and_verification_parameters"
3
3
  module PactBroker
4
4
  module Webhooks
5
5
  class Render
6
- TEMPLATE_PARAMETER_REGEXP = /\$\{pactbroker\.[^\}]+\}/
6
+ TEMPLATE_PARAMETER_REGEXP = /\$\{pactbroker\.[^\}]++\}/
7
7
  DEFAULT_ESCAPER = lambda { |it| it }
8
8
 
9
9
  def self.includes_parameter?(value)
@@ -19,7 +19,7 @@ module Sequel
19
19
 
20
20
  def upsert(opts = {})
21
21
  @upsert_plugin_upserting = true
22
- if postgres? || mysql?
22
+ if postgres?
23
23
  save(opts)
24
24
  else
25
25
  manual_upsert(opts)
@@ -92,9 +92,6 @@ module Sequel
92
92
  if upsert_plugin_upserting
93
93
  if postgres?
94
94
  super.insert_conflict(update: values_to_update, target: self.class.upsert_plugin_identifying_columns)
95
- elsif mysql?
96
- columns_to_update = values_to_update.keys - self.class.upsert_plugin_identifying_columns
97
- super.on_duplicate_key_update(*columns_to_update)
98
95
  else
99
96
  super
100
97
  end
@@ -103,10 +100,6 @@ module Sequel
103
100
  end
104
101
  end
105
102
 
106
- def mysql?
107
- model.db.adapter_scheme.to_s =~ /mysql/
108
- end
109
-
110
103
  def postgres?
111
104
  model.db.adapter_scheme.to_s =~ /postgres/
112
105
  end
data/pact_broker.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
14
14
  gem.summary = %q{See description}
15
15
  gem.homepage = "https://github.com/pact-foundation/pact_broker"
16
16
 
17
- gem.required_ruby_version = ">= 2.7.0"
17
+ gem.required_ruby_version = ">= 3.2.0"
18
18
 
19
19
  gem.files = begin
20
20
  if Dir.exist?(".git")
@@ -49,22 +49,22 @@ Gem::Specification.new do |gem|
49
49
  gem.require_paths = ["lib"]
50
50
  gem.license = "MIT"
51
51
 
52
- gem.add_runtime_dependency "json", "~> 2.3"
52
+ gem.add_runtime_dependency "json", ">= 2.19.9"
53
53
  gem.add_runtime_dependency "psych", "~> 5.0"
54
54
  gem.add_runtime_dependency "roar", "~> 1.1"
55
55
  gem.add_runtime_dependency "dry-validation", "~> 1.8"
56
56
  gem.add_runtime_dependency "reform", "~> 2.6"
57
57
  gem.add_runtime_dependency "sequel", "~> 5.90"
58
- gem.add_runtime_dependency "webmachine", ">= 2.0.0.beta", "< 3.0"
58
+ gem.add_runtime_dependency "webmachine", "~> 2.0"
59
59
  gem.add_runtime_dependency "semver2", "~> 3.4.2"
60
60
  gem.add_runtime_dependency "rack", "~> 3.2"
61
- gem.add_runtime_dependency "redcarpet", ">= 3.5.1", "~>3.5"
62
- gem.add_runtime_dependency "pact-support" , ">= 1.21.2", "~> 1.21"
61
+ gem.add_runtime_dependency "redcarpet", "~> 3.5"
62
+ gem.add_runtime_dependency "pact-support", "~> 1.21"
63
63
  gem.add_runtime_dependency "haml", "~>5.0"
64
64
  gem.add_runtime_dependency "sucker_punch", "~>3.0"
65
65
  gem.add_runtime_dependency "rack-protection", "~> 4.1"
66
66
  gem.add_runtime_dependency "table_print", "~> 1.5"
67
- gem.add_runtime_dependency "semantic_logger", "~> 4.11"
67
+ gem.add_runtime_dependency "semantic_logger", "~> 5.0"
68
68
  gem.add_runtime_dependency "sanitize", "~> 6.0"
69
69
  gem.add_runtime_dependency "wisper", "~> 2.0"
70
70
  gem.add_runtime_dependency "anyway_config", "~> 2.1"
@@ -74,6 +74,7 @@ Gem::Specification.new do |gem|
74
74
  gem.add_runtime_dependency "mutex_m", "~> 0.3" # until as-notifications -> webmachine requires it
75
75
  gem.add_runtime_dependency "csv", "~> 3.0" # lib/pact_broker/api/decorators/relationships_csv_decorator.rb
76
76
  # dropped in ruby 3.5 stdlib
77
- gem.add_runtime_dependency "ostruct", "~> 0.5"
77
+ gem.add_runtime_dependency "ostruct", "~> 0.5"
78
+ gem.add_runtime_dependency "json_schemer", "~> 2.5"
78
79
  gem.add_development_dependency "pstore", "~> 0.1" # until webmachine requires it
79
80
  end