pact_broker 2.107.1 → 2.109.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/Gemfile +5 -6
  4. data/README.md +1 -0
  5. data/db/migrations/20230615_add_integrations_contract_data_updated_at.rb +13 -0
  6. data/db/migrations/20230616_set_integrations_contract_data_updated_at.rb +11 -0
  7. data/db/migrations/20231002_add_version_id_index_to_released_version.rb +21 -0
  8. data/db/migrations/20231003_add_version_id_index_to_deployed_version.rb +21 -0
  9. data/docs/CONFIGURATION.md +10 -0
  10. data/lib/pact_broker/api/contracts/base_contract.rb +2 -1
  11. data/lib/pact_broker/api/contracts/dry_validation_errors_formatter.rb +2 -0
  12. data/lib/pact_broker/api/contracts/pagination_query_params_schema.rb +19 -0
  13. data/lib/pact_broker/api/contracts/publish_contracts_contract_contract.rb +29 -18
  14. data/lib/pact_broker/api/decorators/base_decorator.rb +40 -1
  15. data/lib/pact_broker/api/decorators/branch_decorator.rb +35 -0
  16. data/lib/pact_broker/api/decorators/branch_version_decorator.rb +16 -0
  17. data/lib/pact_broker/api/decorators/configuration.rb +19 -0
  18. data/lib/pact_broker/api/decorators/custom_error_problem_json_decorator.rb +1 -1
  19. data/lib/pact_broker/api/decorators/decorator_context_creator.rb +47 -2
  20. data/lib/pact_broker/api/decorators/deployed_version_decorator.rb +2 -1
  21. data/lib/pact_broker/api/decorators/deployed_versions_decorator.rb +2 -2
  22. data/lib/pact_broker/api/decorators/dry_validation_errors_decorator.rb +32 -0
  23. data/lib/pact_broker/api/decorators/dry_validation_errors_problem_json_decorator.rb +24 -0
  24. data/lib/pact_broker/api/decorators/embedded_branch_version_decorator.rb +2 -2
  25. data/lib/pact_broker/api/decorators/embedded_deployed_version_decorator.rb +30 -0
  26. data/lib/pact_broker/api/decorators/embedded_error_problem_json_decorator.rb +84 -0
  27. data/lib/pact_broker/api/decorators/embedded_released_version_decorator.rb +27 -0
  28. data/lib/pact_broker/api/decorators/embedded_version_decorator.rb +0 -3
  29. data/lib/pact_broker/api/decorators/error_decorator.rb +30 -0
  30. data/lib/pact_broker/api/decorators/extended_pact_decorator.rb +6 -1
  31. data/lib/pact_broker/api/decorators/integration_decorator.rb +3 -2
  32. data/lib/pact_broker/api/decorators/integrations_decorator.rb +3 -0
  33. data/lib/pact_broker/api/decorators/notices_decorator.rb +11 -0
  34. data/lib/pact_broker/api/decorators/pact_pacticipant_decorator.rb +1 -5
  35. data/lib/pact_broker/api/decorators/pact_versions_decorator.rb +0 -1
  36. data/lib/pact_broker/api/decorators/pact_webhooks_status_decorator.rb +0 -1
  37. data/lib/pact_broker/api/decorators/pacticipant_branches_decorator.rb +32 -0
  38. data/lib/pact_broker/api/decorators/pacticipant_decorator.rb +11 -1
  39. data/lib/pact_broker/api/decorators/{pacticipant_collection_decorator.rb → pacticipants_decorator.rb} +8 -4
  40. data/lib/pact_broker/api/decorators/pagination_links.rb +2 -2
  41. data/lib/pact_broker/api/decorators/publish_contract_decorator.rb +6 -1
  42. data/lib/pact_broker/api/decorators/released_versions_decorator.rb +2 -2
  43. data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +2 -2
  44. data/lib/pact_broker/api/decorators/validation_errors_decorator.rb +30 -0
  45. data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +4 -6
  46. data/lib/pact_broker/api/decorators/version_decorator.rb +5 -3
  47. data/lib/pact_broker/api/decorators/versions_decorator.rb +24 -14
  48. data/lib/pact_broker/api/decorators/webhook_decorator.rb +1 -1
  49. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +2 -0
  50. data/lib/pact_broker/api/middleware/configuration.rb +2 -0
  51. data/lib/pact_broker/api/pact_broker_urls.rb +18 -2
  52. data/lib/pact_broker/api/resources/after_reply.rb +15 -0
  53. data/lib/pact_broker/api/resources/all_webhooks.rb +3 -3
  54. data/lib/pact_broker/api/resources/badge_methods.rb +2 -1
  55. data/lib/pact_broker/api/resources/base_resource.rb +6 -4
  56. data/lib/pact_broker/api/resources/branch.rb +40 -0
  57. data/lib/pact_broker/api/resources/branch_versions.rb +59 -0
  58. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_badge.rb +1 -1
  59. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +1 -1
  60. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +1 -1
  61. data/lib/pact_broker/api/resources/dashboard.rb +10 -0
  62. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +1 -1
  63. data/lib/pact_broker/api/resources/environment.rb +4 -0
  64. data/lib/pact_broker/api/resources/environments.rb +1 -1
  65. data/lib/pact_broker/api/resources/error_handler.rb +18 -52
  66. data/lib/pact_broker/api/resources/error_handling_methods.rb +40 -14
  67. data/lib/pact_broker/api/resources/error_response_generator.rb +23 -6
  68. data/lib/pact_broker/api/resources/event_methods.rb +15 -0
  69. data/lib/pact_broker/api/resources/filter_methods.rb +15 -0
  70. data/lib/pact_broker/api/resources/group.rb +11 -2
  71. data/lib/pact_broker/api/resources/index.rb +6 -0
  72. data/lib/pact_broker/api/resources/integrations.rb +18 -4
  73. data/lib/pact_broker/api/resources/latest_version.rb +2 -0
  74. data/lib/pact_broker/api/resources/pact.rb +16 -7
  75. data/lib/pact_broker/api/resources/pacticipant_branches.rb +67 -0
  76. data/lib/pact_broker/api/resources/pacticipants.rb +26 -7
  77. data/lib/pact_broker/api/resources/pacticipants_for_label.rb +2 -2
  78. data/lib/pact_broker/api/resources/pagination_methods.rb +11 -1
  79. data/lib/pact_broker/api/resources/publish_contracts.rb +1 -1
  80. data/lib/pact_broker/api/resources/verifications.rb +9 -4
  81. data/lib/pact_broker/api/resources/versions.rb +12 -0
  82. data/lib/pact_broker/api.rb +5 -0
  83. data/lib/pact_broker/app.rb +10 -9
  84. data/lib/pact_broker/application_context.rb +29 -25
  85. data/lib/pact_broker/async/after_reply.rb +30 -0
  86. data/lib/pact_broker/config/runtime_configuration.rb +9 -21
  87. data/lib/pact_broker/config/runtime_configuration_coercion_methods.rb +32 -2
  88. data/lib/pact_broker/config/runtime_configuration_database_methods.rb +1 -1
  89. data/lib/pact_broker/config/runtime_configuration_logging_methods.rb +15 -5
  90. data/lib/pact_broker/configuration.rb +29 -12
  91. data/lib/pact_broker/contracts/contract_to_publish.rb +4 -5
  92. data/lib/pact_broker/contracts/contracts_to_publish.rb +8 -0
  93. data/lib/pact_broker/contracts/service.rb +9 -3
  94. data/lib/pact_broker/dataset/page.rb +22 -0
  95. data/lib/pact_broker/dataset.rb +122 -0
  96. data/lib/pact_broker/db/data_migrations/set_contract_data_updated_at_for_integrations.rb +47 -0
  97. data/lib/pact_broker/db/migrate_data.rb +1 -0
  98. data/lib/pact_broker/db/models.rb +1 -1
  99. data/lib/pact_broker/deployments/currently_deployed_version_id.rb +2 -5
  100. data/lib/pact_broker/deployments/deployed_version.rb +3 -3
  101. data/lib/pact_broker/deployments/environment.rb +0 -2
  102. data/lib/pact_broker/deployments/released_version.rb +4 -5
  103. data/lib/pact_broker/doc/views/index/pacticipant-branch.markdown +25 -0
  104. data/lib/pact_broker/domain/label.rb +6 -3
  105. data/lib/pact_broker/domain/pact.rb +10 -5
  106. data/lib/pact_broker/domain/pacticipant.rb +4 -34
  107. data/lib/pact_broker/domain/tag.rb +4 -5
  108. data/lib/pact_broker/domain/verification.rb +2 -4
  109. data/lib/pact_broker/domain/version.rb +12 -19
  110. data/lib/pact_broker/errors/error_reporter.rb +30 -0
  111. data/lib/pact_broker/errors.rb +2 -15
  112. data/lib/pact_broker/events/subscriber.rb +12 -4
  113. data/lib/pact_broker/feature_toggle.rb +1 -1
  114. data/lib/pact_broker/groups/service.rb +38 -5
  115. data/lib/pact_broker/index/service.rb +1 -2
  116. data/lib/pact_broker/integrations/event_listener.rb +23 -0
  117. data/lib/pact_broker/integrations/integration.rb +24 -2
  118. data/lib/pact_broker/integrations/repository.rb +34 -1
  119. data/lib/pact_broker/integrations/service.rb +17 -18
  120. data/lib/pact_broker/labels/repository.rb +4 -8
  121. data/lib/pact_broker/locale/en.yml +5 -0
  122. data/lib/pact_broker/logging.rb +10 -0
  123. data/lib/pact_broker/matrix/every_row.rb +58 -40
  124. data/lib/pact_broker/matrix/integration_row.rb +95 -0
  125. data/lib/pact_broker/matrix/integrations_repository.rb +133 -0
  126. data/lib/pact_broker/matrix/matrix_row.rb +88 -0
  127. data/lib/pact_broker/matrix/matrix_row_dataset_module.rb +185 -0
  128. data/lib/pact_broker/matrix/matrix_row_instance_methods.rb +150 -0
  129. data/lib/pact_broker/matrix/matrix_row_verification_dataset_module.rb +83 -0
  130. data/lib/pact_broker/matrix/parse_query.rb +1 -0
  131. data/lib/pact_broker/matrix/repository.rb +62 -285
  132. data/lib/pact_broker/matrix/resolved_selector.rb +13 -4
  133. data/lib/pact_broker/matrix/resolved_selector_builder.rb +84 -0
  134. data/lib/pact_broker/matrix/resolved_selectors_builder.rb +39 -0
  135. data/lib/pact_broker/matrix/row_ignorer.rb +36 -0
  136. data/lib/pact_broker/matrix/selector_ignorer.rb +59 -0
  137. data/lib/pact_broker/matrix/selector_resolver.rb +130 -0
  138. data/lib/pact_broker/metrics/service.rb +4 -9
  139. data/lib/pact_broker/pacticipants/latest_version_for_pacticipant_eager_loader.rb +33 -0
  140. data/lib/pact_broker/pacticipants/repository.rb +7 -9
  141. data/lib/pact_broker/pacticipants/service.rb +2 -2
  142. data/lib/pact_broker/pacts/generate_sha.rb +9 -4
  143. data/lib/pact_broker/pacts/latest_pact_publication_id_for_consumer_version.rb +2 -4
  144. data/lib/pact_broker/pacts/metadata.rb +3 -1
  145. data/lib/pact_broker/pacts/pact_params.rb +1 -1
  146. data/lib/pact_broker/pacts/pact_publication.rb +23 -5
  147. data/lib/pact_broker/pacts/pact_publication_dataset_module.rb +5 -1
  148. data/lib/pact_broker/pacts/pact_version.rb +2 -3
  149. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +2 -5
  150. data/lib/pact_broker/pacts/placeholder_pact.rb +3 -1
  151. data/lib/pact_broker/pacts/repository.rb +12 -12
  152. data/lib/pact_broker/pacts/service.rb +12 -13
  153. data/lib/pact_broker/repositories.rb +9 -1
  154. data/lib/pact_broker/string_refinements.rb +4 -0
  155. data/lib/pact_broker/tags/head_pact_tags.rb +2 -5
  156. data/lib/pact_broker/tags/repository.rb +3 -7
  157. data/lib/pact_broker/test/test_data_builder.rb +57 -2
  158. data/lib/pact_broker/ui/controllers/base_controller.rb +4 -2
  159. data/lib/pact_broker/ui/controllers/groups.rb +2 -1
  160. data/lib/pact_broker/ui/view_models/matrix_line.rb +4 -4
  161. data/lib/pact_broker/ui/views/groups/show.html.erb +2 -1
  162. data/lib/pact_broker/ui.rb +20 -9
  163. data/lib/pact_broker/verifications/latest_verification_for_consumer_and_provider.rb +0 -3
  164. data/lib/pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version.rb +2 -4
  165. data/lib/pact_broker/verifications/repository.rb +2 -5
  166. data/lib/pact_broker/verifications/sequence.rb +1 -4
  167. data/lib/pact_broker/verifications/service.rb +4 -0
  168. data/lib/pact_broker/version.rb +1 -1
  169. data/lib/pact_broker/versions/branch.rb +2 -5
  170. data/lib/pact_broker/versions/branch_head.rb +0 -3
  171. data/lib/pact_broker/versions/branch_repository.rb +76 -0
  172. data/lib/pact_broker/versions/branch_service.rb +13 -25
  173. data/lib/pact_broker/versions/branch_version.rb +0 -3
  174. data/lib/pact_broker/versions/branch_version_repository.rb +17 -0
  175. data/lib/pact_broker/versions/repository.rb +19 -2
  176. data/lib/pact_broker/versions/sequence.rb +1 -3
  177. data/lib/pact_broker/versions/service.rb +4 -0
  178. data/lib/pact_broker/webhooks/execution.rb +3 -7
  179. data/lib/pact_broker/webhooks/job.rb +16 -7
  180. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +2 -2
  181. data/lib/pact_broker/webhooks/repository.rb +0 -1
  182. data/lib/pact_broker/webhooks/trigger_service.rb +11 -12
  183. data/lib/pact_broker/webhooks/triggered_webhook.rb +3 -4
  184. data/lib/pact_broker/webhooks/webhook.rb +2 -2
  185. data/lib/pact_broker/webhooks/webhook_event.rb +2 -5
  186. data/lib/rack/pact_broker/add_cache_header.rb +14 -0
  187. data/lib/rack/pact_broker/application_context.rb +16 -0
  188. data/lib/rack/pact_broker/configurable_make_it_later.rb +1 -1
  189. data/lib/rack/pact_broker/invalid_uri_protection.rb +19 -3
  190. data/lib/webmachine/describe_routes.rb +55 -39
  191. data/lib/webmachine/render_error_monkey_patch.rb +13 -4
  192. data/pact_broker.gemspec +5 -5
  193. metadata +54 -31
  194. data/lib/pact_broker/api/decorators/decorator_context.rb +0 -22
  195. data/lib/pact_broker/matrix/query_builder.rb +0 -90
  196. data/lib/pact_broker/matrix/query_ids.rb +0 -40
  197. data/lib/pact_broker/matrix/quick_row.rb +0 -458
  198. data/lib/pact_broker/relationships/groupify.rb +0 -45
  199. data/lib/pact_broker/repositories/helpers.rb +0 -96
  200. data/lib/pact_broker/repositories/page.rb +0 -24
  201. data/lib/pact_broker/tags/tag_with_latest_flag.rb +0 -28
@@ -1,15 +1,45 @@
1
1
  require "pact_broker/config/space_delimited_string_list"
2
2
  require "pact_broker/config/space_delimited_integer_list"
3
+ require "pact_broker/hash_refinements"
4
+ require "pact_broker/error"
3
5
 
4
6
  module PactBroker
5
7
  module Config
6
8
  module RuntimeConfigurationCoercionMethods
7
9
 
8
- def all_keys_are_number_strings?(hash)
10
+ using PactBroker::HashRefinements
11
+
12
+ COERCE_FEATURES = lambda { | value |
13
+ if value.is_a?(String)
14
+ value.split(" ").each_with_object({}) { | k, h | h[k.downcase.to_sym] = true }
15
+ elsif value.is_a?(Array)
16
+ value.each_with_object({}) { | k, h | h[k.downcase.to_sym] = true }
17
+ elsif value.is_a?(Hash)
18
+ value.each_with_object({}) { | (k, v), new_hash | new_hash[k.downcase.to_sym] = Anyway::AutoCast.call(v) }
19
+ else
20
+ raise PactBroker::ConfigurationError, "Expected a String, Hash or Array for features but got a #{value.class.name}"
21
+ end
22
+ }
23
+
24
+ COERCE_WEBHOOKS = lambda { | value |
25
+ if value.is_a?(Hash) # from env vars
26
+ if RuntimeConfigurationCoercionMethods.all_keys_are_number_strings?(value)
27
+ RuntimeConfigurationCoercionMethods.convert_hash_with_number_string_keys_to_array(value).collect(&:symbolize_keys)
28
+ else
29
+ raise PactBroker::ConfigurationError, "Could not coerce #{value} into an array of webhook configurations. Please check docs for the expected format."
30
+ end
31
+ elsif value.is_a?(Array) # from YAML
32
+ value.collect(&:symbolize_keys)
33
+ else
34
+ raise PactBroker::ConfigurationError, "Webhook certificates cannot be set using a #{value.class}"
35
+ end
36
+ }
37
+
38
+ def self.all_keys_are_number_strings?(hash)
9
39
  hash.keys.all? { | k | k.to_s.to_i.to_s == k } # is an integer as a string
