pact_broker 2.107.1 → 2.108.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/Gemfile +5 -4
  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/released_versions_decorator.rb +2 -2
  42. data/lib/pact_broker/api/decorators/runtime_error_problem_json_decorator.rb +2 -2
  43. data/lib/pact_broker/api/decorators/validation_errors_decorator.rb +30 -0
  44. data/lib/pact_broker/api/decorators/validation_errors_problem_json_decorator.rb +4 -6
  45. data/lib/pact_broker/api/decorators/version_decorator.rb +5 -3
  46. data/lib/pact_broker/api/decorators/versions_decorator.rb +24 -14
  47. data/lib/pact_broker/api/decorators/webhook_execution_result_decorator.rb +2 -0
  48. data/lib/pact_broker/api/middleware/configuration.rb +2 -0
  49. data/lib/pact_broker/api/pact_broker_urls.rb +18 -2
  50. data/lib/pact_broker/api/resources/after_reply.rb +15 -0
  51. data/lib/pact_broker/api/resources/all_webhooks.rb +3 -3
  52. data/lib/pact_broker/api/resources/badge_methods.rb +2 -1
  53. data/lib/pact_broker/api/resources/base_resource.rb +6 -4
  54. data/lib/pact_broker/api/resources/branch.rb +40 -0
  55. data/lib/pact_broker/api/resources/branch_versions.rb +59 -0
  56. data/lib/pact_broker/api/resources/can_i_deploy_pacticipant_version_by_branch_to_environment_badge.rb +1 -1
  57. data/lib/pact_broker/api/resources/currently_deployed_versions_for_environment.rb +1 -1
  58. data/lib/pact_broker/api/resources/currently_supported_versions_for_environment.rb +1 -1
  59. data/lib/pact_broker/api/resources/dashboard.rb +10 -0
  60. data/lib/pact_broker/api/resources/deployed_versions_for_version_and_environment.rb +1 -1
  61. data/lib/pact_broker/api/resources/environments.rb +1 -1
  62. data/lib/pact_broker/api/resources/error_handler.rb +18 -52
  63. data/lib/pact_broker/api/resources/error_handling_methods.rb +40 -14
  64. data/lib/pact_broker/api/resources/error_response_generator.rb +23 -6
  65. data/lib/pact_broker/api/resources/event_methods.rb +15 -0
  66. data/lib/pact_broker/api/resources/filter_methods.rb +15 -0
  67. data/lib/pact_broker/api/resources/group.rb +11 -2
  68. data/lib/pact_broker/api/resources/index.rb +6 -0
  69. data/lib/pact_broker/api/resources/integrations.rb +18 -4
  70. data/lib/pact_broker/api/resources/latest_version.rb +2 -0
  71. data/lib/pact_broker/api/resources/pact.rb +11 -6
  72. data/lib/pact_broker/api/resources/pacticipant_branches.rb +67 -0
  73. data/lib/pact_broker/api/resources/pacticipants.rb +26 -7
  74. data/lib/pact_broker/api/resources/pacticipants_for_label.rb +2 -2
  75. data/lib/pact_broker/api/resources/pagination_methods.rb +11 -1
  76. data/lib/pact_broker/api/resources/verifications.rb +9 -4
  77. data/lib/pact_broker/api/resources/versions.rb +12 -0
  78. data/lib/pact_broker/api.rb +5 -0
  79. data/lib/pact_broker/app.rb +10 -4
  80. data/lib/pact_broker/application_context.rb +29 -25
  81. data/lib/pact_broker/async/after_reply.rb +30 -0
  82. data/lib/pact_broker/config/runtime_configuration.rb +9 -21
  83. data/lib/pact_broker/config/runtime_configuration_coercion_methods.rb +32 -2
  84. data/lib/pact_broker/config/runtime_configuration_database_methods.rb +1 -1
  85. data/lib/pact_broker/config/runtime_configuration_logging_methods.rb +15 -5
  86. data/lib/pact_broker/configuration.rb +29 -12
  87. data/lib/pact_broker/contracts/contracts_to_publish.rb +8 -0
  88. data/lib/pact_broker/contracts/service.rb +7 -1
  89. data/lib/pact_broker/dataset/page.rb +22 -0
  90. data/lib/pact_broker/dataset.rb +122 -0
  91. data/lib/pact_broker/db/data_migrations/set_contract_data_updated_at_for_integrations.rb +47 -0
  92. data/lib/pact_broker/db/migrate_data.rb +1 -0
  93. data/lib/pact_broker/db/models.rb +1 -1
  94. data/lib/pact_broker/deployments/currently_deployed_version_id.rb +2 -5
  95. data/lib/pact_broker/deployments/deployed_version.rb +3 -3
  96. data/lib/pact_broker/deployments/environment.rb +0 -2
  97. data/lib/pact_broker/deployments/released_version.rb +4 -5
  98. data/lib/pact_broker/doc/views/index/pacticipant-branch.markdown +25 -0
  99. data/lib/pact_broker/domain/label.rb +6 -3
  100. data/lib/pact_broker/domain/pact.rb +10 -5
  101. data/lib/pact_broker/domain/pacticipant.rb +4 -34
  102. data/lib/pact_broker/domain/tag.rb +4 -5
  103. data/lib/pact_broker/domain/verification.rb +2 -4
  104. data/lib/pact_broker/domain/version.rb +12 -19
  105. data/lib/pact_broker/errors/error_reporter.rb +30 -0
  106. data/lib/pact_broker/errors.rb +2 -15
  107. data/lib/pact_broker/events/subscriber.rb +12 -4
  108. data/lib/pact_broker/feature_toggle.rb +1 -1
  109. data/lib/pact_broker/groups/service.rb +38 -5
  110. data/lib/pact_broker/index/service.rb +1 -2
  111. data/lib/pact_broker/integrations/event_listener.rb +23 -0
  112. data/lib/pact_broker/integrations/integration.rb +24 -2
  113. data/lib/pact_broker/integrations/repository.rb +34 -1
  114. data/lib/pact_broker/integrations/service.rb +17 -18
  115. data/lib/pact_broker/labels/repository.rb +4 -8
  116. data/lib/pact_broker/locale/en.yml +5 -0
  117. data/lib/pact_broker/matrix/every_row.rb +58 -40
  118. data/lib/pact_broker/matrix/integration_row.rb +95 -0
  119. data/lib/pact_broker/matrix/integrations_repository.rb +133 -0
  120. data/lib/pact_broker/matrix/matrix_row.rb +88 -0
  121. data/lib/pact_broker/matrix/matrix_row_dataset_module.rb +185 -0
  122. data/lib/pact_broker/matrix/matrix_row_instance_methods.rb +150 -0
  123. data/lib/pact_broker/matrix/matrix_row_verification_dataset_module.rb +83 -0
  124. data/lib/pact_broker/matrix/parse_query.rb +1 -0
  125. data/lib/pact_broker/matrix/repository.rb +62 -285
  126. data/lib/pact_broker/matrix/resolved_selector.rb +13 -4
  127. data/lib/pact_broker/matrix/resolved_selector_builder.rb +84 -0
  128. data/lib/pact_broker/matrix/resolved_selectors_builder.rb +39 -0
  129. data/lib/pact_broker/matrix/row_ignorer.rb +36 -0
  130. data/lib/pact_broker/matrix/selector_ignorer.rb +59 -0
  131. data/lib/pact_broker/matrix/selector_resolver.rb +130 -0
  132. data/lib/pact_broker/metrics/service.rb +4 -9
  133. data/lib/pact_broker/pacticipants/latest_version_for_pacticipant_eager_loader.rb +33 -0
  134. data/lib/pact_broker/pacticipants/repository.rb +7 -9
  135. data/lib/pact_broker/pacticipants/service.rb +2 -2
  136. data/lib/pact_broker/pacts/latest_pact_publication_id_for_consumer_version.rb +2 -4
  137. data/lib/pact_broker/pacts/metadata.rb +3 -1
  138. data/lib/pact_broker/pacts/pact_publication.rb +23 -5
  139. data/lib/pact_broker/pacts/pact_publication_dataset_module.rb +5 -1
  140. data/lib/pact_broker/pacts/pact_version.rb +2 -3
  141. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +2 -5
  142. data/lib/pact_broker/pacts/placeholder_pact.rb +3 -1
  143. data/lib/pact_broker/pacts/repository.rb +12 -12
  144. data/lib/pact_broker/pacts/service.rb +1 -1
  145. data/lib/pact_broker/repositories.rb +9 -1
  146. data/lib/pact_broker/string_refinements.rb +4 -0
  147. data/lib/pact_broker/tags/head_pact_tags.rb +2 -5
  148. data/lib/pact_broker/tags/repository.rb +3 -7
  149. data/lib/pact_broker/test/test_data_builder.rb +50 -1
  150. data/lib/pact_broker/ui/controllers/groups.rb +2 -1
  151. data/lib/pact_broker/ui/view_models/matrix_line.rb +4 -4
  152. data/lib/pact_broker/ui/views/groups/show.html.erb +2 -1
  153. data/lib/pact_broker/verifications/latest_verification_for_consumer_and_provider.rb +0 -3
  154. data/lib/pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version.rb +2 -4
  155. data/lib/pact_broker/verifications/repository.rb +2 -5
  156. data/lib/pact_broker/verifications/sequence.rb +1 -4
  157. data/lib/pact_broker/verifications/service.rb +4 -0
  158. data/lib/pact_broker/version.rb +1 -1
  159. data/lib/pact_broker/versions/branch.rb +2 -5
  160. data/lib/pact_broker/versions/branch_head.rb +0 -3
  161. data/lib/pact_broker/versions/branch_repository.rb +76 -0
  162. data/lib/pact_broker/versions/branch_service.rb +13 -25
  163. data/lib/pact_broker/versions/branch_version.rb +0 -3
  164. data/lib/pact_broker/versions/branch_version_repository.rb +17 -0
  165. data/lib/pact_broker/versions/repository.rb +19 -2
  166. data/lib/pact_broker/versions/sequence.rb +1 -3
  167. data/lib/pact_broker/versions/service.rb +4 -0
  168. data/lib/pact_broker/webhooks/execution.rb +3 -7
  169. data/lib/pact_broker/webhooks/job.rb +16 -7
  170. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +2 -2
  171. data/lib/pact_broker/webhooks/repository.rb +0 -1
  172. data/lib/pact_broker/webhooks/trigger_service.rb +11 -12
  173. data/lib/pact_broker/webhooks/triggered_webhook.rb +3 -4
  174. data/lib/pact_broker/webhooks/webhook.rb +2 -2
  175. data/lib/pact_broker/webhooks/webhook_event.rb +2 -5
  176. data/lib/rack/pact_broker/add_cache_header.rb +14 -0
  177. data/lib/rack/pact_broker/application_context.rb +16 -0
  178. data/lib/rack/pact_broker/configurable_make_it_later.rb +1 -1
  179. data/lib/rack/pact_broker/invalid_uri_protection.rb +19 -3
  180. data/lib/webmachine/describe_routes.rb +55 -39
  181. data/lib/webmachine/render_error_monkey_patch.rb +13 -4
  182. data/pact_broker.gemspec +4 -4
  183. metadata +52 -29
  184. data/lib/pact_broker/api/decorators/decorator_context.rb +0 -22
  185. data/lib/pact_broker/matrix/query_builder.rb +0 -90
  186. data/lib/pact_broker/matrix/query_ids.rb +0 -40
  187. data/lib/pact_broker/matrix/quick_row.rb +0 -458
  188. data/lib/pact_broker/relationships/groupify.rb +0 -45
  189. data/lib/pact_broker/repositories/helpers.rb +0 -96
  190. data/lib/pact_broker/repositories/page.rb +0 -24
  191. data/lib/pact_broker/tags/tag_with_latest_flag.rb +0 -28
