pact_broker 2.85.0 → 2.88.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/trigger_pact_docs_update.yml +22 -0
  3. data/CHANGELOG.md +67 -0
  4. data/DEVELOPER_DOCUMENTATION.md +0 -2
  5. data/db/migrations/20210914_add_labels_to_webhooks.rb +14 -0
  6. data/db/migrations/20210915_add_verified_by_to_verification.rb +6 -0
  7. data/db/migrations/20210929_increase_event_context_column_size.rb +14 -0
  8. data/docker-compose-ci-mysql.yml +1 -0
  9. data/docker-compose-test.yml +2 -0
  10. data/docs/CONFIGURATION.md +255 -66
  11. data/docs/api/WEBHOOKS.md +789 -0
  12. data/docs/configuration.yml +166 -101
  13. data/lib/db.rb +0 -1
  14. data/lib/pact/doc/interaction_view_model.rb +2 -2
  15. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +1 -1
  16. data/lib/pact_broker/api/contracts/configuration.rb +33 -0
  17. data/lib/pact_broker/api/contracts/publish_contracts_schema.rb +35 -16
  18. data/lib/pact_broker/api/contracts/webhook_contract.rb +24 -2
  19. data/lib/pact_broker/api/decorators/matrix_decorator.rb +3 -1
  20. data/lib/pact_broker/api/decorators/pact_decorator.rb +12 -0
  21. data/lib/pact_broker/api/decorators/verification_decorator.rb +9 -3
  22. data/lib/pact_broker/api/decorators/webhook_decorator.rb +27 -4
  23. data/lib/pact_broker/api/pact_broker_urls.rb +30 -6
  24. data/lib/pact_broker/api/resources/all_webhooks.rb +2 -2
  25. data/lib/pact_broker/api/resources/default_base_resource.rb +5 -1
  26. data/lib/pact_broker/api/resources/environment.rb +4 -4
  27. data/lib/pact_broker/api/resources/environments.rb +1 -1
  28. data/lib/pact_broker/api/resources/index.rb +7 -1
  29. data/lib/pact_broker/api/resources/metadata_resource_methods.rb +33 -3
  30. data/lib/pact_broker/api/resources/pact_version.rb +4 -0
  31. data/lib/pact_broker/api/resources/previous_distinct_pact_version.rb +1 -1
  32. data/lib/pact_broker/api/resources/publish_contracts.rb +1 -1
  33. data/lib/pact_broker/api/resources/verification.rb +5 -2
  34. data/lib/pact_broker/api/resources/webhook_execution.rb +7 -3
  35. data/lib/pact_broker/api/resources/webhook_execution_methods.rb +23 -17
  36. data/lib/pact_broker/api.rb +6 -0
  37. data/lib/pact_broker/application_context.rb +5 -0
  38. data/lib/pact_broker/config/runtime_configuration.rb +4 -0
  39. data/lib/pact_broker/config/runtime_configuration_database_methods.rb +1 -1
  40. data/lib/pact_broker/db/clean.rb +1 -2
  41. data/lib/pact_broker/db/delete_overwritten_data.rb +41 -23
  42. data/lib/pact_broker/deployments/deployed_version.rb +1 -0
  43. data/lib/pact_broker/deployments/deployed_version_service.rb +1 -0
  44. data/lib/pact_broker/deployments/environment_service.rb +7 -2
  45. data/lib/pact_broker/deployments/released_version_service.rb +1 -0
  46. data/lib/pact_broker/doc/controllers/app.rb +1 -0
  47. data/lib/pact_broker/doc/views/can-i-deploy.markdown +2 -1
  48. data/lib/pact_broker/doc/views/index/publish-contracts.markdown +38 -9
  49. data/lib/pact_broker/doc/views/pacticipant/label.markdown +12 -0
  50. data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +7 -7
  51. data/lib/pact_broker/doc/views/webhooks.markdown +17 -0
  52. data/lib/pact_broker/domain/index_item.rb +9 -0
  53. data/lib/pact_broker/domain/pacticipant.rb +4 -0
  54. data/lib/pact_broker/domain/verification.rb +19 -4
  55. data/lib/pact_broker/domain/webhook.rb +5 -5
  56. data/lib/pact_broker/domain/webhook_pacticipant.rb +6 -0
  57. data/lib/pact_broker/index/service.rb +4 -4
  58. data/lib/pact_broker/locale/en.yml +4 -1
  59. data/lib/pact_broker/matrix/head_row.rb +1 -1
  60. data/lib/pact_broker/matrix/parse_can_i_deploy_query.rb +5 -3
  61. data/lib/pact_broker/matrix/quick_row.rb +0 -1
  62. data/lib/pact_broker/matrix/repository.rb +0 -1
  63. data/lib/pact_broker/matrix/row.rb +2 -2
  64. data/lib/pact_broker/pacticipants/repository.rb +1 -1
  65. data/lib/pact_broker/pacts/latest_pact_publication_id_for_consumer_version.rb +0 -1
  66. data/lib/pact_broker/pacts/metadata.rb +7 -1
  67. data/lib/pact_broker/pacts/pact_publication.rb +7 -0
  68. data/lib/pact_broker/pacts/pact_publication_clean_selector_dataset_module.rb +19 -0
  69. data/lib/pact_broker/pacts/pact_publication_dataset_module.rb +30 -2
  70. data/lib/pact_broker/pacts/pact_version.rb +24 -1
  71. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +5 -4
  72. data/lib/pact_broker/pacts/repository.rb +50 -47
  73. data/lib/pact_broker/pacts/service.rb +5 -5
  74. data/lib/pact_broker/string_refinements.rb +1 -1
  75. data/lib/pact_broker/test/http_test_data_builder.rb +40 -10
  76. data/lib/pact_broker/test/test_data_builder.rb +28 -5
  77. data/lib/pact_broker/ui/helpers/url_helper.rb +12 -0
  78. data/lib/pact_broker/ui/view_models/index_item.rb +15 -1
  79. data/lib/pact_broker/ui/view_models/index_item_branch_head.rb +39 -0
  80. data/lib/pact_broker/ui/view_models/index_item_provider_branch_head.rb +39 -0
  81. data/lib/pact_broker/ui/views/dashboard/show.haml +14 -7
  82. data/lib/pact_broker/verifications/repository.rb +5 -2
  83. data/lib/pact_broker/verifications/service.rb +7 -4
  84. data/lib/pact_broker/version.rb +1 -1
  85. data/lib/pact_broker/versions/abbreviate_number.rb +8 -4
  86. data/lib/pact_broker/versions/branch_head.rb +0 -2
  87. data/lib/pact_broker/versions/branch_version.rb +1 -0
  88. data/lib/pact_broker/versions/repository.rb +0 -1
  89. data/lib/pact_broker/webhooks/event_listener.rb +4 -2
  90. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +18 -3
  91. data/lib/pact_broker/webhooks/repository.rb +10 -4
  92. data/lib/pact_broker/webhooks/trigger_service.rb +1 -1
  93. data/lib/pact_broker/webhooks/webhook.rb +71 -8
  94. data/lib/webmachine/describe_routes.rb +62 -0
  95. data/public/stylesheets/index.css +5 -0
  96. data/script/data/auto-create-things-for-tags.rb +1 -0
  97. data/script/data/branches.rb +1 -1
  98. data/script/data/contract-published-requiring-verification.rb +0 -1
  99. data/script/data/verify-pact-for-multiple-selectors.rb +30 -0
  100. data/script/docs/generate-api-docs.rb +117 -0
  101. data/script/docs/generate-configuration-docs.rb +24 -3
  102. data/script/docs/regenerate-api-docs.sh +11 -0
  103. data/script/generate-erd +55 -0
  104. data/spec/features/create_webhook_spec.rb +55 -10
  105. data/spec/features/get_pact_spec.rb +2 -3
  106. data/spec/fixtures/approvals/docs_webhooks_executing_a_saved_webhook_options.approved.json +20 -0
  107. data/spec/fixtures/approvals/docs_webhooks_executing_a_saved_webhook_post.approved.json +43 -0
  108. data/spec/fixtures/approvals/docs_webhooks_executing_an_unsaved_webhook_options.approved.json +20 -0
  109. data/spec/fixtures/approvals/docs_webhooks_executing_an_unsaved_webhook_post.approved.json +63 -0
  110. data/spec/fixtures/approvals/docs_webhooks_logs_of_triggered_webhook_get.approved.json +20 -0
  111. data/spec/fixtures/approvals/docs_webhooks_logs_of_triggered_webhook_options.approved.json +20 -0
  112. data/spec/fixtures/approvals/docs_webhooks_pact_webhooks_get.approved.json +45 -0
  113. data/spec/fixtures/approvals/docs_webhooks_pact_webhooks_options.approved.json +20 -0
  114. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_pact_publication_get.approved.json +52 -0
  115. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_pact_publication_options.approved.json +20 -0
  116. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_verification_publication_get.approved.json +32 -0
  117. data/spec/fixtures/approvals/docs_webhooks_triggered_webhooks_for_verification_publication_options.approved.json +20 -0
  118. data/spec/fixtures/approvals/docs_webhooks_webhook_get.approved.json +74 -0
  119. data/spec/fixtures/approvals/docs_webhooks_webhook_options.approved.json +20 -0
  120. data/spec/fixtures/approvals/docs_webhooks_webhook_put.approved.json +77 -0
  121. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_a_provider_get.approved.json +41 -0
  122. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_a_provider_options.approved.json +20 -0
  123. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_and_provider_get.approved.json +45 -0
  124. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_and_provider_options.approved.json +20 -0
  125. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_get.approved.json +41 -0
  126. data/spec/fixtures/approvals/docs_webhooks_webhooks_for_consumer_options.approved.json +20 -0
  127. data/spec/fixtures/approvals/docs_webhooks_webhooks_get.approved.json +45 -0
  128. data/spec/fixtures/approvals/docs_webhooks_webhooks_options.approved.json +20 -0
  129. data/spec/fixtures/approvals/docs_webhooks_webhooks_post.approved.json +78 -0
  130. data/spec/fixtures/approvals/docs_webhooks_webhooks_status_get.approved.json +79 -0
  131. data/spec/fixtures/approvals/docs_webhooks_webhooks_status_options.approved.json +20 -0
  132. data/spec/fixtures/approvals/get_provider_pacts_for_verification.approved.json +1 -2
  133. data/spec/fixtures/approvals/publish_contract_no_branch.approved.json +1 -2
  134. data/spec/fixtures/approvals/publish_contract_nothing_exists.approved.json +1 -2
  135. data/spec/fixtures/approvals/publish_contract_nothing_exists_with_webhook.approved.json +1 -2
  136. data/spec/fixtures/approvals/publish_contract_verification_already_exists.approved.json +1 -2
  137. data/spec/fixtures/approvals/publish_contract_with_validation_error.approved.json +1 -2
  138. data/spec/fixtures/invalid-publish-contract-body.json +38 -0
  139. data/spec/fixtures/verification.json +4 -0
  140. data/spec/integration/pact_metdata_spec.rb +105 -0
  141. data/spec/integration/webhooks/contract_publication_spec.rb +68 -0
  142. data/spec/integration/webhooks/contract_requiring_verification_published_spec.rb +67 -0
  143. data/spec/integration/webhooks/pact_publication_spec.rb +51 -0
  144. data/spec/integration/webhooks_documentation_spec.rb +348 -0
  145. data/spec/lib/pact/doc/markdown/consumer_contract_renderer_spec.rb +2 -2
  146. data/spec/lib/pact_broker/api/contracts/publish_contracts_schema_spec.rb +13 -0
  147. data/spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb +50 -0
  148. data/spec/lib/pact_broker/api/decorators/matrix_decorator_spec.rb +1 -1
  149. data/spec/lib/pact_broker/api/decorators/verification_decorator_spec.rb +15 -7
  150. data/spec/lib/pact_broker/api/decorators/verification_summary_decorator_spec.rb +4 -2
  151. data/spec/lib/pact_broker/api/decorators/webhook_decorator_spec.rb +4 -4
  152. data/spec/lib/pact_broker/api/pact_broker_urls_spec.rb +18 -0
  153. data/spec/lib/pact_broker/api/resources/triggered_webhook_logs_spec.rb +6 -5
  154. data/spec/lib/pact_broker/config/runtime_configuration_documentation_spec.rb +30 -0
  155. data/spec/lib/pact_broker/deployments/environment_service_spec.rb +22 -1
  156. data/spec/lib/pact_broker/domain/webhook_spec.rb +35 -0
  157. data/spec/lib/pact_broker/matrix/head_row_spec.rb +9 -5
  158. data/spec/lib/pact_broker/matrix/parse_can_i_deploy_query_spec.rb +13 -0
  159. data/spec/lib/pact_broker/pacts/{latest_tagged_pact_publications_spec.rb → pact_publication_clean_selector_dataset_module_spec.rb} +7 -9
  160. data/spec/lib/pact_broker/pacts/pact_version_spec.rb +32 -0
  161. data/spec/lib/pact_broker/pacts/repository_find_for_verification_spec.rb +4 -5
  162. data/spec/lib/pact_broker/pacts/repository_spec.rb +33 -0
  163. data/spec/lib/pact_broker/ui/view_models/index_item_spec.rb +1 -1
  164. data/spec/lib/pact_broker/verifications/service_spec.rb +22 -8
  165. data/spec/lib/pact_broker/versions/abbreviate_number_spec.rb +2 -1
  166. data/spec/lib/pact_broker/webhooks/render_spec.rb +3 -2
  167. data/spec/lib/pact_broker/webhooks/repository_spec.rb +158 -15
  168. data/spec/lib/pact_broker/webhooks/webhook_spec.rb +8 -5
  169. data/spec/support/documentation.rb +64 -0
  170. data/spec/support/rack_helpers.rb +1 -1
  171. data/tasks/db.rake +4 -1
  172. data/tasks/development.rake +14 -13
  173. metadata +89 -12
  174. data/lib/pact_broker/pacts/all_pact_publications.rb +0 -158
  175. data/lib/pact_broker/pacts/latest_pact_publications.rb +0 -48
  176. data/lib/pact_broker/pacts/latest_pact_publications_by_consumer_version.rb +0 -26
  177. data/lib/pact_broker/pacts/latest_tagged_pact_publications.rb +0 -45
  178. data/lib/pact_broker/verifications/latest_verification_for_pact_version.rb +0 -39
  179. data/spec/lib/pact_broker/verifications/latest_verification_for_pact_version_spec.rb +0 -18