10
40
  end
11
41
 
12
- def convert_hash_with_number_string_keys_to_array(hash)
42
+ def self.convert_hash_with_number_string_keys_to_array(hash)
13
43
  hash.keys.collect{ |k| [k, k.to_i]}.sort_by(&:last).collect(&:first).collect do | key |
14
44
  hash[key]
15
45
  end
@@ -27,7 +27,7 @@ module PactBroker
27
27
  allow_missing_migration_files: true,
28
28
  validate_database_connection_config: true,
29
29
  database_statement_timeout: 15,
30
- metrics_sql_statement_timeout: 30,
30
+ metrics_sql_statement_timeout: 30, # TODO get rid of this in next major version
31
31
  database_connection_validation_timeout: nil
32
32
  )
33
33
 
@@ -25,16 +25,19 @@ module PactBroker
25
25
  # base_url raises a not implemented error
26
26
  def log_configuration(logger)
27
27
  source_info = to_source_trace
28
- (self.class.config_attributes - [:base_url]).collect(&:to_s).each_with_object({})do | key, new_hash |
28
+ attributes_to_log.collect(&:to_s).each_with_object({}) do | key, new_hash |
29
29
  new_hash[key] = {
30
30
  value: self.send(key.to_sym),
31
- source: source_info.dig(key, :source) || {:type=>:defaults}
31
+ source: source_info.dig(key, :source) || source_info.dig(key) || { type: :defaults }
32
32
  }
