pact_broker 2.85.1 → 2.86.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release_gem.yml +1 -1
  3. data/.github/workflows/trigger_pact_docs_update.yml +22 -0
  4. data/CHANGELOG.md +10 -0
  5. data/DEVELOPER_DOCUMENTATION.md +0 -2
  6. data/db/migrations/20210914_add_labels_to_webhooks.rb +14 -0
  7. data/db/migrations/20210915_add_verified_by_to_verification.rb +6 -0
  8. data/docker-compose-ci-mysql.yml +1 -0
  9. data/docs/CONFIGURATION.md +255 -66
  10. data/docs/configuration.yml +166 -101
  11. data/lib/db.rb +0 -1
  12. data/lib/pact_broker/api/contracts/webhook_contract.rb +24 -2
  13. data/lib/pact_broker/api/decorators/verification_decorator.rb +4 -0
  14. data/lib/pact_broker/api/decorators/webhook_decorator.rb +27 -4
  15. data/lib/pact_broker/api/pact_broker_urls.rb +4 -0
  16. data/lib/pact_broker/api/resources/all_webhooks.rb +2 -2
  17. data/lib/pact_broker/config/runtime_configuration.rb +4 -0
  18. data/lib/pact_broker/config/runtime_configuration_database_methods.rb +1 -1
  19. data/lib/pact_broker/db/clean.rb +1 -2
  20. data/lib/pact_broker/doc/views/pacticipant/label.markdown +12 -0
  21. data/lib/pact_broker/doc/views/webhooks.markdown +17 -0
  22. data/lib/pact_broker/domain/pacticipant.rb +4 -0
  23. data/lib/pact_broker/domain/verification.rb +16 -4
  24. data/lib/pact_broker/domain/webhook.rb +5 -5
  25. data/lib/pact_broker/domain/webhook_pacticipant.rb +6 -0
  26. data/lib/pact_broker/locale/en.yml +1 -0
  27. data/lib/pact_broker/matrix/head_row.rb +1 -1
  28. data/lib/pact_broker/matrix/quick_row.rb +0 -1
  29. data/lib/pact_broker/matrix/repository.rb +0 -1
  30. data/lib/pact_broker/matrix/row.rb +2 -2
  31. data/lib/pact_broker/pacts/latest_pact_publication_id_for_consumer_version.rb +0 -1
  32. data/lib/pact_broker/pacts/pact_publication.rb +7 -0
  33. data/lib/pact_broker/pacts/pact_publication_clean_selector_dataset_module.rb +19 -0
  34. data/lib/pact_broker/pacts/pact_publication_dataset_module.rb +30 -2
  35. data/lib/pact_broker/pacts/pact_version.rb +24 -1
  36. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +2 -2
  37. data/lib/pact_broker/pacts/repository.rb +50 -47
  38. data/lib/pact_broker/test/http_test_data_builder.rb +37 -10
  39. data/lib/pact_broker/test/test_data_builder.rb +22 -5
  40. data/lib/pact_broker/verifications/repository.rb +5 -2
  41. data/lib/pact_broker/verifications/service.rb +4 -1
  42. data/lib/pact_broker/version.rb +1 -1
  43. data/lib/pact_broker/webhooks/event_listener.rb +4 -2
  44. data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +1 -0
  45. data/lib/pact_broker/webhooks/repository.rb +10 -4
  46. data/lib/pact_broker/webhooks/webhook.rb +66 -8
  47. data/script/data/verify-pact-for-multiple-selectors.rb +30 -0
  48. data/script/docs/generate-configuration-docs.rb +24 -3
  49. data/script/generate-erd +55 -0
  50. data/spec/features/create_webhook_spec.rb +55 -10
  51. data/spec/features/get_pact_spec.rb +2 -3
  52. data/spec/fixtures/verification.json +4 -0
  53. data/spec/integration/webhooks/pact_publication_spec.rb +51 -0
  54. data/spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb +50 -0
  55. data/spec/lib/pact_broker/api/decorators/verification_decorator_spec.rb +8 -1
  56. data/spec/lib/pact_broker/api/decorators/verification_summary_decorator_spec.rb +3 -1
  57. data/spec/lib/pact_broker/api/decorators/webhook_decorator_spec.rb +4 -4
  58. data/spec/lib/pact_broker/config/runtime_configuration_documentation_spec.rb +30 -0
  59. data/spec/lib/pact_broker/deployments/environment_service_spec.rb +1 -1
  60. data/spec/lib/pact_broker/matrix/head_row_spec.rb +9 -5
  61. data/spec/lib/pact_broker/pacts/{latest_tagged_pact_publications_spec.rb → pact_publication_clean_selector_dataset_module_spec.rb} +7 -9
  62. data/spec/lib/pact_broker/pacts/pact_version_spec.rb +32 -0
  63. data/spec/lib/pact_broker/pacts/repository_spec.rb +33 -0
  64. data/spec/lib/pact_broker/verifications/service_spec.rb +16 -2
  65. data/spec/lib/pact_broker/webhooks/repository_spec.rb +158 -15
  66. data/spec/lib/pact_broker/webhooks/webhook_spec.rb +8 -5
  67. metadata +16 -11
  68. data/lib/pact_broker/pacts/all_pact_publications.rb +0 -158
  69. data/lib/pact_broker/pacts/latest_pact_publications.rb +0 -48
  70. data/lib/pact_broker/pacts/latest_pact_publications_by_consumer_version.rb +0 -26
  71. data/lib/pact_broker/pacts/latest_tagged_pact_publications.rb +0 -45
  72. data/lib/pact_broker/verifications/latest_verification_for_pact_version.rb +0 -39
  73. data/spec/lib/pact_broker/verifications/latest_verification_for_pact_version_spec.rb +0 -18