@@ -1,11 +1,17 @@
1
1
  require "pact_broker/api/resources/base_resource"
2
2
  require "pact_broker/api/renderers/integrations_dot_renderer"
3
3
  require "pact_broker/api/decorators/integrations_decorator"
4
+ require "pact_broker/api/resources/filter_methods"
5
+ require "pact_broker/api/resources/pagination_methods"
6
+ require "pact_broker/api/contracts/pagination_query_params_schema"
4
7
 
5
8
  module PactBroker
6
9
  module Api
7
10
  module Resources
8
11
  class Integrations < BaseResource
12
+ include PaginationMethods
13
+ include FilterMethods
14
+
9
15
  def content_types_provided
10
16
  [
11
17
  ["text/vnd.graphviz", :to_dot],
@@ -17,18 +23,20 @@ module PactBroker
17
23
  ["GET", "OPTIONS", "DELETE"]
18
24
  end
19
25
 
26
+ def malformed_request?
27
+ super || (request.get? && validation_errors_for_schema?(schema, request.query))
28
+ end
29
+
20
30
  def to_dot
31
+ integrations = integration_service.find_all(filter_options, pagination_options)
21
32
  PactBroker::Api::Renderers::IntegrationsDotRenderer.call(integrations)
22
33
  end
23
34
 
24
35
  def to_json
36
+ integrations = integration_service.find_all(filter_options, pagination_options, decorator_class(:integrations_decorator).eager_load_associations)
25
37
  decorator_class(:integrations_decorator).new(integrations).to_json(**decorator_options)
26
38
  end
27
39
 
28
- def integrations
29
- @integrations ||= integration_service.find_all
30
- end
31
-
32
40
  def delete_resource
33
41
  integration_service.delete_all
34
42
  true
@@ -37,6 +45,12 @@ module PactBroker
37
45
  def policy_name
38
46
  :'integrations::integrations'
39
47
  end
48
+
49
+ def schema
50
+ if request.get?
51
+ PactBroker::Api::Contracts::PaginationQueryParamsSchema
52
+ end
53
+ end
40
54
  end
41
55
  end
42
56
  end
@@ -17,6 +17,8 @@ module PactBroker
17
17
  def version
18
18
  if identifier_from_path[:tag]
19
19
  @version ||= version_service.find_by_pacticipant_name_and_latest_tag(identifier_from_path[:pacticipant_name], identifier_from_path[:tag])
20
+ elsif identifier_from_path[:branch_name]
21
+ @version ||= version_service.find_latest_by_pacticipant_name_and_branch_name(identifier_from_path[:pacticipant_name], identifier_from_path[:branch_name])
20
22
  else
21
23
  @version ||= version_service.find_latest_by_pacticpant_name(identifier_from_path)
22
24
  end
@@ -9,14 +9,17 @@ require "pact_broker/api/contracts/put_pact_params_contract"
9
9
  require "pact_broker/webhooks/execution_configuration"
10
10
  require "pact_broker/api/resources/webhook_execution_methods"
11
11
  require "pact_broker/api/resources/pact_resource_methods"
12
+ require "pact_broker/api/resources/event_methods"
13
+ require "pact_broker/integrations/event_listener"
12
14
 
13
15
  module PactBroker
14
16
  module Api
15
17
  module Resources
16
18
  class Pact < BaseResource
19
+ include EventMethods
17
20
  include PacticipantResourceMethods
18
- include WebhookExecutionMethods
19
21
  include PactResourceMethods
22
+ include WebhookExecutionMethods
20
23
  include PactBroker::Messages
21
24
 
22
25
  def content_types_provided
@@ -65,11 +68,13 @@ module PactBroker
65
68
  def from_json
66
69
  response_code = pact ? 200 : 201
67
70
 
68
- handle_webhook_events do
69
- if request.patch? && resource_exists?
70
- @pact = pact_service.merge_pact(pact_params)
71
- else
72
- @pact = pact_service.create_or_update_pact(pact_params)
71
+ subscribe(PactBroker::Integrations::EventListener.new) do
72
+ handle_webhook_events do
73
+ if request.patch? && resource_exists?
74
+ @pact = pact_service.merge_pact(pact_params)
75
+ else
76
+ @pact = pact_service.create_or_update_pact(pact_params)
77
+ end
73
78
  end
74
79
  end
75
80
  response.body = to_json
@@ -0,0 +1,67 @@
1
+ require "pact_broker/api/resources/base_resource"
2
+ require "pact_broker/api/resources/pagination_methods"
3
+ require "pact_broker/api/resources/filter_methods"
4
+ require "pact_broker/api/resources/after_reply"
5
+ require "rack/utils"
6
+
7
+ module PactBroker
8
+ module Api
9
+ module Resources
10
+ class PacticipantBranches < BaseResource
11
+ include PaginationMethods
12
+ include FilterMethods
13
+ include AfterReply
14
+
15
+ def content_types_provided
16
+ [["application/hal+json", :to_json]]
17
+ end
18
+
19
+ def allowed_methods
20
+ ["GET", "DELETE", "OPTIONS"]
21
+ end
22
+
23
+ def resource_exists?
24
+ !!pacticipant
25
+ end
26
+
27
+ def to_json
28
+ decorator_class(:pacticipant_branches_decorator).new(branches).to_json(**decorator_options(pacticipant: pacticipant))
29
+ end
30
+
31
+ def policy_name
32
+ :'versions::branches'
33
+ end
34
+
35
+ # Allows bulk deletion of pacticipant branches, keeping the specified branches and the main branch.
36
+ # Deletes the branches asyncronously, after the response has been sent, for performance reasons.
37
+ def delete_resource
38
+ after_reply do
39
+ branch_service.delete_branches_for_pacticipant(pacticipant, exclude: exclude)
40
+ end
41
+ notices = branch_service.branch_deletion_notices(pacticipant, exclude: exclude)
42
+ response.body = decorator_class(:notices_decorator).new(notices).to_json(**decorator_options)
43
+ 202
44
+ end
45
+
46
+ private
47
+
48
+ def branches
49
+ @branches ||= branch_service.find_all_branches_for_pacticipant(
50
+ pacticipant,
51
+ filter_options,
52
+ default_pagination_options.merge(pagination_options),
53
+ eager_load_associations
54
+ )
55
+ end
56
+
57
+ def exclude
58
+ Rack::Utils.parse_nested_query(request.uri.query)["exclude"] || []
59
+ end
60
+
61
+ def eager_load_associations
62
+ decorator_class(:pacticipant_branches_decorator).eager_load_associations
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -12,7 +12,6 @@ module PactBroker
12
12
  using PactBroker::HashRefinements
13
13
  include PaginationMethods
14
14
 
15
-
16
15
  def content_types_provided
17
16
  [["application/hal+json", :to_json]]
18
17
  end
@@ -26,7 +25,15 @@ module PactBroker
26
25
  end
27
26
 
28
27
  def malformed_request?
29
- super || (request.post? && validation_errors_for_schema?)
28
+ if super
29
+ true
30
+ elsif request.post? && validation_errors_for_schema?
31
+ true
32
+ elsif request.get? && validation_errors_for_schema?(schema, request.query)
33
+ true
34
+ else
35
+ false
36
+ end
30
37
  end
31
38
 
32
39
  def request_body_required?
@@ -47,11 +54,11 @@ module PactBroker
47
54
  end
48
55
 
49
56
  def to_json
50
- generate_json(pacticipant_service.find_all_pacticipants(pagination_options))
57
+ generate_json(pacticipant_service.find_all_pacticipants(filter_options, pagination_options, eager_load_associations))
51
58
  end
52
59
 
53
60
  def generate_json pacticipants
54
- decorator_class(:deprecated_pacticipant_collection_decorator).new(pacticipants).to_json(**decorator_options)
61
+ decorator_class(:deprecated_pacticipants_decorator).new(pacticipants).to_json(**decorator_options)
55
62
  end
56
63
 
57
64
  def decorator_for model
@@ -69,11 +76,23 @@ module PactBroker
69
76
  private
70
77
 
71
78
  def schema
72
- PactBroker::Api::Contracts::PacticipantCreateSchema
79
+ if request.get?
80
+ PactBroker::Api::Contracts::PaginationQueryParamsSchema
81
+ else
82
+ PactBroker::Api::Contracts::PacticipantCreateSchema
83
+ end
84
+ end
85
+
86
+ def eager_load_associations
87
+ decorator_class(:deprecated_pacticipants_decorator).eager_load_associations
73
88
  end
74
89
 
75
- def pacticipants
76
- @pacticipants ||= pacticipant_service.find_all_pacticipants
90
+ def filter_options
91
+ if (request.query.has_key?("q"))
92
+ { query_string: request.query["q"] }
93
+ else
94
+ {}
95
+ end
77
96
  end
78
97
  end
79
98
  end
@@ -1,5 +1,5 @@
1
1
  require "pact_broker/api/resources/base_resource"
2
- require "pact_broker/api/decorators/pacticipant_collection_decorator"
2
+ require "pact_broker/api/decorators/pacticipants_decorator"
3
3
 
4
4
  module PactBroker
5
5
  module Api
@@ -19,7 +19,7 @@ module PactBroker
19
19
  end
20
20
 
21
21
  def generate_json pacticipants
22
- decorator_class(:pacticipant_collection_decorator).new(pacticipants).to_json(**decorator_options)
22
+ decorator_class(:pacticipants_decorator).new(pacticipants).to_json(**decorator_options)
23
23
  end
24
24
 
25
25
  def policy_name
@@ -2,8 +2,14 @@ module PactBroker
2
2
  module Api
3
3
  module Resources
4
4
  module PaginationMethods
5
+ # rubocop: disable Metrics/CyclomaticComplexity
5
6
  def pagination_options
6
- if request.query["pageNumber"] || request.query["pageSize"]
7
+ if request.query["page"] || request.query["size"]
8
+ {
9
+ page_number: request.query["page"]&.to_i || 1,
10
+ page_size: request.query["size"]&.to_i || 100
11
+ }
12
+ elsif request.query["pageNumber"] || request.query["pageSize"]
7
13
  {
8
14
  page_number: request.query["pageNumber"]&.to_i || 1,
9
15
  page_size: request.query["pageSize"]&.to_i || 100
@@ -12,6 +18,10 @@ module PactBroker
12
18
  {}
13
19
  end
14
20
  end
21
+
22
+ def default_pagination_options
23
+ { page_number: 1, page_size: 100 }
24
+ end
15
25
  end
16
26
  end
17
27
  end
@@ -5,6 +5,8 @@ require "pact_broker/api/contracts/verification_contract"
5
5
  require "pact_broker/api/decorators/verification_decorator"
6
6
  require "pact_broker/api/resources/webhook_execution_methods"
7
7
  require "pact_broker/api/resources/metadata_resource_methods"
8
+ require "pact_broker/api/resources/event_methods"
9
+ require "pact_broker/integrations/event_listener"
8
10
 
9
11
  module PactBroker
10
12
  module Api
@@ -12,6 +14,7 @@ module PactBroker
12
14
  class Verifications < BaseResource
13
15
  include WebhookExecutionMethods
14
16
  include MetadataResourceMethods
17
+ include EventMethods
15
18
 
16
19
  def content_types_accepted
17
20
  [["application/json", :from_json]]
@@ -42,10 +45,12 @@ module PactBroker
42
45
  end
43
46
 
44
47
  def from_json
45
- handle_webhook_events(build_url: verification_params["buildUrl"]) do
46
- verified_pacts = pact_service.find_for_verification_publication(pact_params, event_context[:consumer_version_selectors])
47
- verification = verification_service.create(next_verification_number, verification_params, verified_pacts, event_context)
48
- response.body = decorator_for(verification).to_json(**decorator_options)
48
+ subscribe(PactBroker::Integrations::EventListener.new) do
49
+ handle_webhook_events(build_url: verification_params["buildUrl"]) do
50
+ verified_pacts = pact_service.find_for_verification_publication(pact_params, event_context[:consumer_version_selectors])
51
+ verification = verification_service.create(next_verification_number, verification_params, verified_pacts, event_context)
52
+ response.body = decorator_for(verification).to_json(**decorator_options)
53
+ end
49
54
  end
50
55
  true
51
56
  end
@@ -18,6 +18,10 @@ module PactBroker
18
18
  ["GET", "OPTIONS"]
19
19
  end
20
20
 
21
+ def malformed_request?
22
+ super || request.get? && validation_errors_for_schema?(schema, request.query)
23
+ end
24
+
21
25
  def resource_exists?
22
26
  !!pacticipant
23
27
  end
@@ -33,6 +37,14 @@ module PactBroker
33
37
  def policy_name
34
38
  :'versions::versions'
35
39
  end
40
+
41
+ private
42
+
43
+ def schema
44
+ if request.get?
45
+ PactBroker::Api::Contracts::PaginationQueryParamsSchema
46
+ end
47
+ end
36
48
  end
37
49
  end
38
50
  end
@@ -64,6 +64,7 @@ module PactBroker
64
64
  # Latest pacts
65
65
  add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest"], Api::Resources::LatestPact, {resource_name: "latest_pact_publication"}
66
66
  add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest", :tag], Api::Resources::LatestPact, {resource_name: "latest_tagged_pact_publication"}
67
+ add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "branch", :branch_name, "latest"], Api::Resources::LatestPact, {resource_name: "latest_pact_publication_for_branch"}
67
68
  add ["pacts", "provider", :provider_name], Api::Resources::ProviderPacts, {resource_name: "provider_pact_publications"}
68
69
  add ["pacts", "provider", :provider_name, "tag", :tag], Api::Resources::ProviderPacts, {resource_name: "tagged_provider_pact_publications"}
69
70
  add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest-untagged"], Api::Resources::LatestPact, {resource_name: "latest_untagged_pact_publication", tag: :untagged}
@@ -86,12 +87,16 @@ module PactBroker
86
87
 
87
88
  # Versions
88
89
  add ["pacticipants", :pacticipant_name, "versions"], Api::Resources::Versions, {resource_name: "pacticipant_versions"}
90
+ add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions"], Api::Resources::BranchVersions, {resource_name: "pacticipant_branch_versions"}
89
91
  add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number], Api::Resources::Version, {resource_name: "pacticipant_version"}
90
92
  add ["pacticipants", :pacticipant_name, "latest-version", :tag], Api::Resources::LatestVersion, {resource_name: "latest_tagged_pacticipant_version"}
91
93
  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" }
92
94
  add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to, "badge"], Api::Resources::CanIDeployPacticipantVersionByTagToTagBadge, { resource_name: "can_i_deploy_latest_tagged_version_to_tag_badge" }
93
95
  add ["pacticipants", :pacticipant_name, "latest-version"], Api::Resources::LatestVersion, {resource_name: "latest_pacticipant_version"}
94
96
  add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number, "tags", :tag_name], Api::Resources::Tag, {resource_name: "pacticipant_version_tag"}
97
+ add ["pacticipants", :pacticipant_name, "branches"], Api::Resources::PacticipantBranches, {resource_name: "pacticipant_branches"}
98
+ add ["pacticipants", :pacticipant_name, "branches", :branch_name], Api::Resources::Branch, { resource_name: "branch" }
99
+ add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version"], Api::Resources::LatestVersion, { resource_name: "latest_pacticipant_version_for_branch" }
95
100
  add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions", :version_number], Api::Resources::BranchVersion, { resource_name: "branch_version" }
96
101
  add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version", "can-i-deploy", "to-environment", :environment_name], Api::Resources::CanIDeployPacticipantVersionByBranchToEnvironment, { resource_name: "can_i_deploy_latest_branch_version_to_environment" }
97
102
  add ["pacticipants", :pacticipant_name, "branches", :branch_name, "latest-version", "can-i-deploy", "to-environment", :environment_name, "badge"], Api::Resources::CanIDeployPacticipantVersionByBranchToEnvironmentBadge, { resource_name: "can_i_deploy_latest_branch_version_to_environment_badge" }
@@ -21,14 +21,17 @@ require "rack/pact_broker/ui_authentication"
21
21
  require "rack/pact_broker/configurable_make_it_later"
22
22
  require "rack/pact_broker/no_auth"
23
23
  require "rack/pact_broker/reset_thread_data"
24
+ require "rack/pact_broker/add_cache_header"
24
25
  require "rack/pact_broker/add_vary_header"
25
26
  require "rack/pact_broker/use_when"
27
+ require "rack/pact_broker/application_context"
26
28
  require "sucker_punch"
27
29
  require "pact_broker/api/middleware/configuration"
28
30
  require "pact_broker/api/middleware/basic_auth"
29
31
  require "pact_broker/config/basic_auth_configuration"
30
32
  require "pact_broker/api/authorization/resource_access_policy"
31
33
  require "pact_broker/api/middleware/http_debug_logs"
34
+ require "pact_broker/application_context"
32
35
 
33
36
  module PactBroker