33
33
  end.sort_by { |key, _| key }.each { |key, value| log_config_inner(key, value, logger) }
34
- if self.webhook_redact_sensitive_data == false
35
- logger.warn("WARNING!!! webhook_redact_sensitive_data is set to false. This will allow authentication information to be included in the webhook logs. This should only be used for debugging purposes. Do not run the application permanently in production with this value.")
36
- end
34
+ print_warnings(logger)
35
+ end
36
+
37
+ def attributes_to_log
38
+ self.class.config_attributes - [:base_url]
37
39
  end
40
+ private :attributes_to_log
38
41
 
39
42
  def log_config_inner(key, value, logger)
40
43
  # TODO fix the source display for webhook_certificates set by environment variables
@@ -68,6 +71,13 @@ module PactBroker
68
71
  end
69
72
  end
70
73
  private :redact
74
+
75
+ def print_warnings(logger)
76
+ if self.webhook_redact_sensitive_data == false
77
+ logger.warn("WARNING!!! webhook_redact_sensitive_data is set to false. This will allow authentication information to be included in the webhook logs. This should only be used for debugging purposes. Do not run the application permanently in production with this value.")
78
+ end
79
+ end
80
+ private :print_warnings
71
81
  end
72
82
 
73
83
  def self.included(receiver)
@@ -82,23 +82,14 @@ module PactBroker
82
82
 
83
83
  def override_runtime_configuration!(overrides)
84
84
  new_runtime_configuration = runtime_configuration.dup
85
- valid_overrides = {}
86
- invalid_overrides = {}
87
-
88
- overrides.each do | key, value |
89
- if new_runtime_configuration.respond_to?("#{key}=")
90
- valid_overrides[key] = Anyway::AutoCast.call(value)
91
- else
92
- invalid_overrides[key] = Anyway::AutoCast.call(value)
93
- end
94
- end
85
+ valid_overrides, invalid_overrides = identify_valid_and_invalid_configuration_overrides(new_runtime_configuration, overrides)
95
86
 
96
87
  if logger.debug?
97
- logger.debug("Overridding runtime configuration", overrides: valid_overrides, ignoring: invalid_overrides)
88
+ logger.debug("Overriding runtime configuration", overrides: valid_overrides, ignoring: invalid_overrides)
98
89
  end
99
90
 
100
91
  valid_overrides.each do | key, value |
101
- new_runtime_configuration.public_send("#{key}=", value)
92
+ override_or_merge_value(new_runtime_configuration, key, value)
102
93
  end
103
94
 
104
95
  self.runtime_configuration = new_runtime_configuration
@@ -220,5 +211,31 @@ module PactBroker
220
211
  require "pact_broker/config/load"
221
212
  PactBroker::Config::Load.call(runtime_configuration)
222
213
  end
214
+
215
+ private
216
+
217
+ def identify_valid_and_invalid_configuration_overrides(new_runtime_configuration, overrides)
218
+ valid_overrides = {}
219
+ invalid_overrides = {}
220
+
221
+ overrides.each do | key, value |
222
+ if new_runtime_configuration.respond_to?("#{key}=")
223
+ valid_overrides[key] = Anyway::AutoCast.call(value)
224
+ else
225
+ invalid_overrides[key] = Anyway::AutoCast.call(value)
226
+ end
227
+ end
228
+
229
+ return valid_overrides, invalid_overrides
230
+ end
231
+
232
+ def override_or_merge_value(new_runtime_configuration, key, value)
233
+ # Do a merge if original value and new value are both hashes
234
+ if value.is_a?(Hash) && new_runtime_configuration.public_send(key).is_a?(Hash)
235
+ new_runtime_configuration.public_send("#{key}=", new_runtime_configuration.public_send(key).merge(value))
236
+ else
237
+ new_runtime_configuration.public_send("#{key}=", value)
238
+ end
239
+ end
223
240
  end
224
241
  end
@@ -1,11 +1,10 @@
1
1
  module PactBroker
2
2
  module Contracts
3
- ContractToPublish = Struct.new(:consumer_name, :provider_name, :decoded_content, :content_type, :specification, :on_conflict) do
4
- # rubocop: disable Metrics/ParameterLists
5
- def self.from_hash(consumer_name: nil, provider_name: nil, decoded_content: nil, content_type: nil, specification: nil, on_conflict: nil)
6
- new(consumer_name, provider_name, decoded_content, content_type, specification, on_conflict)
3
+ ContractToPublish = Struct.new(:consumer_name, :provider_name, :decoded_content, :content_type, :specification, :on_conflict, :pact_version_sha, keyword_init: true) do
4
+
5
+ def self.from_hash(hash)
6
+ new(**hash)
7
7
  end
8
- # rubocop: enable Metrics/ParameterLists
9
8
 
10
9
  def pact?
11
10
  specification == "pact"
@@ -10,6 +10,14 @@ module PactBroker
10
10
  def pacticipant_names
11
11
  contracts.flat_map(&:pacticipant_names).uniq
12
12
  end
13
+
14
+ def provider_names
15
+ contracts.flat_map(&:provider_name).uniq
16
+ end
17
+
18
+ def logging_info
19
+ to_h.slice(:pacticipant_name, :pacticipant_version_number, :tags, :branch, :build_url).merge(provider_names: provider_names)
20
+ end
13
21
  end
14
22
  end
15
23
  end
@@ -31,9 +31,11 @@ module PactBroker
31
31
  end
32
32
 
33
33
  def publish(parsed_contracts, base_url: )
34
+ logger.info("Publishing contracts", parsed_contracts.logging_info)
34
35
  version, version_notices = create_version(parsed_contracts)
35
36
  tags = create_tags(parsed_contracts, version)
36
37
  pacts, pact_notices = create_pacts(parsed_contracts, base_url)
38
+ update_integrations(pacts)
37
39
  notices = version_notices + pact_notices
38
40
  ContractsPublicationResults.from_hash(
39
41
  pacticipant: version.pacticipant,
@@ -55,7 +57,7 @@ module PactBroker
55
57
  parsed_contracts.contracts.collect do | contract_to_publish |
56
58
  pact_params = create_pact_params(parsed_contracts, contract_to_publish)
57
59
  existing_pact = pact_service.find_pact(pact_params)
58
- if existing_pact && pact_service.disallowed_modification?(existing_pact, contract_to_publish.decoded_content)
60
+ if existing_pact && pact_service.disallowed_modification?(existing_pact, contract_to_publish.pact_version_sha)
59
61
  add_pact_conflict_notice(notices, parsed_contracts, contract_to_publish, existing_pact.json_content, contract_to_publish.decoded_content)
60
62
  end
61
63
  end
@@ -65,7 +67,7 @@ module PactBroker
65
67
 
66
68
  def add_pact_conflict_notice(notices, parsed_contracts, contract_to_publish, existing_json_content, new_json_content)
67
69
  message_params = {
68
- consumer_name: contract_to_publish.provider_name,
70
+ consumer_name: contract_to_publish.consumer_name,
69
71
  consumer_version_number: parsed_contracts.pacticipant_version_number,
70
72
  provider_name: contract_to_publish.provider_name
71
73
  }
@@ -132,7 +134,7 @@ module PactBroker
132
134
  pact_params = create_pact_params(parsed_contracts, contract_to_publish)
133
135
  existing_pact = pact_service.find_pact(pact_params)
134
136
  listener = TriggeredWebhooksCreatedListener.new
135
- created_pact = create_or_merge_pact(contract_to_publish.merge?, existing_pact, pact_params, listener)
137
+ created_pact = create_or_merge_pact(contract_to_publish.merge?, existing_pact, pact_params.merge(pact_version_sha: contract_to_publish.pact_version_sha), listener)
136
138
  notices.concat(notices_for_pact(parsed_contracts, contract_to_publish, existing_pact, created_pact, listener, base_url))
137
139
  created_pact
138
140
  end
@@ -302,6 +304,10 @@ module PactBroker
302
304
  PactBroker::Api::PactBrokerUrls.triggered_webhook_logs_url(triggered_webhook, base_url)
303
305
  end
304
306
 
307
+ def update_integrations(pacts)
308
+ integration_service.handle_bulk_contract_data_published(pacts)
309
+ end
310
+
305
311
  private :url_for_triggered_webhook
306
312
  end
307
313
  end
@@ -0,0 +1,22 @@
1
+ require "forwardable"
2
+
3
+ # An array that provides the pagination details
4
+
5
+ module PactBroker
6
+ module Dataset
7
+ class Page < Array
8
+ extend Forwardable
9
+
10
+ attr_reader :query
11
+
12
+ PAGE_PROPERTIES = [:page_size, :page_count, :page_range, :current_page, :next_page, :prev_page, :first_page?, :last_page?, :pagination_record_count, :current_page_record_count, :current_page_record_range]
13
+
14
+ delegate PAGE_PROPERTIES => :query
15
+
16
+ def initialize(array, query)
17
+ super(array)
18
+ @query = query
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,122 @@
1
+ require "sequel"
2
+ require "pact_broker/dataset/page"
3
+
4
+ Sequel.extension :escaped_like
5
+
6
+ module PactBroker
7
+ module Dataset
8
+ module Helpers
9
+ extend self
10
+
11
+ def mysql?
12
+ Sequel::Model.db.adapter_scheme.to_s =~ /mysql/
13
+ end
14
+
15
+ def postgres?
16
+ Sequel::Model.db.adapter_scheme.to_s =~ /postgres/
17
+ end
18
+
19
+ def escape_wildcards(value)
20
+ value.gsub("_", "\\_").gsub("%", "\\%")
21
+ end
22
+ end
23
+
24
+ include Helpers
25
+
26
+ # Return a dataset that only includes the rows where the specified column
27
+ # includes the given query string.
28
+ # @return [Sequel::Dataset]
29
+ def filter(column_name, query_string)
30
+ where(Sequel.ilike(column_name, "%" + escape_wildcards(query_string) + "%"))
31
+ end
32
+
33
+ def name_like column_name, value
34
+ 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
41
+ else
42
+ Sequel.like(column_name, value.gsub("_", "\\_"), { case_insensitive: true })
43
+ end
44
+ end
45
+
46
+ def where_name_like(column_name, value)
47
+ where(name_like(column_name, value))
48
+ end
49
+
50
+ def select_all_qualified
51
+ select(Sequel[model.table_name].*)
52
+ end
53
+
54
+ def all_with_pagination_options(pagination_options)
55
+ if pagination_options&.any?
56
+ query = paginate(pagination_options[:page_number], pagination_options[:page_size])
57
+ Page.new(query.all, query)
58
+ else
59
+ all
60
+ end
61
+ end
62
+
63
+ def all_forbidding_lazy_load
64
+ all.each{ | row | row.forbid_lazy_load if row.respond_to?(:forbid_lazy_load) }
65
+ end
66
+
67
+ def all_allowing_lazy_load
68
+ all.each{ | row | row.allow_lazy_load if row.respond_to?(:allow_lazy_load) }
69
+ end
70
+
71
+ # @param [Symbol] max_column the name of the column of which to calculate the maxiumum
72
+ # @param [Array<Symbol>] group_by_columns the names of the columns by which to group
73
+ def max_group_by(max_column, group_by_columns, &extra_criteria_block)
74
+ maximums_base_query = extra_criteria_block ? extra_criteria_block.call(self) : self
75
+ maximums = maximums_base_query.select_group(*group_by_columns).select_append(Sequel.function(:max, max_column).as(:max_value))
76
+
77
+ max_join = group_by_columns.each_with_object({ Sequel[:maximums][:max_value] => max_column }) do | column_name, joins |
78
+ joins[Sequel[:maximums][column_name]] = column_name
79
+ end
80
+
81
+ join(maximums, max_join, table_alias: :maximums)
82
+ end
83
+
84
+ 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
91
+ end
92
+
93
+ def no_columns_selected?
94
+ opts[:select].nil?
95
+ end
96
+
97
+ def order_ignore_case column_name = :name
98
+ order(Sequel.function(:lower, column_name))
99
+ end
100
+
101
+ def order_append_ignore_case column_name = :name
102
+ order_append(Sequel.function(:lower, column_name))
103
+ end
104
+ end
105
+ end
106
+
107
+ module Sequel
108
+ # For matching identifying names based on the :use_case_sensitive_resource_names config setting.
109
+ # This has been used inconsistently, and in the next major version, support for case insensitive names will be dropped.
110
+ def self.name_like(column_name, value)
111
+ 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
118
+ else
119
+ Sequel.like(column_name, PactBroker::Dataset::Helpers.escape_wildcards(value), { case_insensitive: true })
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,47 @@
1
+ # Populate the newly created contract_data_updated_at date in the integrations table
2
+ # using the latest created_at date from the pact_publications or verifications tables.
3
+ module PactBroker
4
+ module DB
5
+ module DataMigrations
6
+ class SetContractDataUpdatedAtForIntegrations
7
+ def self.call(connection)
8
+ join = {
9
+ Sequel[:integrations][:consumer_id] => Sequel[:target][:consumer_id],
10
+ Sequel[:integrations][:provider_id] => Sequel[:target][:provider_id]
11
+ }
12
+
13
+ max_created_at_for_each_integration = integrations_max_created_at(connection).from_self(alias: :target).select(:created_at).where(join)
14
+
15
+ connection[:integrations]
16
+ .where(contract_data_updated_at: nil)
17
+ .update(contract_data_updated_at: max_created_at_for_each_integration)
18
+ end
19
+
20
+ # @return [Sequel::Dataset] the overall max created_at from the union of the pact_publications and verifications tables,
21
+ # for each integration keyed by consumer_id/provider_id
22
+ def self.integrations_max_created_at(connection)
23
+ pact_publication_max_created_at(connection)
24
+ .union(verification_max_created_at(connection))
25
+ .select_group(:consumer_id, :provider_id)
26
+ .select_append{ max(:created_at).as(:created_at) }
27
+ end
28
+
29
+ # @return [Sequel::Dataset] the max created_at from the pact_publications table
30
+ # for each integration keyed by consumer_id/provider_id
31
+ def self.pact_publication_max_created_at(connection)
32
+ connection[:pact_publications]
33
+ .select_group(:consumer_id, :provider_id)
34
+ .select_append{ max(:created_at).as(:created_at) }
35
+ end
36
+
37
+ # @return [Sequel::Dataset] the max created_at from the verifications table
38
+ # for each integration keyed by consumer_id/provider_id
39
+ def self.verification_max_created_at(connection)
40
+ connection[:verifications]
41
+ .select_group(:consumer_id, :provider_id)
42
+ .select_append{ max(:created_at).as(:created_at) }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -29,6 +29,7 @@ module PactBroker
29
29
  DataMigrations::CreateBranches.call(database_connection)
30
30
  DataMigrations::MigrateIntegrations.call(database_connection)
31
31
  DataMigrations::MigratePactVersionProviderTagSuccessfulVerifications.call(database_connection)
32
+ DataMigrations::SetContractDataUpdatedAtForIntegrations.call(database_connection)
32
33
  end
33
34
  end
34
35
  end
@@ -17,7 +17,7 @@ require "pact_broker/deployments/released_version"
17
17
  require "pact_broker/versions/branch"
18
18
  require "pact_broker/versions/branch_version"
19
19
  require "pact_broker/versions/branch_head"
20
- require "pact_broker/matrix/quick_row"
20
+ require "pact_broker/matrix/matrix_row"
21
21
  require "pact_broker/matrix/every_row"
22
22
 
23
23
  module PactBroker
@@ -1,5 +1,4 @@
1
- require "sequel"
2
- require "pact_broker/repositories/helpers"
1
+ require "pact_broker/dataset"
3
2
 
4
3
  module PactBroker
5
4
  module Deployments
@@ -8,9 +7,7 @@ module PactBroker
8
7
 
9
8
  plugin :upsert, identifying_columns: [:pacticipant_id, :environment_id, :target_for_index]
10
9
 
11
- dataset_module do
12
- include PactBroker::Repositories::Helpers
13
- end
10
+ dataset_module(PactBroker::Dataset)
14
11
  end
15
12
  end
16
13
  end
@@ -1,4 +1,4 @@
1
- require "pact_broker/repositories/helpers"
1
+ require "pact_broker/dataset"
2
2
  require "pact_broker/deployments/currently_deployed_version_id"
3
3
 
4
4
  module PactBroker
@@ -15,7 +15,7 @@ module PactBroker
15
15
  plugin :insert_ignore, identifying_columns: [:pacticipant_id, :version_id, :environment_id, :target_for_index]
16
16
 
17
17
  dataset_module do
18
- include PactBroker::Repositories::Helpers
18
+ include PactBroker::Dataset
19
19
 
20
20
  def user_created
21
21
  where(auto_created: false)
@@ -50,7 +50,7 @@ module PactBroker
50
50
  end
51
51
 
52
52
  def for_pacticipant_name(pacticipant_name)
53
- where(pacticipant_id: db[:pacticipants].select(:id).where(name_like(:name, pacticipant_name)))
53
+ where(pacticipant_id: db[:pacticipants].select(:id).where(Sequel.name_like(:name, pacticipant_name)))
54
54
  end
55
55
 
56
56
  def for_version_and_environment(version, environment)
@@ -1,7 +1,5 @@
1
- require "sequel"
2
1
  require "sequel/plugins/serialization"
3
2
 
4
-
5
3
  module PactBroker
6
4
  module Deployments
7
5
  class Environment < Sequel::Model
@@ -1,5 +1,4 @@
1
- require "sequel"
2
- require "pact_broker/repositories/helpers"
1
+ require "pact_broker/dataset"
3
2
 
4
3
  module PactBroker
5
4
  module Deployments
@@ -14,7 +13,7 @@ module PactBroker
14
13
  plugin :insert_ignore, identifying_columns: [:version_id, :environment_id]
15
14
 
16
15
  dataset_module do
17
- include PactBroker::Repositories::Helpers
16
+ include PactBroker::Dataset
18
17
 
19
18
  def currently_supported
20
19
  where(support_ended_at: nil)
@@ -25,11 +24,11 @@ module PactBroker
25
24
  end
26
25
 
27
26
  def for_pacticipant_name(pacticipant_name)
28
- where(pacticipant_id: db[:pacticipants].select(:id).where(name_like(:name, pacticipant_name)))
27
+ where(pacticipant_id: db[:pacticipants].select(:id).where(Sequel.name_like(:name, pacticipant_name)))
29
28
  end
30
29
 
31
30
  def for_pacticipant_version_number(pacticipant_version_number)
32
- where(version_id: db[:versions].select(:id).where(name_like(:number, pacticipant_version_number)))
31
+ where(version_id: db[:versions].select(:id).where(Sequel.name_like(:number, pacticipant_version_number)))
33
32
  end
34
33
 
35
34
  def for_version_and_environment(version, environment)
@@ -0,0 +1,25 @@
1
+ # Pacticipant branch
2
+
3
+ Allowed methods: `GET`, `DELETE`
4
+
5
+ Path: `/pacticipants/{pacticipant}/branches/{branch}`
6
+
7
+ Get or delete a pacticipant branch.
8
+
9
+ ## Create
10
+
11
+ Branches cannot be created via the resource URL. They are created automatically when publishing contracts.
12
+
13
+ ## Get
14
+
15
+ ### Example
16
+
17
+ curl http://broker/pacticipants/Bar/branches/main -H "Accept: application/hal+json"
18
+
19
+ ## Delete
20
+
21
+ Deletes a pacticipant branch. Does NOT delete the associated pacticipant versions.
22
+
23
+ Send a `DELETE` request to the branch resource.
24
+
25
+ curl -XDELETE http://broker/pacticipants/Bar/branches/main
@@ -1,4 +1,4 @@
1
- require "pact_broker/db"
1
+ require "pact_broker/dataset"
2
2
 
3
3
  module PactBroker
4
4
  module Domain
@@ -6,15 +6,18 @@ module PactBroker
6
6
  set_primary_key([:name, :pacticipant_id])
7
7
  unrestrict_primary_key
8
8
 
9
+ plugin :timestamps, update_on_create: true
10
+
9
11
  associate(:many_to_one, :pacticipant, :class => "PactBroker::Domain::Pacticipant", :key => :pacticipant_id, :primary_key => :id)
10
12
 
11
13
  def <=> other
12
14
  name <=> other.name
13
15
  end
14
16
 
17
+ dataset_module do
18
+ include PactBroker::Dataset
19
+ end
15
20
  end
16
-
17
- Label.plugin :timestamps, update_on_create: true
18
21
  end
19
22
  end
20
23
 
@@ -1,4 +1,3 @@
1
- require "pact_broker/db"
2
1
  require "pact_broker/json"
3
2
  require "pact_broker/pacts/content"
4
3
 
@@ -23,10 +22,16 @@ module PactBroker
23
22
  :revision_number,
24
23
  :pact_version_sha,
25
24
  :head_tag_names
26
- attr_writer :consumer, :latest_verification
25
+
26
+ attr_writer :consumer,
27
+ :latest_verification,
28
+ :consumer_version_tag_names,
29
+ :consumer_version_branch_names
27
30
 
28
31
  def initialize attributes = {}
29
32
  @latest_verification = UnsetAttribute.new
33
+ @consumer_version_tag_names = UnsetAttribute.new
34
+ @consumer_version_branch_names = UnsetAttribute.new
30
35
  attributes.each_pair do | key, value |
31
36
  self.send(key.to_s + "=", value)
32
37
  end
@@ -45,11 +50,11 @@ module PactBroker
45
50
  end
46
51
 
47
52
  def consumer_version_tag_names
48
- consumer_version.tags.collect(&:name)
53
+ get_attribute_if_set :consumer_version_tag_names
49
54
  end
50
55
 
51
- def latest_consumer_version_tag_names= latest_consumer_version_tag_names
52
- @latest_consumer_version_tag_names = latest_consumer_version_tag_names
56
+ def consumer_version_branch_names
57
+ get_attribute_if_set :consumer_version_branch_names
53
58
  end
54
59
 
55
60
  def latest_verification