@@ -15,6 +15,10 @@ module PactBroker
15
15
  property :execution_date, as: :verificationDate
16
16
  property :build_url, as: :buildUrl
17
17
  property :test_results, as: :testResults
18
+ nested :verifiedBy do
19
+ property :verified_by_implementation, as: :implementation
20
+ property :verified_by_version, as: :version
21
+ end
18
22
 
19
23
  link :self do | options |
20
24
  {
@@ -1,4 +1,5 @@
1
1
  require_relative "base_decorator"
2
+ require "pact_broker/domain/webhook_pacticipant"
2
3
  require "pact_broker/api/decorators/webhook_request_template_decorator"
3
4
  require "pact_broker/api/decorators/timestamps"
4
5
  require "pact_broker/webhooks/webhook_request_template"
@@ -19,12 +20,14 @@ module PactBroker
19
20
 
20
21
  property :description, getter: lambda { |context| context[:represented].display_description }
21
22
 
22
- property :consumer, :class => PactBroker::Domain::Pacticipant, default: nil do
23
+ property :consumer, class: Domain::WebhookPacticipant, default: nil do
23
24
  property :name
25
+ property :label
24
26
  end
25
27
 
26
- property :provider, :class => PactBroker::Domain::Pacticipant, default: nil do
28
+ property :provider, class: Domain::WebhookPacticipant, default: nil do
27
29
  property :name
30
+ property :label
28
31
  end
29
32
 
30
33
  property :enabled, default: true
@@ -50,7 +53,7 @@ module PactBroker
50
53
  end
51
54
 
52
55
  link :'pb:consumer' do | options |
53
- if represented.consumer
56
+ if represented.consumer&.name
54
57
  {
55
58
  title: "Consumer",
56
59
  name: represented.consumer.name,
@@ -59,8 +62,18 @@ module PactBroker
59
62
  end
60
63
  end
61
64
 
65
+ link :'pb:consumers' do | options |
66
+ if represented.consumer&.label
67
+ {
68
+ title: "Consumers by label",
69
+ name: represented.consumer.label,
70
+ href: pacticipants_with_label_url(options.fetch(:base_url), represented.consumer.label)
71
+ }
72
+ end
73
+ end
74
+
62
75
  link :'pb:provider' do | options |
63
- if represented.provider
76
+ if represented.provider&.name
64
77
  {
65
78
  title: "Provider",
66
79
  name: represented.provider.name,
@@ -69,6 +82,16 @@ module PactBroker
69
82
  end
70
83
  end
71
84
 
85
+ link :'pb:providers' do | options |
86
+ if represented.provider&.label
87
+ {
88
+ title: "Providers by label",
89
+ name: represented.provider.label,
90
+ href: pacticipants_with_label_url(options.fetch(:base_url), represented.provider.label)
91
+ }
92
+ end
93
+ end
94
+
72
95
  link :'pb:pact-webhooks' do | options |
73
96
  if represented.consumer && represented.provider
74
97
  {
@@ -22,6 +22,10 @@ module PactBroker
22
22
  "#{pacticipants_url(base_url)}/#{url_encode(pacticipant.name)}"
23
23
  end
24
24
 
25
+ def pacticipants_with_label_url base_url, label_name
26
+ "#{pacticipants_url(base_url)}/label/#{url_encode(label_name)}"
27
+ end
28
+
25
29
  def pacticipant_url_from_params params, base_url = ""
26
30
  [
27
31
  base_url,
@@ -70,11 +70,11 @@ module PactBroker
70
70
  end
71
71
 
72
72
  def consumer
73
- webhook.consumer ? pacticipant_service.find_pacticipant_by_name(webhook.consumer.name) : nil
73
+ webhook.consumer&.name ? pacticipant_service.find_pacticipant_by_name(webhook.consumer.name) : nil
74
74
  end
75
75
 
76
76
  def provider
77
- webhook.provider ? pacticipant_service.find_pacticipant_by_name(webhook.provider.name) : nil
77
+ webhook.provider&.name ? pacticipant_service.find_pacticipant_by_name(webhook.provider.name) : nil
78
78
  end
79
79
 
80
80
  def webhooks
@@ -166,6 +166,10 @@ module PactBroker
166
166
  super(value_to_string_array(webhook_host_whitelist, "webhook_host_whitelist"))
167
167
  end
168
168
 
169
+ def main_branch_candidates= main_branch_candidates
170
+ super(value_to_string_array(main_branch_candidates, "main_branch_candidates"))
171
+ end
172
+
169
173
  def features= features
170
174
  super(value_to_string_array(features, "features").collect(&:downcase))
171
175
  end
@@ -27,7 +27,7 @@ module PactBroker
27
27
  validate_database_connection_config: true,
28
28
  database_statement_timeout: 15,
29
29
  metrics_sql_statement_timeout: 30,
30
- database_connection_validation_timeout: -1
30
+ database_connection_validation_timeout: nil
31
31
  )
32
32
 
33
33
  def database_configuration
@@ -1,6 +1,5 @@
1
1
  require "sequel"
2
2
  require "pact_broker/project_root"
3
- require "pact_broker/pacts/latest_tagged_pact_publications"
4
3
  require "pact_broker/logging"
5
4
  require "pact_broker/db/clean/selector"
6
5
 
@@ -50,7 +49,7 @@ module PactBroker
50
49
 
51
50
  def latest_tagged_pact_publications_ids_to_keep
52
51
  @latest_tagged_pact_publications_ids_to_keep ||= resolve_ids(keep.select(&:tag).select(&:latest).collect do | selector |
53
- PactBroker::Pacts::LatestTaggedPactPublications.select(:id).for_selector(selector)
52
+ PactBroker::Pacts::PactPublication.select(:id).latest_by_consumer_tag_for_clean_selector(selector)
54
53
  end.reduce(&:union) || [])
55
54
  end
56
55
 
@@ -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.
@@ -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.
@@ -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
@@ -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 && "labeled '#{consumer.label}'"))
67
67
  end
68
68
 
69
69
  def provider_name
70
- provider && provider.name
70
+ provider && (provider.name || (provider.label && "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
@@ -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:
@@ -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
@@ -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
@@ -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
@@ -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