Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: trigger one webhook for each pact publication that the verified … #378

Merged
merged 1 commit into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/pact_broker/api/resources/verifications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def create_path
end

def from_json
verification = verification_service.create(next_verification_number, verification_params, pact, metadata, webhook_options)
verification = verification_service.create(next_verification_number, verification_params, pact, event_context, webhook_options)
response.body = decorator_for(verification).to_json(decorator_options)
true
end
Expand Down Expand Up @@ -81,11 +81,14 @@ def wip?
metadata[:wip] == 'true'
end

def event_context
metadata
end

def webhook_options
{
database_connector: database_connector,
webhook_execution_configuration: webhook_execution_configuration
.with_webhook_context(metadata)
}
end

Expand Down
2 changes: 1 addition & 1 deletion lib/pact_broker/api/resources/webhook_execution.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def allowed_methods
end

def process_post
webhook_execution_result = webhook_service.test_execution(webhook, webhook_execution_configuration)
webhook_execution_result = webhook_service.test_execution(webhook, webhook_execution_configuration.webhook_context, webhook_execution_configuration)
response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
response.body = post_response_body(webhook_execution_result)
true
Expand Down
16 changes: 8 additions & 8 deletions lib/pact_broker/domain/webhook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ def request_description
request && request.description
end

def execute pact, verification, options
logger.info "Executing #{self} webhook_context=#{options.fetch(:webhook_context)}"
webhook_request = request.build(template_parameters(pact, verification, options))
def execute pact, verification, event_context, options
logger.info "Executing #{self} event_context=#{event_context}"
webhook_request = request.build(template_parameters(pact, verification, event_context, options))
http_response, error = execute_request(webhook_request)

PactBroker::Webhooks::WebhookExecutionResult.new(
webhook_request.http_request,
http_response,
generate_logs(webhook_request, http_response, error, options[:webhook_context], options.fetch(:logging_options)),
generate_logs(webhook_request, http_response, error, event_context, options.fetch(:logging_options)),
error
)
end
Expand Down Expand Up @@ -106,18 +106,18 @@ def execute_request(webhook_request)
return http_response, error
end

def template_parameters(pact, verification, options)
PactBroker::Webhooks::PactAndVerificationParameters.new(pact, verification, options.fetch(:webhook_context)).to_hash
def template_parameters(pact, verification, event_context, options)
PactBroker::Webhooks::PactAndVerificationParameters.new(pact, verification, event_context).to_hash
end

def generate_logs(webhook_request, http_response, error, webhook_context, logging_options)
def generate_logs(webhook_request, http_response, error, event_context, logging_options)
webhook_request_logger = PactBroker::Webhooks::WebhookRequestLogger.new(logging_options)
webhook_request_logger.log(
uuid,
webhook_request,
http_response,
error,
webhook_context
event_context
)
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/pact_broker/hash_refinements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def slice(*keys)
keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
end

def without(*keys)
reject { |k,_| keys.include?(k) }
end

def camelcase_keys
camelcase_keys_private(self)
end
Expand Down
13 changes: 10 additions & 3 deletions lib/pact_broker/webhooks/pact_and_verification_parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PactAndVerificationParameters
BITBUCKET_VERIFICATION_STATUS = 'pactbroker.bitbucketVerificationStatus'
CONSUMER_LABELS = 'pactbroker.consumerLabels'
PROVIDER_LABELS = 'pactbroker.providerLabels'
EVENT_NAME = 'pactbroker.eventName'

ALL = [
CONSUMER_NAME,
Expand All @@ -26,7 +27,8 @@ class PactAndVerificationParameters
GITHUB_VERIFICATION_STATUS,
BITBUCKET_VERIFICATION_STATUS,
CONSUMER_LABELS,
PROVIDER_LABELS
PROVIDER_LABELS,
EVENT_NAME
]

def initialize(pact, trigger_verification, webhook_context)
Expand All @@ -49,7 +51,8 @@ def to_hash
GITHUB_VERIFICATION_STATUS => github_verification_status,
BITBUCKET_VERIFICATION_STATUS => bitbucket_verification_status,
CONSUMER_LABELS => pacticipant_labels(pact && pact.consumer),
PROVIDER_LABELS => pacticipant_labels(pact && pact.provider)
PROVIDER_LABELS => pacticipant_labels(pact && pact.provider),
EVENT_NAME => event_name
}
end

Expand Down Expand Up @@ -116,6 +119,10 @@ def provider_version_tags
def pacticipant_labels pacticipant
pacticipant && pacticipant.labels ? pacticipant.labels.collect(&:name).join(", ") : ""
end

def event_name
webhook_context.fetch(:event_name)
end
end
end
end
end
13 changes: 7 additions & 6 deletions lib/pact_broker/webhooks/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def self.find_all
webhook_repository.find_all
end

def self.test_execution webhook, execution_configuration
def self.test_execution webhook, event_context, execution_configuration
merged_options = execution_configuration.with_failure_log_message("Webhook execution failed").to_hash

verification = nil
Expand All @@ -96,7 +96,7 @@ def self.test_execution webhook, execution_configuration
end