34
37
 
@@ -36,15 +39,16 @@ module PactBroker
36
39
  include PactBroker::Logging
37
40
  using Rack::PactBroker::UseWhen
38
41
 
39
- attr_accessor :configuration
42
+ attr_accessor :configuration, :application_context
40
43
 
41
- def initialize
44
+ def initialize(application_context = PactBroker::ApplicationContext.default_application_context)
45
+ @application_context = application_context
42
46
  @app_builder = ::Rack::Builder.new
43
47
  @cascade_apps = []
44
48
  @make_it_later_api_auth = ::Rack::PactBroker::ConfigurableMakeItLater.new(Rack::PactBroker::NoAuth)
45
49
  @make_it_later_ui_auth = ::Rack::PactBroker::ConfigurableMakeItLater.new(Rack::PactBroker::NoAuth)
46
50
  # Can only be required after database connection has been made because the decorators rely on the Sequel models
47
- @create_pact_broker_api_block = ->() { require "pact_broker/api"; PactBroker::API }
51
+ @create_pact_broker_api_block = ->() { require "pact_broker/api"; PactBroker.build_api(application_context) }
48
52
  @configuration = PactBroker.configuration
49
53
  yield configuration if block_given?
50
54
  post_configure
@@ -55,7 +59,7 @@ module PactBroker
55
59
  @configuration.freeze
56
60
  end
57
61
 
58
- # Allows middleware to be inserted at the bottom of the shared middlware stack
62
+ # Allows middleware to be inserted at the bottom of the shared middleware stack
59
63
  # (ie just before the cascade is called for diagnostic, UI and API).
60
64
  # To insert middleware at the top of the stack, initialize
61
65
  # the middleware with the app, and run it manually.
@@ -177,12 +181,14 @@ module PactBroker
177
181
  @app_builder.use PactBroker::Api::Middleware::HttpDebugLogs if configuration.http_debug_logging_enabled
178
182
  configure_basic_auth
179
183
  configure_rack_protection
184
+ @app_builder.use Rack::PactBroker::ApplicationContext, application_context
180
185
  @app_builder.use Rack::PactBroker::InvalidUriProtection
181
186
  @app_builder.use Rack::PactBroker::ResetThreadData
182
187
  @app_builder.use Rack::PactBroker::AddPactBrokerVersionHeader
183
188
  @app_builder.use Rack::PactBroker::AddVaryHeader
184
189
  @app_builder.use Rack::Static, :urls => ["/stylesheets", "/css", "/fonts", "/js", "/javascripts", "/images"], :root => PactBroker.project_root.join("public")
185
190
  @app_builder.use Rack::Static, :urls => ["/favicon.ico"], :root => PactBroker.project_root.join("public/images"), header_rules: [[:all, {"Content-Type" => "image/x-icon"}]]
