pact_broker 2.4.2 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +4 -2
  4. data/CHANGELOG.md +54 -0
  5. data/DEVELOPER_DOCUMENTATION.md +11 -7
  6. data/README.md +5 -1
  7. data/UPGRADING.md +18 -0
  8. data/db/migrations/19_make_pact_version_content_sha_not_nullable.rb +9 -1
  9. data/db/migrations/25_make_pv_pacticipants_mandatory.rb +8 -0
  10. data/db/migrations/38_create_triggered_webhooks_table.rb +19 -0
  11. data/db/migrations/39_add_triggered_webhooks_fk_to_execution.rb +24 -0
  12. data/db/migrations/40_create_latest_triggered_webhooks_view.rb +24 -0
  13. data/db/migrations/41_migrate_execution_data.rb +47 -0
  14. data/db/test/backwards_compatibility/.rspec +3 -0
  15. data/db/test/backwards_compatibility/Appraisals +49 -0
  16. data/db/test/backwards_compatibility/Gemfile +11 -0
  17. data/db/test/backwards_compatibility/Rakefile +55 -0
  18. data/db/test/backwards_compatibility/config.ru +18 -0
  19. data/db/test/backwards_compatibility/gemfiles/1.18.0.gemfile +14 -0
  20. data/db/test/backwards_compatibility/gemfiles/1.18.0.gemfile.lock +210 -0
  21. data/db/test/backwards_compatibility/gemfiles/2.0.0.gemfile +14 -0
  22. data/db/test/backwards_compatibility/gemfiles/2.0.0.gemfile.lock +208 -0
  23. data/db/test/backwards_compatibility/gemfiles/2.1.0.gemfile +14 -0
  24. data/db/test/backwards_compatibility/gemfiles/2.1.0.gemfile.lock +209 -0
  25. data/db/test/backwards_compatibility/gemfiles/2.2.0.gemfile +14 -0
  26. data/db/test/backwards_compatibility/gemfiles/2.2.0.gemfile.lock +197 -0
  27. data/db/test/backwards_compatibility/gemfiles/2.3.0.gemfile +13 -0
  28. data/db/test/backwards_compatibility/gemfiles/2.3.0.gemfile.lock +196 -0
  29. data/db/test/backwards_compatibility/gemfiles/2.4.2.gemfile +13 -0
  30. data/db/test/backwards_compatibility/gemfiles/2.4.2.gemfile.lock +196 -0
  31. data/db/test/backwards_compatibility/gemfiles/head.gemfile +13 -0
  32. data/db/test/backwards_compatibility/gemfiles/head.gemfile.lock +200 -0
  33. data/db/test/backwards_compatibility/spec/fixtures/foo-bar.json +22 -0
  34. data/db/test/backwards_compatibility/spec/publish_pact_spec.rb +72 -0
  35. data/db/test/backwards_compatibility/spec/spec_helper.rb +20 -0
  36. data/db/test/backwards_compatibility/spec/support/fixture_helpers.rb +12 -0
  37. data/db/test/backwards_compatibility/spec/support/request_helpers.rb +20 -0
  38. data/example/Gemfile +2 -2
  39. data/example/pact_broker_database.sqlite3 +0 -0
  40. data/lib/pact_broker/api/decorators/pact_collection_decorator.rb +1 -2
  41. data/lib/pact_broker/api/decorators/pact_decorator.rb +12 -10
  42. data/lib/pact_broker/api/decorators/pact_versions_decorator.rb +1 -2
  43. data/lib/pact_broker/api/decorators/pact_webhooks_status_decorator.rb +123 -0
  44. data/lib/pact_broker/api/decorators/versions_decorator.rb +17 -6
  45. data/lib/pact_broker/api/decorators/webhook_decorator.rb +8 -10
  46. data/lib/pact_broker/api/decorators/webhooks_decorator.rb +0 -1
  47. data/lib/pact_broker/api/pact_broker_urls.rb +13 -1
  48. data/lib/pact_broker/api/renderers/html_pact_renderer.rb +47 -3
  49. data/lib/pact_broker/api/resources/badge.rb +3 -3
  50. data/lib/pact_broker/api/resources/base_resource.rb +1 -1
  51. data/lib/pact_broker/api/resources/latest_pact.rb +5 -1
  52. data/lib/pact_broker/api/resources/pact.rb +5 -1
  53. data/lib/pact_broker/api/resources/pact_webhooks_status.rb +61 -0
  54. data/lib/pact_broker/api/resources/triggered_webhook_logs.rb +36 -0
  55. data/lib/pact_broker/api/resources/webhook.rb +31 -3
  56. data/lib/pact_broker/api/resources/webhook_execution.rb +12 -2
  57. data/lib/pact_broker/api.rb +3 -0
  58. data/lib/pact_broker/app.rb +11 -3
  59. data/lib/pact_broker/badges/service.rb +26 -5
  60. data/lib/pact_broker/configuration.rb +12 -5
  61. data/lib/pact_broker/constants.rb +1 -1
  62. data/lib/pact_broker/diagnostic/resources/heartbeat.rb +1 -2
  63. data/lib/pact_broker/doc/views/pact-webhooks.markdown +1 -1
  64. data/lib/pact_broker/doc/views/webhooks-webhooks.markdown +1 -1
  65. data/lib/pact_broker/doc/views/webhooks.markdown +1 -1
  66. data/lib/pact_broker/domain/relationship.rb +13 -4
  67. data/lib/pact_broker/domain/verification.rb +0 -4
  68. data/lib/pact_broker/domain/webhook.rb +2 -6
  69. data/lib/pact_broker/domain/webhook_execution_result.rb +1 -2
  70. data/lib/pact_broker/domain/webhook_request.rb +59 -40
  71. data/lib/pact_broker/pacticipants/service.rb +4 -3
  72. data/lib/pact_broker/pacts/repository.rb +8 -0
  73. data/lib/pact_broker/pacts/service.rb +2 -0
  74. data/lib/pact_broker/services.rb +1 -1
  75. data/lib/pact_broker/ui/view_models/relationship.rb +29 -2
  76. data/lib/pact_broker/ui/views/relationships/show.haml +7 -10
  77. data/lib/pact_broker/verifications/repository.rb +8 -1
  78. data/lib/pact_broker/version.rb +1 -1
  79. data/lib/pact_broker/webhooks/execution.rb +25 -4
  80. data/lib/pact_broker/webhooks/job.rb +55 -13
  81. data/lib/pact_broker/webhooks/latest_triggered_webhook.rb +9 -0
  82. data/lib/pact_broker/webhooks/redact_logs.rb +10 -0
  83. data/lib/pact_broker/webhooks/repository.rb +76 -8
  84. data/lib/pact_broker/webhooks/service.rb +48 -8
  85. data/lib/pact_broker/webhooks/status.rb +29 -0
  86. data/lib/pact_broker/webhooks/triggered_webhook.rb +96 -0
  87. data/lib/pact_broker/webhooks/webhook.rb +19 -8
  88. data/lib/rack/pact_broker/database_transaction.rb +9 -3
  89. data/pact_broker.gemspec +3 -3
  90. data/public/javascripts/pact.js +5 -0
  91. data/public/stylesheets/pact.css +14 -1
  92. data/public/stylesheets/relationships.css +0 -1
  93. data/script/db-spec.sh +7 -0
  94. data/script/seed.rb +13 -8
  95. data/spec/features/create_webhook_spec.rb +1 -1
  96. data/spec/features/delete_pact_spec.rb +5 -1
  97. data/spec/features/delete_webhook_spec.rb +2 -1
  98. data/spec/features/edit_webhook_spec.rb +61 -0
  99. data/spec/features/execute_webhook_spec.rb +73 -0
  100. data/spec/features/get_latest_pact_badge_spec.rb +1 -1
  101. data/spec/features/get_latest_tagged_pact_badge_spec.rb +1 -1
  102. data/spec/features/get_latest_untagged_pact_badge_spec.rb +1 -1
  103. data/spec/features/get_pact_spec.rb +1 -1
  104. data/spec/features/merge_pact_spec.rb +1 -1
  105. data/spec/features/publish_pact_spec.rb +1 -1
  106. data/spec/integration/app_spec.rb +1 -1
  107. data/spec/integration/endpoints/group.rb +1 -1
  108. data/spec/lib/pact_broker/api/decorators/latest_pact_decorator_spec.rb +2 -1
  109. data/spec/lib/pact_broker/api/decorators/pact_decorator_spec.rb +8 -6
  110. data/spec/lib/pact_broker/api/decorators/pact_webhooks_status_decorator_spec.rb +134 -0
  111. data/spec/lib/pact_broker/api/decorators/relationships_csv_decorator_spec.rb +1 -1
  112. data/spec/lib/pact_broker/api/decorators/representable_pact_spec.rb +1 -1
  113. data/spec/lib/pact_broker/api/renderers/html_pact_renderer_spec.rb +27 -1
  114. data/spec/lib/pact_broker/api/resources/badge_spec.rb +32 -15
  115. data/spec/lib/pact_broker/api/resources/base_resource_spec.rb +17 -0
  116. data/spec/lib/pact_broker/api/resources/latest_pact_spec.rb +5 -3
  117. data/spec/lib/pact_broker/api/resources/pact_spec.rb +9 -2
  118. data/spec/lib/pact_broker/api/resources/triggered_webhook_logs_spec.rb +28 -0
  119. data/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb +15 -5
  120. data/spec/lib/pact_broker/api/resources/webhook_spec.rb +43 -31
  121. data/spec/lib/pact_broker/app_spec.rb +12 -8
  122. data/spec/lib/pact_broker/badges/service_spec.rb +15 -1
  123. data/spec/lib/pact_broker/configuration_spec.rb +3 -2
  124. data/spec/lib/pact_broker/domain/relationship_spec.rb +24 -0
  125. data/spec/lib/pact_broker/domain/webhook_request_spec.rb +47 -31
  126. data/spec/lib/pact_broker/domain/webhook_spec.rb +4 -6
  127. data/spec/lib/pact_broker/pacticipants/service_spec.rb +16 -1
  128. data/spec/lib/pact_broker/pacts/repository_spec.rb +22 -1
  129. data/spec/lib/pact_broker/pacts/service_spec.rb +32 -1
  130. data/spec/lib/pact_broker/ui/view_models/relationship_spec.rb +44 -0
  131. data/spec/lib/pact_broker/verifications/repository_spec.rb +19 -0
  132. data/spec/lib/pact_broker/verifications/service_spec.rb +1 -1
  133. data/spec/lib/pact_broker/webhooks/job_spec.rb +80 -19
  134. data/spec/lib/pact_broker/webhooks/redact_logs_spec.rb +49 -0
  135. data/spec/lib/pact_broker/webhooks/repository_spec.rb +271 -21
  136. data/spec/lib/pact_broker/webhooks/service_spec.rb +70 -3
  137. data/spec/lib/pact_broker/webhooks/status_spec.rb +48 -0
  138. data/spec/lib/pact_broker/webhooks/triggered_webhook_spec.rb +40 -0
  139. data/spec/lib/rack/pact_broker/database_transaction_spec.rb +14 -4
  140. data/spec/migrations/23_pact_versions_spec.rb +8 -30
  141. data/spec/migrations/24_populate_pact_contents_spec.rb +3 -21
  142. data/spec/migrations/34_latest_tagged_pacts_spec.rb +1 -17
  143. data/spec/migrations/34_pact_revisions_spec.rb +7 -23
  144. data/spec/migrations/41_migrate_execution_data_spec.rb +109 -0
  145. data/spec/service_consumers/pact_helper.rb +5 -1
  146. data/spec/spec_helper.rb +15 -7
  147. data/spec/support/database_cleaner.rb +15 -2
  148. data/spec/support/migration_helpers.rb +16 -0
  149. data/spec/support/test_data_builder.rb +41 -9
  150. data/tasks/database.rb +7 -2
  151. data/tasks/db.rake +10 -0
  152. data/tasks/rspec.rake +1 -1
  153. data/vendor/hal-browser/browser.html +3 -2
  154. data/vendor/hal-browser/js/hal/resource.js +16 -2
  155. metadata +72 -13
  156. data/script/record_verification.sh +0 -4
@@ -3,6 +3,7 @@ require 'pact_broker/domain/webhook_execution_result'
3
3
  require 'pact_broker/logging'
4
4
  require 'pact_broker/messages'
5
5
  require 'net/http'
6
+ require 'pact_broker/webhooks/redact_logs'
6
7
 
7
8
  module PactBroker
8
9
 
@@ -46,60 +47,80 @@ module PactBroker
46
47
  password.nil? ? nil : "**********"
47
48
  end
48
49
 
49
- def execute
50
-
50
+ def execute options = {}
51
51
  logs = StringIO.new
52
52
  execution_logger = Logger.new(logs)
53
-
54
53
  begin
55
- req = http_request
56
- execution_logger.info "HTTP/1.1 #{method.upcase} #{url_with_credentials}"
54
+ execute_and_build_result(options, logs, execution_logger)
55
+ rescue StandardError => e
56
+ handle_error_and_build_result(e, options, logs, execution_logger)
57
+ end
58
+ end
57
59
 
58
- headers.each_pair do | name, value |
59
- execution_logger.info "#{name}: #{value}"
60
- req[name] = value
61
- end
60
+ private
62
61
 
63
- req.basic_auth(username, password) if username
62
+ def execute_and_build_result options, logs, execution_logger
63
+ req = build_request(execution_logger)
64
+ response = do_request(req)
65
+ log_response(response, execution_logger)
66
+ result = WebhookExecutionResult.new(response, logs.string)
67
+ log_completion_message(options, execution_logger, result.success?)
68
+ result
69
+ end
64
70
 
65
- unless body.nil?
66
- if String === body
67
- req.body = body
68
- else
69
- req.body = body.to_json
70
- end
71
- end
71
+ def handle_error_and_build_result e, options, logs, execution_logger
72
+ logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message} #{e.backtrace.join("\n")}"
73
+ execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
74
+ log_completion_message(options, execution_logger, false)
75
+ WebhookExecutionResult.new(nil, logs.string, e)
76
+ end
77
+
78
+ def build_request execution_logger
79
+ req = http_request
80
+ execution_logger.info "HTTP/1.1 #{method.upcase} #{url_with_credentials}"
72
81
 
73
- execution_logger.info req.body
82
+ headers.each_pair do | name, value |
83
+ execution_logger.info Webhooks::RedactLogs.call("#{name}: #{value}")
84
+ req[name] = value
85
+ end
74
86
 
75
- logger.info "Making webhook #{uuid} request #{to_s}"
87
+ req.basic_auth(username, password) if username
76
88
 
77
- response = Net::HTTP.start(uri.hostname, uri.port,
78
- :use_ssl => uri.scheme == 'https') do |http|
79
- http.request req
89
+ unless body.nil?
90
+ if String === body
91
+ req.body = body
92
+ else
93
+ req.body = body.to_json
80
94
  end
95
+ end
81
96
 
82
- execution_logger.info(" ")
83
- logger.info "Received response for webhook #{uuid} status=#{response.code}"
84
- execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
85
- response.each_header do | header |
86
- execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
87
- end
88
- logger.debug "body=#{response.body}"
89
- execution_logger.info response.body
90
- WebhookExecutionResult.new(response, logs.string)
97
+ execution_logger.info req.body
98
+ req
99
+ end
91
100
 
92
- rescue StandardError => e
93
- logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
94
- execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
95
- logger.error e.backtrace.join("\n")
96
- execution_logger.error e.backtrace.join("\n")
97
- WebhookExecutionResult.new(nil, logs.string, e)
101
+ def do_request req
102
+ logger.info "Making webhook #{uuid} request #{to_s}"
103
+ Net::HTTP.start(uri.hostname, uri.port,
104
+ :use_ssl => uri.scheme == 'https') do |http|
105
+ http.request req
98
106
  end
107
+ end
99
108
 
109
+ def log_response response, execution_logger
110
+ execution_logger.info(" ")
111
+ logger.info "Received response for webhook #{uuid} status=#{response.code}"
112
+ execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
113
+ response.each_header do | header |
114
+ execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
115
+ end
116
+ logger.debug "body=#{response.body}"
117
+ execution_logger.info response.body
100
118
  end
101
119
 
102
- private
120
+ def log_completion_message options, execution_logger, success
121
+ execution_logger.info(options[:success_log_message]) if options[:success_log_message] && success
122
+ execution_logger.info(options[:failure_log_message]) if options[:failure_log_message] && !success
123
+ end
103
124
 
104
125
  def to_s
105
126
  "#{method.upcase} #{url}, username=#{username}, password=#{display_password}, headers=#{headers}, body=#{body}"
@@ -119,7 +140,5 @@ module PactBroker
119
140
  u
120
141
  end
121
142
  end
122
-
123
143
  end
124
-
125
144
  end
@@ -58,12 +58,14 @@ module PactBroker
58
58
  end
59
59
  end
60
60
 
61
+ # This needs to move into a new service
61
62
  def self.find_relationships
62
63
  pact_repository.find_latest_pacts
63
64
  .collect do | pact|
64
65
  latest_verification = verification_service.find_latest_verification_for(pact.consumer, pact.provider)
65
66
  webhooks = webhook_service.find_by_consumer_and_provider pact.consumer, pact.provider
66
- PactBroker::Domain::Relationship.create pact.consumer, pact.provider, pact, latest_verification, webhooks
67
+ triggered_webhooks = webhook_service.find_latest_triggered_webhooks pact.consumer, pact.provider
68
+ PactBroker::Domain::Relationship.create pact.consumer, pact.provider, pact, latest_verification, webhooks, triggered_webhooks
67
69
  end
68
70
  end
69
71
 
@@ -83,7 +85,7 @@ module PactBroker
83
85
  version_ids = PactBroker::Domain::Version.where(pacticipant_id: pacticipant.id).select_for_subquery(:id) #stupid mysql doesn't allow subqueries
84
86
  select_pacticipant = "select id from pacticipants where name = '#{name}'"
85
87
  tag_repository.delete_by_version_id version_ids
86
- webhook_repository.delete_executions_by_pacticipant pacticipant
88
+ webhook_service.delete_all_webhhook_related_objects_by_pacticipant pacticipant
87
89
  pact_repository.delete_by_version_id version_ids
88
90
  connection.run("delete from pact_publications where provider_id = #{pacticipant.id}")
89
91
  connection.run("delete from verifications where pact_version_id IN (select id from pact_versions where provider_id = #{pacticipant.id})")
@@ -92,7 +94,6 @@ module PactBroker
92
94
  connection.run("delete from pact_versions where consumer_id = #{pacticipant.id}")
93
95
  connection.run("delete from versions where pacticipant_id = #{pacticipant.id}")
94
96
  version_repository.delete_by_id version_ids
95
- webhook_service.delete_by_pacticipant pacticipant
96
97
  connection.run("delete from pacticipants where id = #{pacticipant.id}")
97
98
  end
98
99
 
@@ -112,6 +112,14 @@ module PactBroker
112
112
  query.limit(1).collect(&:to_domain_with_content)[0]
113
113
  end
114
114
 
115
+ def find_all_revisions consumer_name, consumer_version, provider_name
116
+ AllPactPublications
117
+ .consumer(consumer_name)
118
+ .provider(provider_name)
119
+ .consumer_version_number(consumer_version)
120
+ .order(:consumer_version_order, :revision_number).collect(&:to_domain_with_content)
121
+ end
122
+
115
123
  def find_previous_pact pact
116
124
  LatestPactPublicationsByConsumerVersion
117
125
  .eager(:tags)
@@ -31,6 +31,8 @@ module PactBroker
31
31
 
32
32
  def delete params
33
33
  logger.info "Deleting pact version with params #{params}"
34
+ pacts = pact_repository.find_all_revisions(params[:consumer_name], params[:consumer_version_number], params[:provider_name])
35
+ webhook_service.delete_all_webhook_related_objects_by_pact_publication_ids(pacts.collect(&:id))
34
36
  pact_repository.delete(params)
35
37
  end
36
38
 
@@ -42,7 +42,7 @@ module PactBroker
42
42
  Verifications::Service
43
43
  end
44
44
 
45
- def badges_service
45
+ def badge_service
46
46
  require 'pact_broker/badges/service'
47
47
  Badges::Service
48
48
  end
@@ -37,8 +37,35 @@ module PactBroker
37
37
  @relationship.any_webhooks?
38
38
  end
39
39
 
40
- def webhooks_url
41
- url = PactBroker::Api::PactBrokerUrls.webhooks_for_pact_url @relationship.latest_pact.consumer, @relationship.latest_pact.provider, ''
40
+ def webhook_label
41
+ case @relationship.webhook_status
42
+ when :none then "Create"
43
+ when :success, :failure then webhook_last_execution_date
44
+ when :retrying then "Retrying"
45
+ when :not_run then "Not run"
46
+ end
47
+ end
48
+
49
+ def webhook_status
50
+ case @relationship.webhook_status
51
+ when :success then "success"
52
+ when :failure then "danger"
53
+ when :retrying then "warning"
54
+ else ""
55
+ end
56
+ end
57
+
58
+ def webhook_last_execution_date
59
+ PactBroker::DateHelper.distance_of_time_in_words(@relationship.last_webhook_execution_date, DateTime.now) + " ago"
60
+ end
61
+
62
+ def webhook_url
63
+ url = case @relationship.webhook_status
64
+ when :none
65
+ PactBroker::Api::PactBrokerUrls.webhooks_for_pact_url @relationship.latest_pact.consumer, @relationship.latest_pact.provider
66
+ else
67
+ PactBroker::Api::PactBrokerUrls.webhooks_status_url @relationship.latest_pact.consumer, @relationship.latest_pact.provider
68
+ end
42
69
  "/hal-browser/browser.html##{url}"
43
70
  end
44
71
 
@@ -26,11 +26,11 @@
26
26
  %span.glyphicon.glyphicon-sort.relationships-sort
27
27
  %th
28
28
  %th
29
- Latest pact published
29
+ Latest pact<br>published
30
30
  %th
31
- Webhooks
31
+ Webhook<br>status
32
32
  %th
33
- Last verified
33
+ Last<br>verified
34
34
  %tbody
35
35
 
36
36
  - relationships.each do | relationship |
@@ -48,13 +48,10 @@
48
48
  %td
49
49
  %td
50
50
  = relationship.publication_date_of_latest_pact
51
- %td
52
- - if relationship.any_webhooks?
53
- %a{:href => relationship.webhooks_url}
54
- Edit
55
- - else
56
- %a{:href => relationship.webhooks_url}
57
- Create
51
+ %td{class: relationship.webhook_status}
52
+ %a{:href => relationship.webhook_url}
53
+ = relationship.webhook_label
54
+
58
55
  %td{class: relationship.verification_status, title: relationship.verification_tooltip, "data-toggle": "tooltip", "data-placement": "left"}
59
56
  %div
60
57
  = relationship.last_verified_date
@@ -19,6 +19,7 @@ module PactBroker
19
19
 
20
20
  def find consumer_name, provider_name, pact_version_sha, verification_number
21
21
  PactBroker::Domain::Verification
22
+ .select_all_qualified
22
23
  .join(:all_pact_publications, pact_version_id: :pact_version_id)
23
24
  .consumer(consumer_name)
24
25
  .provider(provider_name)
@@ -30,6 +31,7 @@ module PactBroker
30
31
  # Use LatestPactPublicationsByConsumerVersion not AllPactPublcations because we don't
31
32
  # want verifications for shadowed revisions as it would be misleading.
32
33
  LatestVerificationsByConsumerVersion
34
+ .select_all_qualified
33
35
  .join(:latest_pact_publications_by_consumer_versions, pact_version_id: :pact_version_id)
34
36
  .consumer(consumer_name)
35
37
  .consumer_version_number(consumer_version_number)
@@ -39,6 +41,7 @@ module PactBroker
39
41
 
40
42
  def find_latest_verification_for consumer_name, provider_name, tag = nil
41
43
  query = LatestVerificationsByConsumerVersion
44
+ .select_all_qualified
42
45
  .join(:all_pact_publications, pact_version_id: :pact_version_id)
43
46
  .consumer(consumer_name)
44
47
  .provider(provider_name)
@@ -47,7 +50,11 @@ module PactBroker
47
50
  elsif tag
48
51
  query = query.tag(tag)
49
52
  end
50
- query.latest.single_record
53
+ query.reverse_order(
54
+ Sequel[:all_pact_publications][:consumer_version_order],
55
+ Sequel[:all_pact_publications][:revision_number],
56
+ Sequel[LatestVerificationsByConsumerVersion.table_name][:number]
57
+ ).limit(1).single_record
51
58
  end
52
59
 
53
60
  def pact_version_id_for pact
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '2.4.2'
2
+ VERSION = '2.5.0'
3
3
  end
@@ -1,14 +1,35 @@
1
1
  require 'sequel'
2
+ require 'pact_broker/db'
3
+ require 'pact_broker/repositories/helpers'
4
+
2
5
 
3
6
  module PactBroker
4
7
  module Webhooks
5
- class Execution < Sequel::Model(:webhook_executions)
8
+ class Execution < Sequel::Model(
9
+ PactBroker::DB.connection[:webhook_executions].select(
10
+ Sequel[:webhook_executions][:id],
11
+ :triggered_webhook_id,
12
+ :success,
13
+ :logs,
14
+ Sequel[:webhook_executions][:created_at])
15
+ )
16
+
17
+ dataset_module do
18
+ include PactBroker::Repositories::Helpers
19
+ end
20
+
21
+ associate(:many_to_one, :triggered_webhook, :class => "PactBroker::Webhooks::TriggeredWebhook", :key => :triggered_webhook_id, :primary_key => :id)
6
22
 
7
- associate(:many_to_one, :webhook, :class => "PactBroker::Webhooks::Webhook", :key => :webhook_id, :primary_key => :id)
8
- associate(:many_to_one, :pact_publication, :class => "PactBroker::Pacts::PactPublication", :key => :pact_publication_id, :primary_key => :id)
23
+ def <=> other
24
+ comp = created_date <=> other.created_date
25
+ comp = id <=> other.id if comp == 0
26
+ comp
27
+ end
28
+ end
29
+
30
+ class DeprecatedExecution < Sequel::Model(:webhook_executions)
9
31
  associate(:many_to_one, :provider, :class => "PactBroker::Domain::Pacticipant", :key => :provider_id, :primary_key => :id)
10
32
  associate(:many_to_one, :consumer, :class => "PactBroker::Domain::Pacticipant", :key => :consumer_id, :primary_key => :id)
11
-
12
33
  end
13
34
 
14
35
  Execution.plugin :timestamps
@@ -6,17 +6,20 @@ module PactBroker
6
6
  module Webhooks
7
7
  class Job
8
8
 
9
- BACKOFF_TIMES = [10, 60, 120, 300, 600, 1200] #10 sec, 1 min, 2 min, 5 min, 10 min, 20 min => 38 minutes
10
-
11
9
  include SuckerPunch::Job
12
10
  include PactBroker::Logging
13
11
 
14
12
  def perform data
15
- @webhook = data[:webhook]
13
+ @data = data
14
+ @triggered_webhook = data[:triggered_webhook]
16
15
  @error_count = data[:error_count] || 0
17
16
  begin
18
- webhook_execution_result = PactBroker::Webhooks::Service.execute_webhook_now webhook
19
- reschedule_job unless webhook_execution_result.success?
17
+ webhook_execution_result = PactBroker::Webhooks::Service.execute_triggered_webhook_now triggered_webhook, execution_options
18
+ if webhook_execution_result.success?
19
+ handle_success
20
+ else
21
+ handle_failure
22
+ end
20
23
  rescue StandardError => e