pact = pact_service.search_for_latest_pact(consumer_name: webhook.consumer_name, provider_name: webhook.provider_name) || PactBroker::Pacts::PlaceholderPact.new
webhook.execute(pact, verification, merged_options)
webhook.execute(pact, verification, event_context.merge(event_name: "test"), merged_options)
end

def self.execute_triggered_webhook_now triggered_webhook, webhook_execution_configuration_hash
Expand Down Expand Up @@ -126,18 +126,19 @@ def self.trigger_webhooks pact, verification, event_name, event_context, options

if webhooks.any?
webhook_execution_configuration = options.fetch(:webhook_execution_configuration).with_webhook_context(event_name: event_name)
run_later(webhooks, pact, verification, event_name, options.merge(webhook_execution_configuration: webhook_execution_configuration))
# bit messy to merge in base_url here, but easier than a big refactor
base_url = options.fetch(:webhook_execution_configuration).webhook_context.fetch(:base_url)
run_later(webhooks, pact, verification, event_name, event_context.merge(event_name: event_name, base_url: base_url), options.merge(webhook_execution_configuration: webhook_execution_configuration))
else
logger.info "No enabled webhooks found for consumer \"#{pact.consumer.name}\" and provider \"#{pact.provider.name}\" and event #{event_name}"
end
end

def self.run_later webhooks, pact, verification, event_name, options
def self.run_later webhooks, pact, verification, event_name, event_context, options
trigger_uuid = next_uuid
webhooks.each do | webhook |
begin
webhook_context = options.fetch(:webhook_execution_configuration).webhook_context
triggered_webhook = webhook_repository.create_triggered_webhook(trigger_uuid, webhook, pact, verification, RESOURCE_CREATION, event_name, webhook_context)
triggered_webhook = webhook_repository.create_triggered_webhook(trigger_uuid, webhook, pact, verification, RESOURCE_CREATION, event_name, event_context)
logger.info "Scheduling job for webhook with uuid #{webhook.uuid}"
logger.debug "Schedule webhook with options #{options}"
job_data = { triggered_webhook: triggered_webhook }.deep_merge(options)
Expand Down
68 changes: 49 additions & 19 deletions lib/pact_broker/webhooks/trigger_service.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'pact_broker/services'
require 'pact_broker/hash_refinements'

module PactBroker
module Webhooks
Expand All @@ -8,6 +9,7 @@ module TriggerService
extend PactBroker::Repositories
extend PactBroker::Services
include PactBroker::Logging
using PactBroker::HashRefinements