191
+ @app_builder.use Rack::PactBroker::AddCacheHeader
186
192
  @app_builder.use Rack::PactBroker::ConvertFileExtensionToAcceptHeader
187
193
  # Rack::PactBroker::SetBaseUrl needs to be before the Rack::PactBroker::HalBrowserRedirect
188
194
  @app_builder.use Rack::PactBroker::SetBaseUrl, configuration.base_urls
@@ -1,10 +1,3 @@
1
- require "pact_broker/api/decorators/configuration"
2
- require "pact_broker/api/contracts/configuration"
3
- require "pact_broker/api/decorators/decorator_context_creator"
4
- require "pact_broker/webhooks/execution_configuration_creator"
5
- require "pact_broker/errors/error_logger"
6
- require "pact_broker/api/resources/error_response_generator"
7
-
8
1
  module PactBroker
9
2
  class ApplicationContext
10
3
  attr_reader :decorator_configuration,
@@ -15,32 +8,43 @@ module PactBroker
15
8
  :before_resource,
16
9
  :after_resource,
17
10
  :error_logger,
11
+ :error_reporter,
18
12
  :error_response_generator
19
13
 
20
- def initialize(params = {})
21
- params_with_defaults = {
14
+ def initialize(params)
15
+ @decorator_configuration = params[:decorator_configuration]
16
+ @api_contract_configuration = params[:api_contract_configuration]
17
+ @decorator_context_creator = params[:decorator_context_creator]
18
+ @webhook_execution_configuration_creator = params[:webhook_execution_configuration_creator]
19
+ @resource_authorizer = params[:resource_authorizer]
20
+ @before_resource = params[:before_resource]
21
+ @after_resource = params[:after_resource]
22
+ @error_logger = params[:error_logger]
23
+ @error_reporter = params[:error_reporter]
24
+ @error_response_generator = params[:error_response_generator]
25
+ end
26
+
27
+ # TODO pass in configuration
28
+ def self.default_application_context(overrides = {})
29
+ require "pact_broker/api/decorators/configuration"
30
+ require "pact_broker/api/contracts/configuration"
31
+ require "pact_broker/api/decorators/decorator_context_creator"
32
+ require "pact_broker/webhooks/execution_configuration_creator"
33
+ require "pact_broker/errors/error_logger"
34
+ require "pact_broker/errors/error_reporter"
35
+ require "pact_broker/api/resources/error_response_generator"
36
+
37
+ params = {
22
38
  decorator_configuration: PactBroker::Api::Decorators::Configuration.default_configuration,
23
39
  api_contract_configuration: PactBroker::Api::Contracts::Configuration.default_configuration,
24
40
  decorator_context_creator: PactBroker::Api::Decorators::DecoratorContextCreator,
25
41
  webhook_execution_configuration_creator: PactBroker::Webhooks::ExecutionConfigurationCreator,
26
42
  error_logger: PactBroker::Errors::ErrorLogger,
27
- error_response_generator: PactBroker::Api::Resources::ErrorResponseGenerator
28
- }.merge(params)
43
+ error_reporter: PactBroker::Errors::ErrorReporter.new(PactBroker.configuration.api_error_reporters),
44
+ error_response_generator: PactBroker::Api::Resources::ErrorResponseGenerator,
45
+ }.merge(overrides)
29
46
 