21
24
  handle_error e
22
25
  end
@@ -24,23 +27,62 @@ module PactBroker
24
27
 
25
28
  private
26
29
 
27
- attr_reader :webhook, :error_count
30
+ attr_reader :triggered_webhook, :error_count
31
+
32
+ def execution_options
33
+ {
34
+ success_log_message: "Successfully executed webhook",
35
+ failure_log_message: failure_log_message
36
+ }
37
+ end
38
+
39
+ def failure_log_message
40
+ if reschedule_job?
41
+ "Retrying webhook in #{backoff_time} seconds"
42
+ else
43
+ "Webhook execution failed after #{retry_schedule.size + 1} attempts"
44
+ end
45
+ end
28
46
 
29
47
  def handle_error e
30
48
  log_error e
31
- reschedule_job
49
+ handle_failure
32
50
  end
33
51
 
34
- def reschedule_job
35
- case error_count
36
- when 0...BACKOFF_TIMES.size
37
- logger.debug "Re-enqeuing job for webhook #{webhook.uuid} to run in #{BACKOFF_TIMES[error_count]} seconds"
38
- Job.perform_in(BACKOFF_TIMES[error_count], {webhook: webhook, error_count: error_count+1})
52
+ def handle_success
53
+ update_triggered_webhook_status TriggeredWebhook::STATUS_SUCCESS
54
+ end
55
+
56
+ def handle_failure
57
+ if reschedule_job?
58
+ reschedule_job
59
+ update_triggered_webhook_status TriggeredWebhook::STATUS_RETRYING
39
60
  else
40
- logger.error "Failed to execute webhook #{webhook.uuid} after #{BACKOFF_TIMES.size} times."
61
+ logger.error "Failed to execute webhook #{triggered_webhook.webhook_uuid} after #{retry_schedule.size + 1} attempts."
62
+ update_triggered_webhook_status TriggeredWebhook::STATUS_FAILURE
41
63
  end
42
64
  end
43
65
 
66
+ def reschedule_job?
67
+ error_count < retry_schedule.size
68
+ end
69
+
70
+ def reschedule_job
71
+ logger.debug "Re-enqeuing job for webhook #{triggered_webhook.webhook_uuid} to run in #{backoff_time} seconds"
72
+ Job.perform_in(backoff_time, @data.merge(error_count: error_count+1))
73
+ end
74
+
75
+ def update_triggered_webhook_status status
76
+ PactBroker::Webhooks::Service.update_triggered_webhook_status triggered_webhook, status
77
+ end
78
+
79
+ def backoff_time
80
+ retry_schedule[error_count]
81
+ end
82
+
83
+ def retry_schedule
84
+ PactBroker.configuration.webhook_retry_schedule
85
+ end
44
86
  end
45
87
  end
46
88
  end
@@ -0,0 +1,9 @@
1
+ require 'pact_broker/webhooks/triggered_webhook'
2
+
3
+ module PactBroker
4
+ module Webhooks
5
+ class LatestTriggeredWebhook < TriggeredWebhook
6
+ set_dataset(:latest_triggered_webhooks)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module PactBroker
2
+ module Webhooks
3
+ class RedactLogs
4
+ def self.call logs
5
+ logs.gsub(/(Authorization: )(.*)/i,'\1[REDACTED]')
6
+ .gsub(/(Token: )(.*)/i,'\1[REDACTED]')
7
+ end
8
+ end
9
+ end
10
+ end
@@ -3,6 +3,8 @@ require 'pact_broker/domain/webhook'
3
3
  require 'pact_broker/domain/pacticipant'
4
4
  require 'pact_broker/db'
5
5
  require 'pact_broker/webhooks/webhook'
6
+ require 'pact_broker/webhooks/triggered_webhook'
7
+ require 'pact_broker/webhooks/latest_triggered_webhook'
6
8
  require 'pact_broker/webhooks/execution'
7
9
 
8
10
  module PactBroker
@@ -26,6 +28,16 @@ module PactBroker
26
28
  Webhook.where(uuid: uuid).limit(1).collect(&:to_domain)[0]
27
29
  end
28
30
 
31
+ def update_by_uuid uuid, webhook
32
+ existing_webhook = Webhook.find(uuid: uuid)
33
+ existing_webhook.update_from_domain(webhook).save
34
+ existing_webhook.headers.collect(&:delete)
35
+ webhook.request.headers.each_pair do | name, value |
36
+ existing_webhook.add_header PactBroker::Webhooks::WebhookHeader.from_domain(name, value, existing_webhook.id)
37
+ end
38
+ find_by_uuid uuid
39
+ end
40
+
29
41
  def delete_by_uuid uuid