def trigger_webhooks_for_new_pact(pact, event_context, webhook_options)
webhook_service.trigger_webhooks pact, nil, PactBroker::Webhooks::WebhookEvent::CONTRACT_PUBLISHED, event_context, webhook_options
Expand All @@ -30,31 +32,33 @@ def trigger_webhooks_for_updated_pact(existing_pact, updated_pact, event_context
end

def trigger_webhooks_for_verification_results_publication(pact, verification, event_context, webhook_options)
if verification.success
webhook_service.trigger_webhooks(
pact,
verification,
PactBroker::Webhooks::WebhookEvent::VERIFICATION_SUCCEEDED,
event_context,
webhook_options
)
else
expand_events(event_context).each do | reconstituted_event_context |
if verification.success
webhook_service.trigger_webhooks(
pact,
verification,
PactBroker::Webhooks::WebhookEvent::VERIFICATION_SUCCEEDED,
reconstituted_event_context,
webhook_options
)
else
webhook_service.trigger_webhooks(
pact,
verification,
PactBroker::Webhooks::WebhookEvent::VERIFICATION_FAILED,
reconstituted_event_context,
webhook_options
)
end

webhook_service.trigger_webhooks(
pact,
verification,
PactBroker::Webhooks::WebhookEvent::VERIFICATION_FAILED,
event_context,
PactBroker::Webhooks::WebhookEvent::VERIFICATION_PUBLISHED,
reconstituted_event_context,
webhook_options
)
end

webhook_service.trigger_webhooks(
pact,
verification,
PactBroker::Webhooks::WebhookEvent::VERIFICATION_PUBLISHED,
event_context,
webhook_options
)
end

private
Expand All @@ -71,6 +75,32 @@ def sha_changed_or_no_previous_version?(previous_pact, new_pact)
previous_pact.nil? || new_pact.pact_version_sha != previous_pact.pact_version_sha
end

def merge_consumer_version_selectors(consumer_version_number, selectors, event_context)
event_context.merge(
consumer_version_number: consumer_version_number,
consumer_version_tags: selectors.collect{ | selector | selector[:tag] }.compact.uniq
)
end

# Now that we de-duplicate the pact contents when verifying though the 'pacts for verification' API,
# we no longer get a webhook triggered for the verification results publication of each indiviual
# consumer version tag, meaning that only the most recent commit will get the updated verification status.
# To fix this, each URL of the pacts returned by the 'pacts for verification' API now contains the
# relevant selectors in the metadata, so that when the verification results are published,
# we can parse that metadata, and trigger one webhook for each consumer version like we used to.
# Actually, we used to trigger one webhook per tag, but given that the most likely use of the
# verification published webhook is for reporting git statuses,
# it makes more sense to trigger per consumer version number (ie. commit).
def expand_events(event_context)
triggers = if event_context[:consumer_version_selectors].is_a?(Array)
event_context[:consumer_version_selectors]
.group_by{ | selector | selector[:consumer_version_number] }
.collect { | consumer_version_number, selectors | merge_consumer_version_selectors(consumer_version_number, selectors, event_context.without(:consumer_version_selectors)) }
else
[event_context]
end
end

def print_debug_messages(changed_pacts)
if changed_pacts.any?
messages = changed_pacts.collect do |tag, previous_pact|
Expand Down
4 changes: 3 additions & 1 deletion lib/pact_broker/webhooks/triggered_webhook.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
require 'sequel'
require 'pact_broker/repositories/helpers'
require 'pact_broker/webhooks/execution'
require 'pact_broker/hash_refinements'

# Represents the relationship between a webhook and the event and object
# that caused it to be triggered. eg a pact publication

module PactBroker
module Webhooks
class TriggeredWebhook < Sequel::Model(:triggered_webhooks)
using PactBroker::HashRefinements
plugin :timestamps, update_on_create: true
plugin :serialization, :json, :event_context

Expand Down Expand Up @@ -60,7 +62,7 @@ def execute options
# getting a random 'no method to_domain for null' error
# not sure on which object, so splitting this out into two lines
pact = pact_publication.to_domain
webhook.to_domain.execute(pact, verification, options)
webhook.to_domain.execute(pact, verification, event_context.symbolize_keys, options)
end

def consumer_name
Expand Down
5 changes: 0 additions & 5 deletions spec/lib/pact_broker/api/resources/verifications_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ module Resources
expect(subject.headers['Location']).to eq("http://example.org/pacts/provider/Provider/consumer/Consumer/pact-version/1234/verification-results/2")
end

it "merges the upstream verification metdata into the webhook context" do
expect(webhook_execution_configuration).to receive(:with_webhook_context).with(parsed_metadata)
subject
end

it "stores the verification in the database" do
expect(PactBroker::Verifications::Service).to receive(:create).with(
next_verification_number,
Expand Down
5 changes: 3 additions & 2 deletions spec/lib/pact_broker/api/resources/webhook_execution_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ module Resources
let(:pact) { instance_double("PactBroker::Domain::Pact") }
let(:consumer_name) { "foo" }
let(:provider_name) { "bar" }
let(:webhook_execution_configuration) { instance_double(PactBroker::Webhooks::ExecutionConfiguration) }
let(:webhook_execution_configuration) { instance_double(PactBroker::Webhooks::ExecutionConfiguration, webhook_context: event_context) }
let(:event_context) { { some: "data" } }

before do
allow(PactBroker::Webhooks::Service).to receive(:test_execution).and_return(execution_result)
Expand All @@ -39,7 +40,7 @@ module Resources
end

it "executes the webhook" do
expect(PactBroker::Webhooks::Service).to receive(:test_execution).with(webhook, webhook_execution_configuration)
expect(PactBroker::Webhooks::Service).to receive(:test_execution).with(webhook, event_context, webhook_execution_configuration)
subject
end

Expand Down
12 changes: 6 additions & 6 deletions spec/lib/pact_broker/domain/webhook_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ module Domain
let(:webhook_template_parameters_hash) { { 'foo' => 'bar' } }
let(:http_request) { double('http request') }
let(:http_response) { double('http response') }
let(:webhook_context) { { some: 'things' } }
let(:event_context) { { some: 'things' } }
let(:logging_options) { { other: 'options' } }
let(:options) { { webhook_context: webhook_context, logging_options: logging_options } }
let(:options) { { logging_options: logging_options } }
let(:pact) { double('pact') }
let(:verification) { double('verification') }
let(:logger) { double('logger').as_null_object }
Expand Down Expand Up @@ -61,11 +61,11 @@ module Domain

let(:webhook_request_logger) { instance_double(PactBroker::Webhooks::WebhookRequestLogger, log: "logs") }

let(:execute) { subject.execute pact, verification, options }
let(:execute) { subject.execute(pact, verification, event_context, options) }

it "creates the template parameters" do
expect(PactBroker::Webhooks::PactAndVerificationParameters).to receive(:new).with(
pact, verification, webhook_context
pact, verification, event_context
)
execute
end
Expand All @@ -81,7 +81,7 @@ module Domain
end

it "generates the execution logs" do
expect(webhook_request_logger).to receive(:log).with(uuid, webhook_request, http_response, nil, webhook_context)
expect(webhook_request_logger).to receive(:log).with(uuid, webhook_request, http_response, nil, event_context)
execute
end

Expand All @@ -106,7 +106,7 @@ module Domain
end

it "generates the execution logs" do
expect(webhook_request_logger).to receive(:log).with(uuid, webhook_request, nil, instance_of(error_class), webhook_context)
expect(webhook_request_logger).to receive(:log).with(uuid, webhook_request, nil, instance_of(error_class), event_context)
execute
end

Expand Down
Loading