@@ -2,11 +2,14 @@ require "pact_broker/deployments/environment"
2
2
  require "securerandom"
3
3
  require "pact_broker/pacticipants/generate_display_name"
4
4
  require "pact_broker/string_refinements"
5
+ require "pact_broker/repositories/scopes"
5
6
 
6
7
  module PactBroker
7
8
  module Deployments
8
9
  module EnvironmentService
9
10
  using PactBroker::StringRefinements
11
+ extend PactBroker::Repositories::Scopes
12
+
10
13
  extend self
11
14
 
12
15
  def self.included(base)
@@ -25,7 +28,7 @@ module PactBroker
25
28
  environment.save
26
29
  end
27
30
 
28
- def update(uuid, environment)
31
+ def replace(uuid, environment)
29
32
  environment.uuid = uuid
30
33
  if environment.display_name.blank?
31
34
  environment.display_name = PactBroker::Pacticipants::GenerateDisplayName.call(environment.name)
@@ -33,8 +36,10 @@ module PactBroker
33
36
  environment.upsert
34
37
  end
35
38
 
39
+ alias_method :update, :replace # For PF
40
+
36
41
  def find_all
37
- PactBroker::Deployments::Environment.order(Sequel.function(:lower, :display_name)).all
42
+ scope_for(PactBroker::Deployments::Environment).order(Sequel.function(:lower, :display_name)).all
38
43
  end