30
42
  Webhook.where(uuid: uuid).destroy
31
43
  end
@@ -43,23 +55,79 @@ module PactBroker
43
55
  Webhook.where(consumer_id: consumer.id, provider_id: provider.id).collect(&:to_domain)
44
56
  end
45
57
 
46
- def create_execution webhook, webhook_execution_result
58
+ def find_by_consumer_and_provider_existing_at consumer, provider, date_time
59
+ Webhook.where(consumer_id: consumer.id, provider_id: provider.id)
60
+ .where(Sequel.lit("created_at < ?", date_time))
61
+ .collect(&:to_domain)
62
+ end
63
+
64
+ def create_triggered_webhook trigger_uuid, webhook, pact, trigger_type
47
65
  db_webhook = Webhook.where(uuid: webhook.uuid).single_record
48
- execution = Execution.create(
66
+ TriggeredWebhook.create(
67
+ status: TriggeredWebhook::STATUS_NOT_RUN,
68
+ pact_publication_id: pact.id,
49
69
  webhook: db_webhook,
70
+ webhook_uuid: db_webhook.uuid,
71
+ trigger_uuid: trigger_uuid,
72
+ trigger_type: trigger_type,
50
73
  consumer: db_webhook.consumer,
51
- provider: db_webhook.provider,
74
+ provider: db_webhook.provider
75
+ )
76
+ end
77
+
78
+ def update_triggered_webhook_status triggered_webhook, status
79
+ triggered_webhook.update(status: status)
80
+ end
81
+
82
+ def create_execution triggered_webhook, webhook_execution_result
83
+ Execution.create(
84
+ triggered_webhook: triggered_webhook,
52
85
  success: webhook_execution_result.success?,
53
86
  logs: webhook_execution_result.logs)
54
87
  end
55
88
 
56
- def delete_executions_by_pacticipant pacticipant
57
- Execution.where(consumer: pacticipant).delete
58
- Execution.where(provider: pacticipant).delete
89
+ def delete_triggered_webhooks_by_pacticipant pacticipant
90
+ TriggeredWebhook.where(consumer: pacticipant).delete
91
+ TriggeredWebhook.where(provider: pacticipant).delete
92
+ end
93
+
94
+ def delete_executions_by_pacticipant pacticipants
95
+ # TODO this relationship no longer exists, deprecate in next version
96
+ DeprecatedExecution.where(consumer: pacticipants).delete
97
+ DeprecatedExecution.where(provider: pacticipants).delete
98
+ execution_ids = Execution
99
+ .join(:triggered_webhooks, {id: :triggered_webhook_id})
100
+ .where(Sequel.or(
101
+ Sequel[:triggered_webhooks][:consumer_id] => [*pacticipants].collect(&:id),
102
+ Sequel[:triggered_webhooks][:provider_id] => [*pacticipants].collect(&:id),
103
+ )).all.collect(&:id)
104
+ Execution.where(id: execution_ids).delete
105
+ end
106
+
107
+ def unlink_triggered_webhooks_by_webhook_uuid uuid
108
+ TriggeredWebhook.where(webhook: Webhook.where(uuid: uuid)).update(webhook_id: nil)
109
+ DeprecatedExecution.where(webhook_id: Webhook.where(uuid: uuid).select(:id)).update(webhook_id: nil)
110
+ end
111
+
112
+ def delete_triggered_webhooks_by_pact_publication_ids pact_publication_ids
113
+ triggered_webhook_ids = TriggeredWebhook.where(pact_publication_id: pact_publication_ids).select_for_subquery(:id)
114
+ Execution.where(triggered_webhook_id: triggered_webhook_ids).delete
115
+ TriggeredWebhook.where(id: triggered_webhook_ids).delete
116
+ DeprecatedExecution.where(pact_publication_id: pact_publication_ids).delete
117
+ end
118
+
119
+ def find_latest_triggered_webhooks consumer, provider
120
+ LatestTriggeredWebhook
121
+ .where(consumer: consumer, provider: provider)
122
+ .order(:id)
123
+ .all
124
+ .group_by{|w| [w.consumer_id, w.provider_id, w.webhook_uuid]}
125
+ .values
126
+ .collect(&:last)
59
127
  end
60
128
 
61
- def unlink_executions_by_webhook_uuid uuid
62
- Execution.where(webhook: Webhook.where(uuid: uuid)).update(webhook_id: nil)
129
+ def fail_retrying_triggered_webhooks
130
+ TriggeredWebhook.retrying.update(status: TriggeredWebhook::STATUS_FAILURE)
63
131
  end
64
132
  end
65
133
  end