require "pact_broker/services" require "pact_broker/hash_refinements" module PactBroker module Webhooks module TriggerService RESOURCE_CREATION = PactBroker::Webhooks::TriggeredWebhook::TRIGGER_TYPE_RESOURCE_CREATION USER = PactBroker::Webhooks::TriggeredWebhook::TRIGGER_TYPE_USER extend self extend PactBroker::Repositories extend PactBroker::Services include PactBroker::Logging using PactBroker::HashRefinements # the main entry point def create_triggered_webhooks_for_event pact, verification, event_name, event_context webhooks = webhook_repository.find_webhooks_to_trigger(consumer: pact.consumer, provider: pact.provider, event_name: event_name) if webhooks.any? create_triggered_webhooks_for_webhooks(webhooks, pact, verification, event_name, event_context.merge(event_name: event_name)) else [] end end def next_uuid SecureRandom.uuid end def test_execution webhook, event_context, execution_configuration merged_options = execution_configuration.with_failure_log_message("Webhook execution failed").to_hash verification = nil if webhook.trigger_on_provider_verification_published? verification = verification_service.search_for_latest(webhook.consumer_name, webhook.provider_name) || PactBroker::Verifications::PlaceholderVerification.new 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, event_context.merge(event_name: "test"), merged_options) end def execute_triggered_webhook_now triggered_webhook, webhook_execution_configuration_hash webhook_execution_result = triggered_webhook.execute(webhook_execution_configuration_hash) webhook_repository.create_execution(triggered_webhook, webhook_execution_result) webhook_execution_result end def schedule_webhooks(triggered_webhooks, options) triggered_webhooks.each_with_index do | triggered_webhook, i | logger.info "Scheduling job for webhook with uuid #{triggered_webhook.webhook.uuid}, context: #{triggered_webhook.event_context}" logger.debug "Schedule webhook with options #{options}" job_data = { triggered_webhook: triggered_webhook }.deep_merge(options) begin # Delay slightly to make sure the request transaction has finished before we execute the webhook Job.perform_in(5 + (i * 3), job_data) rescue StandardError => e logger.warn("Error scheduling webhook execution for webhook with uuid #{triggered_webhook&.webhook&.uuid}", e) nil end end end def create_triggered_webhooks_for_webhooks webhooks, pact, verification, event_name, event_context webhooks.flat_map do | webhook | expanded_event_contexts = expand_events_for_currently_deployed_environments(webhook, pact, event_context) expanded_event_contexts = expand_events_for_required_verifications(event_name, pact, expanded_event_contexts) expanded_event_contexts = expanded_event_contexts.flat_map { | ec | expand_events_for_verification_of_multiple_selected_pacts(ec) } expanded_event_contexts.collect do | expanded_event_context | pact_for_triggered_webhook = verification ? find_pact_for_verification_triggered_webhook(pact, expanded_event_context) : pact webhook_repository.create_triggered_webhook(next_uuid, webhook, pact_for_triggered_webhook, verification, RESOURCE_CREATION, event_name, expanded_event_context) end end end private :create_triggered_webhooks_for_webhooks 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 private :merge_consumer_version_selectors # 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_for_verification_of_multiple_selected_pacts(event_context) 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 private :expand_events_for_verification_of_multiple_selected_pacts def expand_events_for_currently_deployed_environments(webhook, pact, event_context) if PactBroker.feature_enabled?(:expand_currently_deployed_provider_versions) && webhook.expand_currently_deployed_provider_versions? deployed_version_service.find_currently_deployed_versions_for_pacticipant(pact.provider).collect(&:version_number).uniq.collect do | version_number | event_context.merge(currently_deployed_provider_version_number: version_number) end else [event_context] end end private :expand_events_for_currently_deployed_environments def find_pact_for_verification_triggered_webhook(pact, reconstituted_event_context) if reconstituted_event_context[:consumer_version_number] find_pact_params = { consumer_name: pact.consumer_name, provider_name: pact.provider_name, consumer_version_number: reconstituted_event_context[:consumer_version_number] } pact_service.find_pact(find_pact_params) || pact else pact end end private :find_pact_for_verification_triggered_webhook def expand_events_for_required_verifications(event_name, pact, event_contexts) if event_name == PactBroker::Webhooks::WebhookEvent::CONTRACT_REQUIRING_VERIFICATION_PUBLISHED required_verifications = verification_service.calculate_required_verifications_for_pact(pact) event_contexts.flat_map do | event_context | required_verifications.collect do | required_verification | event_context.merge( provider_version_number: required_verification.provider_version.number, provider_version_branch: provider_version_branch_for_required_verification(required_verification, ), provider_version_descriptions: required_verification.provider_version_descriptions.uniq ) end end else event_contexts end end def provider_version_branch_for_required_verification(required_verification) required_verification.provider_version_selectors.find(&:latest_for_main_branch?)&.resolved_branch_name || required_verification.provider_version.branch_versions.last&.branch_name end end end end