39
44
 
40
45
  def find(uuid)
@@ -52,6 +52,7 @@ module PactBroker
52
52
  .where(pacticipant_id: pacticipant.id)
53
53
  .eager(:version)
54
54
  .eager(:environment)
55
+ .order(:created_at, :id)
55
56
  .all
56
57
  end
57
58
  end
@@ -17,6 +17,7 @@ module PactBroker
17
17
  "webhooks-webhooks" => "webhooks",
18
18
  "webhook" => "webhooks",
19
19
  "can-i-deploy-pacticipant-version-to-tag" => "can-i-deploy",
20
+ "can-i-deploy-pacticipant-version-to-environment" => "can-i-deploy",
20
21
  "pacticipant" => "pacticipants"
21
22
  }.freeze
22
23
 
@@ -10,7 +10,8 @@ A simplified resource that accepts the same parameters as the basic usage of the
10
10
  * _version_: The version of the pacticipant (application) you want to deploy (required).
11
11
  * _environment_: The name of the environment into which the pacticipant (application) is to be deployed.
12
12
  * _to_: The tag used to identify the environment into which you wish to deploy the application (eg. `test` or `prod`). Deprecated - use the `environment=ENVIRONMENT` parameter in preference to the `to=TAG` parameter as deployments and environments are now explictly supported.
13
- * _ignore[]_: The name of the pacticipant to ignore when determining if it is safe to deploy (optional). May be used multiple times.
13
+ * _ignore[]_: The name of the pacticipant to ignore when determining if it is safe to deploy (optional). May be used multiple times. eg `ignore[]=foo&ignore[]=bar`
14
+ * _ignore[][pacticipant]_ and _ignore[][version]_: When ignoring a specific pacticipant version, the name and version of the application to ignore may be specified using "nested" param names syntax. eg. `ignore[][pacticipant]=foo&ignore[][version]=2ac5a946&ignore[][pacticipant]=bar`
14
15
 
15
16
 
16
17
  If you have an environment that you identify with the name `prod`, and each time you deployed an application to the prod environment you recorded the deployment of relevant application version in the Pact Broker using `record-deployment`, then calling `/can-i-deploy?pacticipant=Foo&version=734137278d&environment=prod` will check that version 734137278d of Foo has a successful verification result with each of the integrated application versions that are currently in prod. That is, it is safe to deploy.
@@ -4,11 +4,16 @@ Allowed methods: `POST`
4
4
 
5
5
  Path: `/contracts/publish`
6
6
 
7
+ Index relation: `pb:publish-contracts`
8
+
9
+ Supported from: `v2.86.0`
10
+
7
11
  This is the preferred endpoint with which to publish contracts (previously, contracts were published using multiple calls to different endpoints to create the tag and contract resources). To detect whether this endpoint exists in a particular version of the Pact Broker, make a request to the index resource, and locate the `pb:publish-contracts` relation. Do a `POST` to the href specified for that relation.
8
12
 
9
- The previous tag and pact endpoints are still supported, however, future features that build on this endpoint may not be able to be backported into those endpoints (eg. publishing pacts with a branch).
13
+ The previous tag and pact endpoints are still supported, however, future features that build on this endpoint may not be able to be backported into those endpoints.
10
14
 
11
15
  ## Parameters
16
+
12
17
  * `pacticipantName`: the name of the application. Required.
13
18
  * `pacticipantVersionNumber`: the version number of the application. Required. It is recommended that this should be or include the git SHA. See [http://docs.pact.io/versioning](http://docs.pact.io/versioning).
14
19
  * `branch`: The git branch name. Optional but strongly recommended.
@@ -20,7 +25,31 @@ The previous tag and pact endpoints are still supported, however, future feature
20
25
  * `specification`: currently, only contracts of type "pact" are supported, but this will be extended in the future. Required.
21
26
  * `contentType`: currently, only contracts with a content type of "application/json" are supported. Required.
22
27
  * `content`: the content of the contract. Must be Base64 encoded. Required.
23
- * `onConflict`: Specifies the action to take when a contract for this consumer version already exists with different content. Allowed values are `overwrite`|`merge`. Optional. Defaults to `overwrite`. When `merge` is specified, the interactions are merged into any pre-existing pact. This is required when the tests that generate pact files are split over multiple nodes.
28
+
29
+ ## Responses
30
+
31
+ ### Success
32
+
33
+ * `notices`
34
+ * `level`: one of `debug`, `info`, `warning`,`prompt`,`success`
35
+ * `text`: the text of the notice. This is designed to be displayed in the output of a CLI.
36
+
37
+ The `_links` section will contain links to all the resources created by the publication. The relations are:
38
+
39
+ * `pb:contracts` (array)
40
+ * `pb:pacticipant-version-tags` (array)
41
+ * `pb:pacticipant-version`
42
+ * `pb:pacticipant`
43
+
44
+ ### Errors
45
+
46
+ Any validation errors will be returned in the standard Pact Broker format:
47
+
48
+ {
49
+ "errors": {
50
+ "<fieldName>": ["message 1", "message 2"]
51
+ }
52
+ }
24
53
 
25
54
  ## Example
26
55
 
@@ -37,30 +66,29 @@ The previous tag and pact endpoints are still supported, however, future feature
37
66
  "providerName": "Bar",
38
67
  "specification": "pact",
39
68
  "contentType": "application/json",
40
- "content": "<base64 encoded JSON pact>",
41
- "onConflict": "overwrite"
69
+ "content": "<base64 encoded JSON pact>"
42
70
  }
43
71
  ]
44
72
  }
45
73
 
46
74
  {
47
75
  {
48
- "logs": [
76
+ "notices": [
49
77
  {
50
78
  "level": "debug",
51
- "message": "Created Foo version dc5eb529230038a4673b8c971395bd2922d8b240 with branch main and tags main"
79
+ "text": "Created Foo version dc5eb529230038a4673b8c971395bd2922d8b240 with branch main and tags main"
52
80
  },
53
81
  {
54
82
  "level": "info",
55
- "message": "Pact published for Foo version dc5eb529230038a4673b8c971395bd2922d8b240 and provider Bar."
83
+ "text": "Pact published for Foo version dc5eb529230038a4673b8c971395bd2922d8b240 and provider Bar."
56
84
  },
57
85
  {
58
86
  "level": "debug",
59
- "message": " Events detected: contract_published, contract_content_changed (first time any pact published for this consumer with consumer version tagged main)"
87
+ "text": " Events detected: contract_published, contract_content_changed (first time any pact published for this consumer with consumer version tagged main)"
60
88
  },
61
89
  {
62
90
  "level": "debug",
63
- "message": " Webhook \"foo webhook\" triggered for event contract_content_changed.\n See logs at http://example.org/triggered-webhooks/1234/logs\""
91
+ "text": " Webhook \"foo webhook\" triggered for event contract_content_changed.\n See logs at http://example.org/triggered-webhooks/1234/logs\""
64
92
  }
65
93
  ],
66
94
  "_embedded": {
@@ -118,3 +146,4 @@ The previous tag and pact endpoints are still supported, however, future feature
118
146
  }
119
147
  }
120
148
  }
149
+
@@ -0,0 +1,12 @@
1
+
2
+ # Pacticipant labels
3
+
4
+ Allowed methods: `GET`, `PUT`, `DELETE`
5
+
6
+ Path: `/pacticipants/{pacticipant}/labels/{label}`
7
+
8
+ Get, create or delete pacticipant labels.
9
+
10
+ Pacticipants can be queried by label with `/pacticipants/label/{label}`.
11
+
12
+ Labels are also used to create generic webhooks that are triggered for subset of pacticipants with label.
@@ -26,34 +26,34 @@ Example: This data structure represents the way a user might specify "I want to
26
26
  "includeWipPactsSince": "2020-01-01"
27
27
  }
28
28
 
29
+ `consumerVersionSelectors.mainBranch`: if the key is specified, can only be set to `true`. Return the pacts for the configured `mainBranch` of each consumer. Use of this selector requires that the consumer has configured the `mainBranch` property, and has set a branch name when publishing the pacts.
30
+
29
31
  `consumerVersionSelectors.branch`: the branch name of the consumer versions to get the pacts for. Use of this selector requires that the consumer has configured a branch name when publishing the pacts.
30
32
 
31
- `consumerVersionSelectors.fallbackBranch`: the name of the branch to fallback to if the specified `branch` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features.
33
+ `consumerVersionSelectors.fallbackBranch`: the name of the branch to fallback to if the specified `branch` does not exist. Use of this property is discouraged as it may allow a pact to pass on a feature branch while breaking backwards compatibility with the main branch, which is generally not desired. It is better to use two separate consumer version selectors, one with the main branch name, and one with the feature branch name, rather than use this property.
32
34
 
33
35
  `consumerVersionSelectors.deployed`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are currently deployed to any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-deployment` CLI.
34
36
 
35
37
  `consumerVersionSelectors.released`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are released and currently supported in any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-release` CLI.
36
38
 
37
-
38
39
  `consumerVersionSelectors.deployedOrReleased`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are currently deployed or released and currently supported in any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-deployment` or `record-release` CLI.
39
40
 
40
-
41
41
  `consumerVersionSelectors.environment`: the name of the environment containing the consumer versions for which to return the pacts. Used to further qualify `{ "deployed": true }` or `{ "released": true }`. Normally, this would not be needed, as it is recommended to verify the pacts for all currently deployed/currently supported released versions.
42
42
 
43
43
  `consumerVersionSelectors.latest`: true. Used in conjuction with the `tag` and `branch` properties. When used with a `branch`, it may be `true` or the key ommitted (in which case it will be inferred to be `true`). This is because it only makes sense to verify the latest pact for a branch. If a `tag` is specified, and `latest` is `true`, then the latest pact for each of the consumers with that tag will be returned. If a `tag` is specified and the latest flag is *not* set to `true`, *all* the pacts with the specified tag will be returned. (This might seem a bit weird, but it's done this way to match the syntax used for the matrix query params. See https://docs.pact.io/selectors).
44
44
 
45
45
  `consumerVersionSelectors.consumer`: allows a selector to only be applied to a certain consumer.
46
46
 
47
+ `consumerVersionSelectors.tag`: the tag name(s) of the consumer versions to get the pacts for. *This field is still supported but it is recommended to use the `branch` in preference now.*
48
+
49
+ `consumerVersionSelectors.fallbackTag`: the name of the tag to fallback to if the specified `tag` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use the `fallbackBranch` in preference now.*
50
+
47
51
  `providerVersionBranch`: the repository branch name for the provider application version that will be published with the verification results. This is used by the Broker to determine whether or not a particular pact is in pending state or not.
48
52
 
49
53
  `includePendingStatus`: true|false (default false). When true, a pending boolean will be added to the verificationProperties in the response, and an extra message will appear in the notices array to indicate why this pact is/is not in pending state. This will allow your code to handle the response based on only what is present in the response, and not have to do ifs based on the user's options together with the response. As requested in the "pacts for verification" issue, please print out these messages in the tests if possible. If not possible, perhaps create a separate task which will list the pact URLs and messages for debugging purposes.
50
54
 
51
55
  `includeWipPactsSince`: Date string. The date from which to include the "work in progress" pacts. See https://docs.pact.io/wip for more information on work in progress pacts.
52
56
 
53
- `consumerVersionSelectors.tag`: the tag name(s) of the consumer versions to get the pacts for. *This field is still supported but it is recommended to use the `branch` in preference now.*
54
-
55
- `consumerVersionSelectors.fallbackTag`: the name of the tag to fallback to if the specified `tag` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use the `fallbackBranch` in preference now.*
56
-
57
57
  `providerVersionTags`: the tag name(s) for the provider application version that will be published with the verification results. This is used by the Broker to determine whether or not a particular pact is in pending state or not. This parameter can be specified multiple times. *This field is still supported but it is recommended to use the `providerVersionBranch` in preference now.*
58
58
 
59
59
  ### Response body
@@ -79,6 +79,23 @@ To specify an XML body, you will need to use a correctly escaped string (or use
79
79
 
80
80
  **BEWARE** While the basic auth password, and any header containing the word `authorization` or `token` will be redacted from the UI and the logs, the password could be reverse engineered from the database, so make a separate account for the Pact Broker to use in your webhooks. Don't use your personal account!
81
81
 
82
+ #### Consumer or provider label matching
83
+
84
+ Webhooks can be created to match events of certain set of [consumers or providers by label](/doc/label?context=pacticipant). Use `label` attribute for either `provider` or `consumer`. Both are optional, but they cannot be provided when `name` attribute is present. Following example would trigger a webhook when any contract with `async` labeled provider changed its content:
85
+
86
+ {
87
+ "provider": {
88
+ "label": "async"
89
+ },
90
+ "events": [{
91
+ "name": "contract_content_changed"
92
+ }],
93
+ "request": {
94
+ "method": "POST",
95
+ "url": "http://master.ci.my.domain:8085/rest/api/latest/queue/SOME-PROJECT"
96
+ }
97
+ }
98
+
82
99
  #### Event types
83
100
 
84
101
  `contract_published:` triggered every time a contract is published. It is not recommended to trigger your provider verification build every time a contract is published - see `contract_content_changed` below.
@@ -74,6 +74,10 @@ module PactBroker
74
74
  consumer_version.branch_heads.collect(&:branch_name)
75
75
  end
76
76
 
77
+ def consumer_version_branch_heads
78
+ consumer_version.branch_heads
79
+ end
80
+
77
81
  def consumer_version_environment_names
78
82
  (consumer_version.current_deployed_versions.collect(&:environment).collect(&:name) + consumer_version.current_supported_released_versions.collect(&:environment).collect(&:name)).uniq
79
83
  end
@@ -90,6 +94,11 @@ module PactBroker
90
94
  @latest_verification ? @latest_verification.provider_version_number : nil
91
95
  end
92
96
 
97
+ def provider_version_branch_heads
98
+ provider_version&.branch_heads || []
99
+ end
100
+
101
+
93
102
  def provider_version_branches
94
103
  provider_version&.branch_heads&.collect(&:branch_name) || []
95
104
  end
@@ -76,6 +76,10 @@ module PactBroker
76
76
  def branch_head_for(branch_name)
77
77
  branch_heads.find{ | branch_head | branch_head.branch_name == branch_name }
78
78
  end
79
+
80
+ def label?(name)
81
+ labels.any? { |label| label.name == name }
82
+ end
79
83
  end
80
84
  end
81
85
  end
@@ -37,6 +37,22 @@ module PactBroker
37
37
  where(consumer: PactBroker::Domain::Pacticipant.find_by_name(consumer_name))
38
38
  end
39
39
 
40
+ # TODO optimise this
41
+ def from_provider_main_branch
42
+ providers_join = {
43
+ Sequel[:verifications][:provider_id] => Sequel[:providers][:id]
44
+ }
45
+
46
+ branch_versions_join = {
47
+ Sequel[:verifications][:provider_version_id] => Sequel[:branch_versions][:version_id],
48
+ Sequel[:providers][:main_branch] => Sequel[:branch_versions][:branch_name]
49
+ }
50
+
51
+ join(:pacticipants, providers_join, { table_alias: :providers })
52
+ .join(:branch_versions, branch_versions_join)
53
+ end
54
+
55
+ # TODO change this to a group by
40
56
  def latest_by_pact_version
41
57
  base_query = self
42
58
  base_join = {
@@ -116,10 +132,6 @@ module PactBroker
116
132
  join(:latest_pact_publication_ids_for_consumer_versions, { pact_version_id: :pact_version_id } )
117
133
  end
118
134
 
119
- # Expects to be joined with AllPactPublications or subclass
120
- # Beware that when columns with the same name exist in both datasets
121
- # you may get the wrong column back in your model.
122
-
123
135
  def delete
124
136
  require "pact_broker/webhooks/triggered_webhook"
125
137
  PactBroker::Webhooks::TriggeredWebhook.where(verification: self).delete
@@ -246,6 +258,9 @@ end
246
258
  # wip | boolean | NOT NULL DEFAULT false
247
259
  # consumer_version_selector_hashes | text |
248
260
  # tag_names | text |
261
+ # pact_pending | boolean |
262
+ # verified_by_implementation | text |
263
+ # verified_by_version | text |
249
264
  # Indexes:
250
265
  # verifications_pkey | PRIMARY KEY btree (id)
251
266
  # verifications_pact_version_id_number_index | UNIQUE btree (pact_version_id, number)
@@ -33,11 +33,11 @@ module PactBroker
33
33
 
34
34
  def scope_description
35
35
  if consumer && provider
36
- "A webhook for the pact between #{consumer.name} and #{provider.name}"
36
+ "A webhook for the pact between #{consumer_name} and #{provider_name}"
37
37
  elsif provider
38
- "A webhook for all pacts with provider #{provider.name}"
38
+ "A webhook for all pacts with provider #{provider_name}"
39
39
  elsif consumer
40
- "A webhook for all pacts with consumer #{consumer.name}"
40
+ "A webhook for all pacts with consumer #{consumer_name}"
41
41
  else
42
42
  "A webhook for all pacts"
43
43
  end
@@ -63,11 +63,11 @@ module PactBroker
63
63
  end
64
64
 
65
65
  def consumer_name
66
- consumer && consumer.name
66
+ consumer && (consumer.name || (consumer.label && "#{provider ? 'consumers ' : ''}labeled '#{consumer.label}'"))
67
67
  end
68
68
 
69
69
  def provider_name
70
- provider && provider.name
70
+ provider && (provider.name || (provider.label && "#{consumer ? 'providers ' : ''}labeled '#{provider.label}'"))
71
71
  end
72
72
 
73
73
  def trigger_on_contract_content_changed?
@@ -0,0 +1,6 @@
1
+
2
+ module PactBroker
3
+ module Domain
4
+ WebhookPacticipant = Struct.new(:name, :label, keyword_init: true)
5
+ end
6
+ end
@@ -112,11 +112,11 @@ module PactBroker
112
112
  elsif tags_option == true
113
113
  latest_verifications_for_cv_tags
114
114
  .select{ | v | v.consumer_id == pact_publication.consumer_id && v.provider_id == pact_publication.provider_id && pact_publication.head_pact_tags.collect(&:name).include?(v.consumer_version_tag_name) }
115
- .sort{ |v1, v2| v1.id <=> v2.id }.last || (is_overall_latest && pact_publication.integration.latest_verification)
115
+ .sort{ |v1, v2| v1.id <=> v2.id }.last || (is_overall_latest ? pact_publication.integration.latest_verification : nil)
116
116
  elsif tags_option.is_a?(Array)
117
117
  latest_verifications_for_cv_tags
118
118
  .select{ | v | v.consumer_id == pact_publication.consumer_id && v.provider_id == pact_publication.provider_id && pact_publication.head_pact_tags.collect(&:name).include?(v.consumer_version_tag_name) && tags_option.include?(v.consumer_version_tag_name) }
119
- .sort{ |v1, v2| v1.id <=> v2.id }.last || (is_overall_latest && pact_publication.integration.latest_verification)
119
+ .sort{ |v1, v2| v1.id <=> v2.id }.last || (is_overall_latest ? pact_publication.integration.latest_verification : nil)
120
120
  else
121
121
  pact_publication.integration.latest_verification
122
122
  end
@@ -138,8 +138,8 @@ module PactBroker
138
138
  pact_publications = head_pact_publications(consumer_name: consumer_name, provider_name: provider_name, tags: true, page_number: page_number, page_size: page_size)
139
139
  .eager(:consumer)
140
140
  .eager(:provider)
141
- .eager(pact_version: { latest_verification: { provider_version: [{ current_deployed_versions: :environment }, { current_supported_released_versions: :environment }, :branch_heads, { tags: :head_tag }]} })
142
- .eager(consumer_version: [{ current_deployed_versions: :environment }, { current_supported_released_versions: :environment }, :branch_heads, { tags: :head_tag }])
141
+ .eager(pact_version: { latest_verification: { provider_version: [{ current_deployed_versions: :environment }, { current_supported_released_versions: :environment }, { branch_heads: :branch_version }, { tags: :head_tag }]} })
142
+ .eager(consumer_version: [{ current_deployed_versions: :environment }, { current_supported_released_versions: :environment }, { branch_heads: :branch_version }, { tags: :head_tag }])
143
143
  .eager(:head_pact_publications_for_tags)
144
144
 
145
145
  pact_publications.all.collect do | pact_publication |
@@ -13,6 +13,7 @@ en:
13
13
  single_line?: "cannot contain multiple lines"
14
14
  no_spaces?: "cannot contain spaces"
15
15
  environment_with_name_exists?: "with name '%{value}' does not exist"
16
+ none?: "cannot be provided"
16
17
 
17
18
  pact_broker:
18
19
  messages:
@@ -33,16 +34,18 @@ en:
33
34
  consumerVersionTags: The list of tag names for the most recent consumer version associated with the pact content, separated by ", "
34
35
  consumerVersionBranch: The repository branch associated with the consumer version
35
36
  providerVersionTags: The list of tag names for the provider version associated with the verification result, separated by ", ".
37
+ providerVersionDescriptions: The descriptions of the provider version(s) for which the contract_requiring_verification_published webhook has been triggered.
36
38
  providerVersionBranch: The repository branch associated with the provider version
37
39
  consumerLabels: The list of labels for the consumer associated with the pact content, separated by ", ".
38
40
  providerLabels: The list of labels for the provider associated with the pact content, separated by ", ".
39
- pactUrl: The "permalink" URL to the newly published pact (the URL specifying the consumer version URL, rather than the "/latest" format.
41
+ pactUrl: The "permalink" URL to the newly published pact content (using the pact version SHA).
40
42
  verificationResultUrl: The URL to the relevant verification result.
41
43
  githubVerificationStatus: The verification status using the correct keywords for posting to the Github commit status API. See https://developer.github.com/v3/repos/statuses
42
44
  bitbucketVerificationStatus: The verification status using the correct keywords for posting to the Bitbucket commit status API. See https://developer.atlassian.com/server/bitbucket/how-tos/updating-build-status-for-commits/
43
45
  azureDevOpsVerificationStatus: The verification status using the correct keywords for posting to the Azure DevOps GitStatusState API. See https://docs.microsoft.com/en-us/rest/api/azure/devops/git/statuses/create?view=azure-devops-rest-6.0
44
46
  gitlabVerificationStatus: The verification status using the correct keywords for posting to the Gitlab Commits API. See https://docs.gitlab.com/ee/api/commits.html#post-the-build-status-to-a-commit
45
47
  eventName: The name of the event that triggered the webhook
48
+ buildUrl: The URL of the build that published the resources that have triggered the webhook (currently only supported for contracts published using the 'all in one' endpoint).
46
49
  currentlyDeployedProviderVersionNumber: The version number of the currently deployed provider version (when used in a template, the webhook will be triggered once for each currently deployed provider version)
47
50
  no_webhooks_enabled_for_event: No enabled webhooks found for the detected events
48
51
  webhook_triggered_for_event: Webhook %{webhook_description} triggered for event %{event_name}.
@@ -38,7 +38,7 @@ module PactBroker
38
38
  # This will only work when using eager loading. The keys are just blanked out to avoid errors.
39
39
  # I don't understand how they work at all.
40
40
  # It would be nice to do this declaratively.
41
- many_to_many :webhooks, :left_key => [], left_primary_key: [], :eager_loader=>(proc do |eo_opts|
41
+ many_to_many :webhooks, class: :'PactBroker::Webhooks::Webhook', :left_key => [], left_primary_key: [], :eager_loader=>(proc do |eo_opts|
42
42
  eo_opts[:rows].each do |row|
43
43
  row.associations[:webhooks] = []
44
44
  end
@@ -29,9 +29,11 @@ module PactBroker
29
29
  end
30
30
 
31
31
  if params[:ignore].is_a?(Array)
32
- options[:ignore_selectors] = params[:ignore].collect do | pacticipant_name |
33
- if pacticipant_name.is_a?(String)
34
- PactBroker::Matrix::UnresolvedSelector.new(pacticipant_name: pacticipant_name)
32
+ options[:ignore_selectors] = params[:ignore].collect do | param |
33
+ if param.is_a?(String)
34
+ PactBroker::Matrix::UnresolvedSelector.new(pacticipant_name: param)
35
+ elsif param.is_a?(Hash) && param.key?(:pacticipant)
36
+ PactBroker::Matrix::UnresolvedSelector.new({ pacticipant_name: param[:pacticipant], pacticipant_version_number: param[:version] }.compact)
35
37
  end
36
38
  end.compact
37
39
  else
@@ -1,4 +1,3 @@
1
- require "pact_broker/pacts/all_pact_publications"
2
1
  require "pact_broker/repositories/helpers"
3
2
  require "pact_broker/matrix/query_builder"
4
3
  require "sequel"
@@ -11,7 +11,6 @@ require "pact_broker/matrix/query_results_with_deployment_status_summary"
11
11
  require "pact_broker/matrix/resolved_selector"
12
12
  require "pact_broker/matrix/unresolved_selector"
13
13
  require "pact_broker/verifications/latest_verification_id_for_pact_version_and_provider_version"
14
- require "pact_broker/pacts/latest_pact_publications_by_consumer_version"
15
14
 
16
15
  module PactBroker
17
16
  module Matrix
@@ -150,11 +150,11 @@ module PactBroker
150
150
  end
151
151
 
152
152
  def consumer
153
- @consumer ||= OpenStruct.new(name: consumer_name, id: consumer_id)
153
+ @consumer ||= Domain::Pacticipant.new(name: consumer_name).tap { |pacticipant| pacticipant.id = consumer_id }
154
154
  end
155
155
 
156
156
  def provider
157
- @provider ||= OpenStruct.new(name: provider_name, id: provider_id)
157
+ @provider ||= Domain::Pacticipant.new(name: provider_name).tap { |pacticipant| pacticipant.id = provider_id }
158
158
  end
159
159
 
160
160
  def consumer_version
@@ -95,7 +95,7 @@ module PactBroker
95
95
  end
96
96
 
97
97
  def search_by_name(pacticipant_name)
98
- terms = pacticipant_name.split.map { |v| v.gsub("_", '\\_') }
98
+ terms = pacticipant_name.split.map { |v| v.gsub("_", "\\_") }
99
99
  string_match_query = Sequel.|( *terms.map { |term| Sequel.ilike(Sequel[:pacticipants][:name], "%#{term}%") })
100
100
  PactBroker::Domain::Pacticipant.where(string_match_query)
101
101
  end
@@ -1,4 +1,3 @@
1
- require "pact_broker/pacts/all_pact_publications"
2
1
  require "pact_broker/repositories/helpers"
3
2
 
4
3
  module PactBroker
@@ -5,7 +5,7 @@ module PactBroker
5
5
 
6
6
  MAPPINGS = [
7
7
  [:consumer_version_tags, "cvt"],
8
- [:consumer_version_number, "cvn"], # for old urls
8
+ [:consumer_version_number, "cvn"], # for old urls and build_metadata_for_consumer_version_number
9
9
  [:consumer_version_id, "cv"],
10
10
  [:wip, "w"],
11
11
  [:pending, "p"],
@@ -33,6 +33,12 @@ module PactBroker
33
33
  end
34
34
  end
35
35
 
36
+ def build_metadata_for_consumer_version_number(consumer_version_number)
37
+ {
38
+ "cvn" => consumer_version_number
39
+ }
40
+ end
41
+
36
42
  # When a pact is published, and a webhook is triggered, this stores
37
43
  # the current tags and consumer version number in the metadata parameter of the
38
44
  # pact version URL that is made available in the webhook template
@@ -8,6 +8,8 @@ require "pact_broker/pacts/pact_publication_dataset_module"
8
8
  require "pact_broker/pacts/pact_publication_wip_dataset_module"
9
9
  require "pact_broker/pacts/eager_loaders"
10
10
  require "pact_broker/pacts/lazy_loaders"
11
+ require "pact_broker/pacts/pact_publication_clean_selector_dataset_module"
12
+ require "pact_broker/pacts/head_pact"
11
13
 
12
14
  module PactBroker
13
15
  module Pacts
@@ -77,6 +79,7 @@ module PactBroker
77
79
  dataset_module do
78
80
  include PactBroker::Repositories::Helpers
79
81
  include PactPublicationDatasetModule
82
+ include PactPublicationCleanSelectorDatasetModule
80
83
  include PactPublicationWipDatasetModule
81
84
  end
82
85
 
@@ -110,6 +113,10 @@ module PactBroker
110
113
  pact_version.latest_verification
111
114
  end
112
115
 
116
+ def latest_main_branch_verification
117
+ pact_version.latest_main_branch_verification
118
+ end
119
+
113
120
  def latest_for_branch?
114
121
  if !defined?(@latest_for_branch)
115
122
  if consumer_version.branch_versions.empty?
@@ -0,0 +1,19 @@
1
+ module PactBroker
2
+ module Pacts
3
+ module PactPublicationCleanSelectorDatasetModule
4
+ # we've already done the latest_by_consumer_tag in the clean
5
+ def latest_by_consumer_tag_for_clean_selector(selector)
6
+ query = latest_by_consumer_tag
7
+ query = query.for_consumer_name(selector.pacticipant_name) if selector.pacticipant_name
8
+ query = query.for_consumer_version_tag(selector.tag) if selector.tag && selector.tag.is_a?(String)
9
+ query = query.where_age_less_than(selector.max_age) if selector.max_age
10
+ query
11
+ end
12
+
13
+ def where_age_less_than(days)
14
+ start_date = Date.today - days
15
+ where{ pact_publications[:created_at] >= start_date }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -27,6 +27,11 @@ module PactBroker
27
27
  where(consumer: consumer)
28
28
  end
29
29
 
30
+ def untagged
31
+ left_outer_join(:tags, { version_id: :consumer_version_id })
32
+ .where(Sequel.qualify(:tags, :name) => nil)
33
+ end
34
+
30
35
  def for_consumer_version_tag tag_name
31
36
  base_query = self
32
37
  if no_columns_selected?
@@ -38,6 +43,12 @@ module PactBroker
38
43
  .remove_overridden_revisions_from_complete_query
39
44
  end
40
45
 
46
+ def for_consumer_version_tag_all_revisions tag_name
47
+ join(:tags, { version_id: :consumer_version_id }) do
48
+ name_like(Sequel[:tags][:name], tag_name)
49
+ end
50
+ end
51
+
41
52
  def for_consumer_name_and_maybe_version_number(consumer_name, consumer_version_number)
42
53
  if consumer_version_number
43
54
  where(consumer_version: PactBroker::Domain::Version.where_pacticipant_name_and_version_number(consumer_name, consumer_version_number))
@@ -128,6 +139,7 @@ module PactBroker
128
139
  .remove_overridden_revisions_from_complete_query
129
140
  end
130
141
 
142
+ # The latest pact publication for each tag
131
143
  def latest_by_consumer_tag
132
144
  tags_join = {
133
145
  Sequel[:pact_publications][:consumer_version_id] => Sequel[:tags][:version_id]
@@ -234,7 +246,7 @@ module PactBroker
234
246
  deployed = base_query.join(:currently_deployed_version_ids, currently_deployed_join)
235
247
  released = base_query.join(:released_versions, released_join)
236
248
 
237
- deployed.union(released)
249
+ deployed.union(released).remove_overridden_revisions_from_complete_query
238
250
  end
239
251
 
240
252
  def verified_before_date(date)
@@ -344,7 +356,19 @@ module PactBroker
344
356
  end
345
357
 
346
358
  def consumer_version_tag(tag)
347
- where(Sequel[:ct][:name] => tag)
359
+ where(name_like(Sequel[:ct][:name],tag))
360
+ end
361
+
362
+ def consumer_version_order_before order
363
+ where(Sequel.lit("consumer_version_order < ?", order))
364
+ end
365
+
366
+ def consumer_version_order_after order
367
+ where(Sequel.lit("consumer_version_order > ?", order))
368
+ end
369
+
370
+ def latest_by_consumer_version_order
371
+ reverse_order(:consumer_version_order).limit(1)
348
372
  end
349
373
 
350
374
  def order_by_consumer_name
@@ -355,6 +379,10 @@ module PactBroker
355
379
  order_append(:consumer_version_order, :revision_number)
356
380
  end
357
381
 
382
+ def earliest
383
+ order(:consumer_version_order).limit(1)
384
+ end
385
+
358
386
  def latest
359
387
  order(:consumer_version_order, :revision_number).last
360
388
  end