30
- @decorator_configuration = params_with_defaults[:decorator_configuration]
31
- @api_contract_configuration = params_with_defaults[:api_contract_configuration]
32
- @decorator_context_creator = params_with_defaults[:decorator_context_creator]
33
- @webhook_execution_configuration_creator = params_with_defaults[:webhook_execution_configuration_creator]
34
- @resource_authorizer = params_with_defaults[:resource_authorizer]
35
- @before_resource = params_with_defaults[:before_resource]
36
- @after_resource = params_with_defaults[:after_resource]
37
- @error_logger = params_with_defaults[:error_logger]
38
- @error_response_generator = params_with_defaults[:error_response_generator]
39
-
40
- end
41
-
42
- def self.default_application_context(overrides = {})
43
- ApplicationContext.new(overrides)
47
+ ApplicationContext.new(params)
44
48
  end
45
49
  end
46
50
  end
@@ -0,0 +1,30 @@
1
+ # Saves a block for execution after the HTTP response has been sent to the user.
2
+ # When the block is executed, it connects to the database before executing the code.
3
+ # This is good for doing things that might take a while and don't have to be done before
4
+ # the response is sent, and don't need retries (in which case, it might be better to use a SuckerPunch Job).
5
+ #
6
+ # This leverages a feature of Puma which I'm not sure is meant to be public or not.
7
+ # There are serveral mentions of it on the internet, so I assume it's ok to use it.
8
+ # Puma itself uses the rack.after_reply for http request logging.
9
+ #
10
+ # https://github.com/search?q=repo%3Apuma%2Fpuma%20rack.after_reply&type=code
11
+
12
+ module PactBroker
13
+ module Async
14
+ class AfterReply
15
+ def initialize(rack_env)
16
+ @rack_env = rack_env
17
+ @database_connector = rack_env.fetch("pactbroker.database_connector")
18
+ end
19
+
20
+ def execute(&block)
21
+ dc = @database_connector
22
+ @rack_env["rack.after_reply"] << lambda {
23
+ dc.call do
24
+ block.call
25
+ end
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -31,6 +31,8 @@ module PactBroker
31
31
  include RuntimeConfigurationDatabaseMethods
32
32
  include RuntimeConfigurationBasicAuthMethods
33
33
 
34
+ config_name :pact_broker
35
+
34
36
  # logging attributes
35
37
  attr_config(
36
38
  log_dir: File.expand_path("./log"),
@@ -91,7 +93,8 @@ module PactBroker
91
93
  allow_dangerous_contract_modification: false,
92
94
  semver_formats: ["%M.%m.%p%s%d", "%M.%m", "%M"],
93
95
  seed_example_data: true,
94
- features: []
96
+ network_diagram_max_pacticipants: 150,
97
+ features: {}
95
98
  )
96
99
 
97
100
  def self.getter_and_setter_method_names
@@ -105,8 +108,11 @@ module PactBroker
105
108
  config_attributes + config_attributes.collect{ |k| "#{k}=".to_sym } + extra_methods - [:base_url]
106
109
  end
107
110
 
108
- config_name :pact_broker
109
-
111
+ coerce_types(
112
+ features: COERCE_FEATURES,
113
+ network_diagram_max_pacticipants: :integer,
114
+ webhook_certificates: COERCE_WEBHOOKS
115
+ )
110
116
  sensitive_values(:database_url, :database_password)
111
117
 
112
118
  def log_level= log_level
@@ -179,10 +185,6 @@ module PactBroker
179
185
  super(value_to_string_array(main_branch_candidates, "main_branch_candidates"))
180
186
  end
181
187
 
182
- def features= features
183
- super(value_to_string_array(features, "features").collect(&:downcase))
184
- end
185
-
186
188
  def rack_protection_use= rack_protection_use
187
189
  super(value_to_string_array(rack_protection_use, "rack_protection_use")&.collect(&:to_sym))
188
190
  end
@@ -191,20 +193,6 @@ module PactBroker
191
193
  super(value_to_string_array(rack_protection_except, "rack_protection_except")&.collect(&:to_sym))
192
194
  end
193
195
 
194
- def webhook_certificates= webhook_certificates
195
- if webhook_certificates.is_a?(Array)
196
- super(webhook_certificates.collect(&:symbolize_keys))
197
- elsif webhook_certificates.is_a?(Hash)
198
- if all_keys_are_number_strings?(webhook_certificates)
199
- super(convert_hash_with_number_string_keys_to_array(webhook_certificates).collect(&:symbolize_keys))
200
- else
201
- raise_validation_error("webhook_certificates must be an array, or a hash where each key is an integer in string format.")
202
- end
203
- elsif !webhook_certificates.nil?
204
- raise_validation_error("webhook_certificates cannot be set using a #{webhook_certificates.class}")
205
- end
206
- end
207
-
208
196
  def warning_error_classes
209
197
  warning_error_class_names.collect do | class_name |
210
198
  begin
@@ -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