Commit 6d445705 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '227202-create-gitlab-issue-from-pagerduty-incident' into 'master'

Create GitLab issues from PagerDuty webhook payload (Part 1)

See merge request gitlab-org/gitlab!36489
parents ded8a083 129dfa33
# frozen_string_literal: true
module IncidentManagement
module Settings
include Gitlab::Utils::StrongMemoize
def incident_management_setting
strong_memoize(:incident_management_setting) do
project.incident_management_setting ||
......
# frozen_string_literal: true
module IncidentManagement
module PagerDuty
class CreateIncidentIssueService < BaseService
include IncidentManagement::Settings
def initialize(project, incident_payload)
super(project, User.alert_bot, incident_payload)
end
def execute
return forbidden unless webhook_available?
issue = create_issue
return error(issue.errors.full_messages.to_sentence, issue) unless issue.valid?
success(issue)
end
private
alias_method :incident_payload, :params
def create_issue
label_result = find_or_create_incident_label
# Create an unlabelled issue if we couldn't create the label
# due to a race condition.
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/65042
extra_params = label_result.success? ? { label_ids: [label_result.payload[:label].id] } : {}
Issues::CreateService.new(
project,
current_user,
title: issue_title,
description: issue_description,
**extra_params
).execute
end
def webhook_available?
Feature.enabled?(:pagerduty_webhook, project) &&
incident_management_setting.pagerduty_active?
end
def forbidden
ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
end
def find_or_create_incident_label
::IncidentManagement::CreateIncidentLabelService.new(project, current_user).execute
end
def issue_title
incident_payload['title']
end
def issue_description
Gitlab::IncidentManagement::PagerDuty::IncidentIssueDescription.new(incident_payload).to_s
end
def success(issue)
ServiceResponse.success(payload: { issue: issue })
end
def error(message, issue = nil)
ServiceResponse.error(payload: { issue: issue }, message: message)
end
end
end
end
......@@ -691,6 +691,14 @@
:weight: 2
:idempotent: true
:tags: []
- :name: incident_management:incident_management_pager_duty_process_incident
:feature_category: :incident_management
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 2
:idempotent:
:tags: []
- :name: incident_management:incident_management_process_alert
:feature_category: :incident_management
:has_external_dependencies:
......@@ -869,7 +877,7 @@
:tags: []
- :name: pipeline_background:ci_pipeline_success_unlock_artifacts
:feature_category: :continuous_integration
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -877,7 +885,7 @@
:tags: []
- :name: pipeline_background:ci_ref_delete_unlock_artifacts
:feature_category: :continuous_integration
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -1670,11 +1678,11 @@
:tags: []
- :name: service_desk_email_receiver
:feature_category: :issue_tracking
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: system_hook_push
:feature_category: :source_code_management
......
# frozen_string_literal: true
module IncidentManagement
module PagerDuty
class ProcessIncidentWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :incident_management
feature_category :incident_management
def perform(project_id, incident_payload)
return unless project_id
project = find_project(project_id)
return unless project
result = create_issue(project, incident_payload)
log_error(result) if result.error?
end
private
def find_project(project_id)
Project.find_by_id(project_id)
end
def create_issue(project, incident_payload)
::IncidentManagement::PagerDuty::CreateIncidentIssueService
.new(project, incident_payload)
.execute
end
def log_error(result)
Gitlab::AppLogger.warn(
message: 'Cannot create issue for PagerDuty incident',
issue_errors: result.message
)
end
end
end
end
......@@ -5,31 +5,31 @@
---
- :name: cronjob:adjourned_group_deletion
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:adjourned_projects_deletion_cron
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:clear_shared_runners_minutes
:feature_category: :continuous_integration
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:elastic_cluster_reindexing_cron
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
......@@ -37,7 +37,7 @@
:tags: []
- :name: cronjob:elastic_index_bulk_cron
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
......@@ -45,7 +45,7 @@
:tags: []
- :name: cronjob:elastic_index_initial_bulk_cron
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
......@@ -53,39 +53,39 @@
:tags: []
- :name: cronjob:geo_container_repository_sync_dispatch
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_file_download_dispatch
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_metrics_update
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_prune_event_log
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_registry_sync
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -93,63 +93,63 @@
:tags: []
- :name: cronjob:geo_repository_sync
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_repository_verification_primary_batch
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_repository_verification_secondary_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_repository_verification_secondary_shard
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_scheduler_per_shard_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_scheduler_primary_per_shard_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_scheduler_secondary_per_shard_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:geo_secondary_registry_consistency
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -157,27 +157,27 @@
:tags: []
- :name: cronjob:geo_sidekiq_cron_config
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:historical_data
:feature_category: :license_compliance
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:import_software_licenses
:feature_category: :license_compliance
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:ingress_modsecurity_counter_metrics
:feature_category: :web_firewall
......@@ -185,7 +185,7 @@
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:iterations_update_status
:feature_category: :issue_tracking
......@@ -201,7 +201,7 @@
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:ldap_sync
:feature_category: :authentication_and_authorization
......@@ -209,51 +209,51 @@
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:network_policy_metrics
:feature_category: :container_network_security
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:pseudonymizer
:feature_category: :integrations
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:sync_seat_link
:feature_category: :billing
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:update_all_mirrors
:feature_category: :source_code_management
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: cronjob:update_max_seats_used_for_gitlab_com_subscriptions
:feature_category: :license_compliance
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :cpu
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: dependency_proxy:purge_dependency_proxy_cache
:feature_category: :dependency_proxy
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -261,31 +261,31 @@
:tags: []
- :name: epics:epics_update_epics_dates
:feature_category: :epics
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_batch_project_registry
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_batch_project_registry_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_blob_verification_primary
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -293,207 +293,207 @@
:tags: []
- :name: geo:geo_container_repository_sync
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_design_repository_shard_sync
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_design_repository_sync
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_event
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_file_download
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_file_registry_removal
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_file_removal
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_hashed_storage_attachments_migration
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_hashed_storage_migration
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_project_sync
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_rename_repository
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_repositories_clean_up
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_repository_cleanup
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_repository_destroy
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_repository_shard_sync
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_repository_verification_primary_shard
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_repository_verification_primary_single
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_repository_verification_secondary_single
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_scheduler_primary_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_scheduler_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_scheduler_secondary_scheduler
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: geo:geo_secondary_repository_backfill
:feature_category: :geo_replication
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: jira_connect:jira_connect_sync_branch
:feature_category: :integrations
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: jira_connect:jira_connect_sync_merge_request
:feature_category: :integrations
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: package_repositories:packages_nuget_extraction
:feature_category: :package_registry
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: personal_access_tokens:personal_access_tokens_groups_policy
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -501,63 +501,63 @@
:tags: []
- :name: personal_access_tokens:personal_access_tokens_instance_policy
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: pipeline_default:ci_trigger_downstream_subscriptions
:feature_category: :continuous_integration
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :cpu
:weight: 3
:idempotent:
:idempotent:
:tags: []
- :name: security_scans:store_security_reports
:feature_category: :static_application_security_testing
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: security_scans:store_security_scans
:feature_category: :static_application_security_testing
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: security_scans:sync_security_reports_to_report_approval_rules
:feature_category: :static_application_security_testing
:has_external_dependencies:
:has_external_dependencies:
:urgency: :high
:resource_boundary: :cpu
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: adjourned_project_deletion
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: admin_emails
:feature_category: :issue_tracking
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: analytics_code_review_metrics
:feature_category: :code_analytics
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -565,7 +565,7 @@
:tags: []
- :name: ci_batch_reset_minutes
:feature_category: :continuous_integration
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -577,11 +577,11 @@
:urgency: :low
:resource_boundary: :cpu
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: elastic_commit_indexer
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
......@@ -589,7 +589,7 @@
:tags: []
- :name: elastic_delete_project
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
......@@ -597,23 +597,23 @@
:tags: []
- :name: elastic_full_index
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: elastic_indexer
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :throttled
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: elastic_indexing_control
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -621,19 +621,19 @@
:tags: []
- :name: elastic_namespace_indexer
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: elastic_namespace_rollout
:feature_category: :global_search
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: ldap_group_sync
:feature_category: :authentication_and_authorization
......@@ -641,51 +641,51 @@
:urgency: :low
:resource_boundary: :unknown
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: new_epic
:feature_category: :epics
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :cpu
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: project_import_schedule
:feature_category: :importers
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: refresh_license_compliance_checks
:feature_category: :license_compliance
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 2
:idempotent:
:idempotent:
:tags: []
- :name: repository_push_audit_event
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: repository_update_mirror
:feature_category: :source_code_management
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:idempotent:
:tags: []
- :name: requirements_management_process_requirements_reports
:feature_category: :requirements_management
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......@@ -709,7 +709,7 @@
:tags: []
- :name: vulnerability_exports_export
:feature_category: :vulnerability_management
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :cpu
:weight: 1
......@@ -717,7 +717,7 @@
:tags: []
- :name: vulnerability_exports_export_deletion
:feature_category: :vulnerability_management
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......
# frozen_string_literal: true
module Gitlab
module IncidentManagement
module PagerDuty
class IncidentIssueDescription
def initialize(incident_payload)
@incident_payload = incident_payload
end
def to_s
markdown_line_break = " \n"
[
"**Incident:** #{markdown_incident}",
"**Incident number:** #{incident_payload['incident_number']}",
"**Urgency:** #{incident_payload['urgency']}",
"**Status:** #{incident_payload['status']}",
"**Incident key:** #{incident_payload['incident_key']}",
"**Created at:** #{markdown_incident_created_at}",
"**Assignees:** #{markdown_assignees.join(', ')}",
"**Impacted services:** #{markdown_impacted_services.join(', ')}"
].join(markdown_line_break)
end
private
attr_reader :incident_payload
def markdown_incident
markdown_link(incident_payload['title'], incident_payload['url'])
end
def incident_created_at
Time.parse(incident_payload['created_at'])
rescue
Time.current.utc # PagerDuty provides time in UTC
end
def markdown_incident_created_at
incident_created_at.strftime('%d %B %Y, %-l:%M%p (%Z)')
end
def markdown_assignees
Array(incident_payload['assignees']).map do |assignee|
markdown_link(assignee['summary'], assignee['url'])
end
end
def markdown_impacted_services
Array(incident_payload['impacted_services']).map do |is|
markdown_link(is['summary'], is['url'])
end
end
def markdown_link(label, url)
return label if url.blank?
"[#{label}](#{url})"
end
end
end
end
end
......@@ -124,7 +124,7 @@ module Gitlab
issues_created_manually_from_alerts: issues_created_manually_from_alerts,
incident_issues: alert_bot_incident_count,
alert_bot_incident_issues: alert_bot_incident_count,
incident_labeled_issues: count(::Issue.with_label_attributes(IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES)),
incident_labeled_issues: count(::Issue.with_label_attributes(::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES)),
keys: count(Key),
label_lists: count(List.label),
lfs_objects: count(LfsObject),
......
# frozen_string_literal: true
module PagerDuty
class WebhookPayloadParser
def initialize(payload)
@payload = payload
end
def self.call(payload)
new(payload).call
end
def call
Array(payload['messages']).map { |msg| parse_message(msg) }
end
private
attr_reader :payload
def parse_message(message)
{
'event' => message['event'],
'incident' => parse_incident(message['incident'])
}
end
def parse_incident(incident)
return {} if incident.blank?
{
'url' => incident['html_url'],
'incident_number' => incident['incident_number'],
'title' => incident['title'],
'status' => incident['status'],
'created_at' => incident['created_at'],
'urgency' => incident['urgency'],
'incident_key' => incident['incident_key'],
'assignees' => reject_empty(parse_assignees(incident)),
'impacted_services' => reject_empty(parse_impacted_services(incident))
}
end
def parse_assignees(incident)
Array(incident['assignments']).map do |a|
{
'summary' => a.dig('assignee', 'summary'),
'url' => a.dig('assignee', 'html_url')
}
end
end
def parse_impacted_services(incident)
Array(incident['impacted_services']).map do |is|
{
'summary' => is['summary'],
'url' => is['html_url']
}
end
end
def reject_empty(entities)
Array(entities).reject { |e| e['summary'].blank? && e['url'].blank? }
end
end
end
{
"messages": [
{
"event": "incident.trigger",
"log_entries": [
{
"id": "R2XGXEI3W0FHMSDXHDIBQGBQ5E",
"type": "trigger_log_entry",
"summary": "Triggered through the website",
"self": "https://api.pagerduty.com/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
"html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
"created_at": "2017-09-26T15:14:36Z",
"agent": {
"id": "P553OPV",
"type": "user_reference",
"summary": "Laura Haley",
"self": "https://api.pagerduty.com/users/P553OPV",
"html_url": "https://webdemo.pagerduty.com/users/P553OPV"
},
"channel": {
"type": "web_trigger",
"summary": "My new incident",
"subject": "My new incident",
"details": "Oh my gosh",
"details_omitted": false
},
"service": {
"id": "PN49J75",
"type": "service_reference",
"summary": "Production XDB Cluster",
"self": "https://api.pagerduty.com/services/PN49J75",
"html_url": "https://webdemo.pagerduty.com/services/PN49J75"
},
"incident": {
"id": "PRORDTY",
"type": "incident_reference",
"summary": "[#33] My new incident",
"self": "https://api.pagerduty.com/incidents/PRORDTY",
"html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY"
},
"teams": [
{
"id": "P4SI59S",
"type": "team_reference",
"summary": "Engineering",
"self": "https://api.pagerduty.com/teams/P4SI59S",
"html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
}
],
"contexts": [],
"event_details": {
"description": "My new incident"
}
}
],
"webhook": {
"endpoint_url": "https://requestb.in/18ao6fs1",
"name": "V2 wabhook",
"description": null,
"webhook_object": {
"id": "PN49J75",
"type": "service_reference",
"summary": "Production XDB Cluster",
"self": "https://api.pagerduty.com/services/PN49J75",
"html_url": "https://webdemo.pagerduty.com/services/PN49J75"
},
"config": {},
"outbound_integration": {
"id": "PJFWPEP",
"type": "outbound_integration_reference",
"summary": "Generic V2 Webhook",
"self": "https://api.pagerduty.com/outbound_integrations/PJFWPEP",
"html_url": null
},
"accounts_addon": null,
"id": "PKT9NNX",
"type": "webhook",
"summary": "V2 wabhook",
"self": "https://api.pagerduty.com/webhooks/PKT9NNX",
"html_url": null
},
"incident": {
"incident_number": 33,
"title": "My new incident",
"description": "My new incident",
"created_at": "2017-09-26T15:14:36Z",
"status": "triggered",
"pending_actions": [
{
"type": "escalate",
"at": "2017-09-26T15:44:36Z"
},
{
"type": "resolve",
"at": "2017-09-26T19:14:36Z"
}
],
"incident_key": null,
"service": {
"id": "PN49J75",
"name": "Production XDB Cluster",
"description": "This service was created during onboarding on July 5, 2017.",
"auto_resolve_timeout": 14400,
"acknowledgement_timeout": 1800,
"created_at": "2017-07-05T17:33:09Z",
"status": "critical",
"last_incident_timestamp": "2017-09-26T15:14:36Z",
"teams": [
{
"id": "P4SI59S",
"type": "team_reference",
"summary": "Engineering",
"self": "https://api.pagerduty.com/teams/P4SI59S",
"html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
}
],
"incident_urgency_rule": {
"type": "constant",
"urgency": "high"
},
"scheduled_actions": [],
"support_hours": null,
"escalation_policy": {
"id": "PINYWEF",
"type": "escalation_policy_reference",
"summary": "Default",
"self": "https://api.pagerduty.com/escalation_policies/PINYWEF",
"html_url": "https://webdemo.pagerduty.com/escalation_policies/PINYWEF"
},
"addons": [],
"privilege": null,
"alert_creation": "create_alerts_and_incidents",
"integrations": [
{
"id": "PUAYF96",
"type": "generic_events_api_inbound_integration_reference",
"summary": "API",
"self": "https://api.pagerduty.com/services/PN49J75/integrations/PUAYF96",
"html_url": "https://webdemo.pagerduty.com/services/PN49J75/integrations/PUAYF96"
},
{
"id": "P90GZUH",
"type": "generic_email_inbound_integration_reference",
"summary": "Email",
"self": "https://api.pagerduty.com/services/PN49J75/integrations/P90GZUH",
"html_url": "https://webdemo.pagerduty.com/services/PN49J75/integrations/P90GZUH"
}
],
"metadata": {},
"type": "service",
"summary": "Production XDB Cluster",
"self": "https://api.pagerduty.com/services/PN49J75",
"html_url": "https://webdemo.pagerduty.com/services/PN49J75"
},
"assignments": [
{
"at": "2017-09-26T15:14:36Z",
"assignee": {
"id": "P553OPV",
"type": "user_reference",
"summary": "Laura Haley",
"self": "https://api.pagerduty.com/users/P553OPV",
"html_url": "https://webdemo.pagerduty.com/users/P553OPV"
}
}
],
"acknowledgements": [],
"last_status_change_at": "2017-09-26T15:14:36Z",
"last_status_change_by": {
"id": "PN49J75",
"type": "service_reference",
"summary": "Production XDB Cluster",
"self": "https://api.pagerduty.com/services/PN49J75",
"html_url": "https://webdemo.pagerduty.com/services/PN49J75"
},
"first_trigger_log_entry": {
"id": "R2XGXEI3W0FHMSDXHDIBQGBQ5E",
"type": "trigger_log_entry_reference",
"summary": "Triggered through the website",
"self": "https://api.pagerduty.com/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E",
"html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY/log_entries/R2XGXEI3W0FHMSDXHDIBQGBQ5E"
},
"escalation_policy": {
"id": "PINYWEF",
"type": "escalation_policy_reference",
"summary": "Default",
"self": "https://api.pagerduty.com/escalation_policies/PINYWEF",
"html_url": "https://webdemo.pagerduty.com/escalation_policies/PINYWEF"
},
"privilege": null,
"teams": [
{
"id": "P4SI59S",
"type": "team_reference",
"summary": "Engineering",
"self": "https://api.pagerduty.com/teams/P4SI59S",
"html_url": "https://webdemo.pagerduty.com/teams/P4SI59S"
}
],
"alert_counts": {
"all": 0,
"triggered": 0,
"resolved": 0
},
"impacted_services": [
{
"id": "PN49J75",
"type": "service_reference",
"summary": "Production XDB Cluster",
"self": "https://api.pagerduty.com/services/PN49J75",
"html_url": "https://webdemo.pagerduty.com/services/PN49J75"
}
],
"is_mergeable": true,
"basic_alert_grouping": null,
"alert_grouping": null,
"metadata": {},
"external_references": [],
"importance": null,
"incidents_responders": [],
"responder_requests": [],
"subscriber_requests": [],
"urgency": "high",
"id": "PRORDTY",
"type": "incident",
"summary": "[#33] My new incident",
"self": "https://api.pagerduty.com/incidents/PRORDTY",
"html_url": "https://webdemo.pagerduty.com/incidents/PRORDTY",
"alerts": [
{
"alert_key": "c24117fc42e44b44b4d6876190583378"
}
]
},
"id": "69a7ced0-a2cd-11e7-a799-22000a15839c",
"created_on": "2017-09-26T15:14:36Z"
}
]
}
# frozen_string_literal: true
require 'fast_spec_helper'
require 'timecop'
RSpec.describe Gitlab::IncidentManagement::PagerDuty::IncidentIssueDescription do
describe '#to_s' do
let(:markdown_line_break) { ' ' }
let(:created_at) { '2017-09-26T15:14:36Z' }
let(:assignees) do
[{ 'summary' => 'Laura Haley', 'url' => 'https://webdemo.pagerduty.com/users/P553OPV' }]
end
let(:impacted_services) do
[{ 'summary' => 'Production XDB Cluster', 'url' => 'https://webdemo.pagerduty.com/services/PN49J75' }]
end
let(:incident_payload) do
{
'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
'incident_number' => 33,
'title' => 'My new incident',
'status' => 'triggered',
'created_at' => created_at,
'urgency' => 'high',
'incident_key' => 'SOME-KEY',
'assignees' => assignees,
'impacted_services' => impacted_services
}
end
subject(:to_s) { described_class.new(incident_payload).to_s }
it 'returns description' do
expect(to_s).to eq(
<<~MARKDOWN.chomp
**Incident:** [My new incident](https://webdemo.pagerduty.com/incidents/PRORDTY)#{markdown_line_break}
**Incident number:** 33#{markdown_line_break}
**Urgency:** high#{markdown_line_break}
**Status:** triggered#{markdown_line_break}
**Incident key:** SOME-KEY#{markdown_line_break}
**Created at:** 26 September 2017, 3:14PM (UTC)#{markdown_line_break}
**Assignees:** [Laura Haley](https://webdemo.pagerduty.com/users/P553OPV)#{markdown_line_break}
**Impacted services:** [Production XDB Cluster](https://webdemo.pagerduty.com/services/PN49J75)
MARKDOWN
)
end
context 'when created_at is missing' do
let(:created_at) { nil }
it 'description contains current time in UTC' do
Timecop.freeze do
now = Time.current.utc.strftime('%d %B %Y, %-l:%M%p (%Z)')
expect(to_s).to include(
<<~MARKDOWN.chomp
**Created at:** #{now}#{markdown_line_break}
MARKDOWN
)
end
end
end
context 'when there are several assignees' do
let(:assignees) do
[
{ 'summary' => 'Laura Haley', 'url' => 'https://laura.pagerduty.com' },
{ 'summary' => 'John Doe', 'url' => 'https://john.pagerduty.com' }
]
end
it 'assignees is a list of links' do
expect(to_s).to include(
<<~MARKDOWN.chomp
**Assignees:** [Laura Haley](https://laura.pagerduty.com), [John Doe](https://john.pagerduty.com)#{markdown_line_break}
MARKDOWN
)
end
end
context 'when there are several impacted services' do
let(:impacted_services) do
[
{ 'summary' => 'XDB Cluster', 'url' => 'https://xdb.pagerduty.com' },
{ 'summary' => 'BRB Cluster', 'url' => 'https://brb.pagerduty.com' }
]
end
it 'impacted services is a list of links' do
expect(to_s).to include(
<<~MARKDOWN.chomp
**Impacted services:** [XDB Cluster](https://xdb.pagerduty.com), [BRB Cluster](https://brb.pagerduty.com)
MARKDOWN
)
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe PagerDuty::WebhookPayloadParser do
describe '.call' do
let(:fixture_file) do
File.read(File.join(File.dirname(__FILE__), '../../fixtures/pager_duty/webhook_incident_trigger.json'))
end
subject(:parse) { described_class.call(payload) }
context 'when payload is a correct PagerDuty payload' do
let(:payload) { Gitlab::Json.parse(fixture_file) }
it 'returns parsed payload' do
is_expected.to eq(
[
{
'event' => 'incident.trigger',
'incident' => {
'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
'incident_number' => 33,
'title' => 'My new incident',
'status' => 'triggered',
'created_at' => '2017-09-26T15:14:36Z',
'urgency' => 'high',
'incident_key' => nil,
'assignees' => [{
'summary' => 'Laura Haley',
'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
}],
'impacted_services' => [{
'summary' => 'Production XDB Cluster',
'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
}]
}
}
]
)
end
context 'when assignments summary and html_url are blank' do
before do
payload['messages'].each do |m|
m['incident']['assignments'] = [{ 'assignee' => { 'summary' => '', 'html_url' => '' } }]
end
end
it 'returns parsed payload with blank assignees' do
assignees = parse.map { |events| events['incident'].slice('assignees') }
expect(assignees).to eq([{ 'assignees' => [] }])
end
end
context 'when impacted_services summary and html_url are blank' do
before do
payload['messages'].each do |m|
m['incident']['impacted_services'] = [{ 'summary' => '', 'html_url' => '' }]
end
end
it 'returns parsed payload with blank assignees' do
assignees = parse.map { |events| events['incident'].slice('impacted_services') }
expect(assignees).to eq([{ 'impacted_services' => [] }])
end
end
end
context 'when payload has no incidents' do
let(:payload) { { 'messages' => [{ 'event' => 'incident.trigger' }] } }
it 'returns payload with blank incidents' do
is_expected.to eq([{ 'event' => 'incident.trigger', 'incident' => {} }])
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::PagerDuty::CreateIncidentIssueService do
let_it_be(:project, reload: true) { create(:project) }
let_it_be(:user) { User.alert_bot }
let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
let(:parsed_payload) { ::PagerDuty::WebhookPayloadParser.call(webhook_payload) }
let(:incident_payload) { parsed_payload.first['incident'] }
subject(:execute) { described_class.new(project, incident_payload).execute }
describe '#execute' do
context 'when pagerduty_webhook feature enabled' do
before do
stub_feature_flags(pagerduty_webhook: project)
end
context 'when PagerDuty webhook setting is active' do
let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
context 'when issue can be created' do
it 'creates a new issue' do
expect { execute }.to change(Issue, :count).by(1)
end
it 'responds with success' do
response = execute
expect(response).to be_success
expect(response.payload[:issue]).to be_kind_of(Issue)
end
it 'the issue author is Alert bot' do
expect(execute.payload[:issue].author).to eq(User.alert_bot)
end
it 'issue has a correct title' do
expect(execute.payload[:issue].title).to eq(incident_payload['title'])
end
it 'issue has a correct description' do
markdown_line_break = ' '
expect(execute.payload[:issue].description).to eq(
<<~MARKDOWN.chomp
**Incident:** [My new incident](https://webdemo.pagerduty.com/incidents/PRORDTY)#{markdown_line_break}
**Incident number:** 33#{markdown_line_break}
**Urgency:** high#{markdown_line_break}
**Status:** triggered#{markdown_line_break}
**Incident key:** #{markdown_line_break}
**Created at:** 26 September 2017, 3:14PM (UTC)#{markdown_line_break}
**Assignees:** [Laura Haley](https://webdemo.pagerduty.com/users/P553OPV)#{markdown_line_break}
**Impacted services:** [Production XDB Cluster](https://webdemo.pagerduty.com/services/PN49J75)
MARKDOWN
)
end
end
context 'when the payload does not contain a title' do
let(:incident_payload) { {} }
it 'does not create a GitLab issue' do
expect { execute }.not_to change(Issue, :count)
end
it 'responds with error' do
expect(execute).to be_error
expect(execute.message).to eq("Title can't be blank")
end
end
end
context 'when PagerDuty webhook setting is not active' do
let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
it 'does not create a GitLab issue' do
expect { execute }.not_to change(Issue, :count)
end
it 'responds with forbidden' do
expect(execute).to be_error
expect(execute.http_status).to eq(:forbidden)
end
end
end
context 'when pagerduty_webhook feature disabled' do
before do
stub_feature_flags(pagerduty_webhook: false)
end
it 'does not create a GitLab issue' do
expect { execute }.not_to change(Issue, :count)
end
it 'responds with forbidden' do
expect(execute).to be_error
expect(execute.http_status).to eq(:forbidden)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::PagerDuty::ProcessIncidentWorker do
let_it_be(:project) { create(:project) }
let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
describe '#perform' do
subject(:perform) { described_class.new.perform(project.id, incident_payload) }
context 'with valid incident payload' do
let(:incident_payload) do
{
'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
'incident_number' => 33,
'title' => 'My new incident',
'status' => 'triggered',
'created_at' => '2017-09-26T15:14:36Z',
'urgency' => 'high',
'incident_key' => nil,
'assignees' => [{
'summary' => 'Laura Haley', 'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
}],
'impacted_services' => [{
'summary' => 'Production XDB Cluster', 'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
}]
}
end
it 'creates a GitLab issue' do
expect { perform }.to change(Issue, :count).by(1)
end
end
context 'with invalid incident payload' do
let(:incident_payload) { {} }
before do
allow(Gitlab::AppLogger).to receive(:warn).and_call_original
end
it 'does not create a GitLab issue' do
expect { perform }.not_to change(Issue, :count)
end
it 'logs a warning' do
perform
expect(Gitlab::AppLogger).to have_received(:warn).with(
message: 'Cannot create issue for PagerDuty incident',
issue_errors: "Title can't be blank"
)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment