Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-20 21:10:16 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-20 21:10:16 +0300
commit694555850c08df0e98209b49b9c26d4c0428ad31 (patch)
tree1e69bb796398f055a8b2e1823a78d4a4ae879930 /lib/gitlab/alert_management
parent520f3178665de5e7d313d332989cd445da83817b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'lib/gitlab/alert_management')
-rw-r--r--lib/gitlab/alert_management/payload.rb48
-rw-r--r--lib/gitlab/alert_management/payload/base.rb144
-rw-r--r--lib/gitlab/alert_management/payload/generic.rb24
-rw-r--r--lib/gitlab/alert_management/payload/managed_prometheus.rb58
-rw-r--r--lib/gitlab/alert_management/payload/prometheus.rb103
5 files changed, 377 insertions, 0 deletions
diff --git a/lib/gitlab/alert_management/payload.rb b/lib/gitlab/alert_management/payload.rb
new file mode 100644
index 00000000000..177d544d720
--- /dev/null
+++ b/lib/gitlab/alert_management/payload.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module AlertManagement
+ module Payload
+ MONITORING_TOOLS = {
+ prometheus: 'Prometheus'
+ }.freeze
+
+ class << self
+ # Instantiates an instance of a subclass of
+ # Gitlab::AlertManagement::Payload::Base. This can
+ # be used to create new alerts or read content from
+ # the payload of an existing AlertManagement::Alert
+ #
+ # @param project [Project]
+ # @param payload [Hash]
+ # @param monitoring_tool [String]
+ def parse(project, payload, monitoring_tool: nil)
+ payload_class = payload_class_for(
+ monitoring_tool: monitoring_tool || payload&.dig('monitoring_tool'),
+ payload: payload
+ )
+
+ payload_class.new(project: project, payload: payload)
+ end
+
+ private
+
+ def payload_class_for(monitoring_tool:, payload:)
+ if monitoring_tool == MONITORING_TOOLS[:prometheus]
+ if gitlab_managed_prometheus?(payload)
+ ::Gitlab::AlertManagement::Payload::ManagedPrometheus
+ else
+ ::Gitlab::AlertManagement::Payload::Prometheus
+ end
+ else
+ ::Gitlab::AlertManagement::Payload::Generic
+ end
+ end
+
+ def gitlab_managed_prometheus?(payload)
+ payload&.dig('labels', 'gitlab_alert_id').present?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/alert_management/payload/base.rb b/lib/gitlab/alert_management/payload/base.rb
new file mode 100644
index 00000000000..3601775f1b2
--- /dev/null
+++ b/lib/gitlab/alert_management/payload/base.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+# Representation of a payload of an alert. Defines a constant
+# API so that payloads from various sources can be treated
+# identically. Subclasses should define how to parse payload
+# based on source of alert.
+module Gitlab
+ module AlertManagement
+ module Payload
+ class Base
+ include ActiveModel::Model
+ include Gitlab::Utils::StrongMemoize
+ include Gitlab::Routing
+
+ attr_accessor :project, :payload
+
+ # Any attribute expected to be specifically read from
+ # or derived from an alert payload should be defined.
+ EXPECTED_PAYLOAD_ATTRIBUTES = [
+ :alert_markdown,
+ :alert_title,
+ :annotations,
+ :ends_at,
+ :environment,
+ :environment_name,
+ :full_query,
+ :generator_url,
+ :gitlab_alert,
+ :gitlab_fingerprint,
+ :gitlab_prometheus_alert_id,
+ :gitlab_y_label,
+ :description,
+ :hosts,
+ :metric_id,
+ :metrics_dashboard_url,
+ :monitoring_tool,
+ :runbook,
+ :service,
+ :severity,
+ :starts_at,
+ :status,
+ :title
+ ].freeze
+
+ # Define expected API for a payload
+ EXPECTED_PAYLOAD_ATTRIBUTES.each do |key|
+ define_method(key) {}
+ end
+
+ # Defines a method which allows access to a given
+ # value within an alert payload
+ #
+ # @param key [Symbol] Name expected to be used to reference value
+ # @param paths [String, Array<String>, Array<Array<String>>,]
+ # List of (nested) keys at value can be found, the
+ # first to yield a result will be used
+ # @param type [Symbol] If value should be converted to another type,
+ # that should be specified here
+ # @param fallback [Proc] Block to be executed to yield a value if
+ # a value cannot be idenitied at any provided paths
+ # Example)
+ # attribute :title
+ # paths: [['title'],
+ # ['details', 'title']]
+ # fallback: Proc.new { 'New Alert' }
+ #
+ # The above sample definition will define a method
+ # called #title which will return the value from the
+ # payload under the key `title` if available, otherwise
+ # looking under `details.title`. If neither returns a
+ # value, the return value will be `'New Alert'`
+ def self.attribute(key, paths:, type: nil, fallback: -> { nil })
+ define_method(key) do
+ strong_memoize(key) do
+ paths = Array(paths).first.is_a?(String) ? [Array(paths)] : paths
+ value = value_for_paths(paths)
+ value = parse_value(value, type) if value
+
+ value.presence || fallback.call
+ end
+ end
+ end
+
+ # Attributes of an AlertManagement::Alert as read
+ # directly from a payload. Prefer accessing
+ # AlertManagement::Alert directly for read operations.
+ def alert_params
+ {
+ description: description,
+ ended_at: ends_at,
+ environment: environment,
+ fingerprint: gitlab_fingerprint,
+ hosts: Array(hosts),
+ monitoring_tool: monitoring_tool,
+ payload: payload,
+ project_id: project.id,
+ prometheus_alert: gitlab_alert,
+ service: service,
+ severity: severity,
+ started_at: starts_at,
+ title: title
+ }.transform_values(&:presence).compact
+ end
+
+ def gitlab_fingerprint
+ strong_memoize(:gitlab_fingerprint) do
+ Gitlab::AlertManagement::Fingerprint.generate(plain_gitlab_fingerprint) if plain_gitlab_fingerprint
+ end
+ end
+
+ private
+
+ def plain_gitlab_fingerprint; end
+
+ def value_for_paths(paths)
+ target_path = paths.find { |path| payload&.dig(*path) }
+
+ payload&.dig(*target_path) if target_path
+ end
+
+ def parse_value(value, type)
+ case type
+ when :time
+ parse_time(value)
+ when :integer
+ parse_integer(value)
+ else
+ value
+ end
+ end
+
+ def parse_time(value)
+ Time.parse(value).utc
+ rescue ArgumentError
+ end
+
+ def parse_integer(value)
+ Integer(value)
+ rescue ArgumentError, TypeError
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/alert_management/payload/generic.rb b/lib/gitlab/alert_management/payload/generic.rb
new file mode 100644
index 00000000000..8f1eafd2ade
--- /dev/null
+++ b/lib/gitlab/alert_management/payload/generic.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# Attribute mapping for alerts via generic alerting integration.
+module Gitlab
+ module AlertManagement
+ module Payload
+ class Generic < Base
+ DEFAULT_TITLE = 'New: Incident'
+ DEFAULT_SEVERITY = 'critical'
+
+ attribute :hosts, paths: 'hosts'
+ attribute :monitoring_tool, paths: 'monitoring_tool'
+ attribute :runbook, paths: 'runbook'
+ attribute :service, paths: 'service'
+ attribute :severity, paths: 'severity', fallback: -> { DEFAULT_SEVERITY }
+ attribute :starts_at, paths: 'start_time', type: :time, fallback: -> { Time.current.utc }
+ attribute :title, paths: 'title', fallback: -> { DEFAULT_TITLE }
+
+ attribute :plain_gitlab_fingerprint, paths: 'fingerprint'
+ private :plain_gitlab_fingerprint
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/alert_management/payload/managed_prometheus.rb b/lib/gitlab/alert_management/payload/managed_prometheus.rb
new file mode 100644
index 00000000000..2236e60a0c6
--- /dev/null
+++ b/lib/gitlab/alert_management/payload/managed_prometheus.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+# Attribute mapping for alerts via prometheus alerting integration,
+# and for which payload includes gitlab-controlled attributes.
+module Gitlab
+ module AlertManagement
+ module Payload
+ class ManagedPrometheus < ::Gitlab::AlertManagement::Payload::Prometheus
+ attribute :gitlab_prometheus_alert_id,
+ paths: %w(labels gitlab_prometheus_alert_id),
+ type: :integer
+ attribute :metric_id,
+ paths: %w(labels gitlab_alert_id),
+ type: :integer
+
+ def gitlab_alert
+ strong_memoize(:gitlab_alert) do
+ next unless metric_id || gitlab_prometheus_alert_id
+
+ alerts = Projects::Prometheus::AlertsFinder
+ .new(project: project, metric: metric_id, id: gitlab_prometheus_alert_id)
+ .execute
+
+ next if alerts.blank? || alerts.size > 1
+
+ alerts.first
+ end
+ end
+
+ def full_query
+ gitlab_alert&.full_query || super
+ end
+
+ def environment
+ gitlab_alert&.environment || super
+ end
+
+ def metrics_dashboard_url
+ return unless gitlab_alert
+
+ metrics_dashboard_project_prometheus_alert_url(
+ project,
+ gitlab_alert.prometheus_metric_id,
+ environment_id: environment.id,
+ embedded: true,
+ **alert_embed_window_params
+ )
+ end
+
+ private
+
+ def plain_gitlab_fingerprint
+ [metric_id, starts_at_raw].join('/')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/alert_management/payload/prometheus.rb b/lib/gitlab/alert_management/payload/prometheus.rb
new file mode 100644
index 00000000000..cdfa6e9d15b
--- /dev/null
+++ b/lib/gitlab/alert_management/payload/prometheus.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+# Attribute mapping for alerts via prometheus alerting integration.
+module Gitlab
+ module AlertManagement
+ module Payload
+ class Prometheus < Base
+ attribute :alert_markdown, paths: %w(annotations gitlab_incident_markdown)
+ attribute :annotations, paths: 'annotations'
+ attribute :description, paths: %w(annotations description)
+ attribute :ends_at, paths: 'endsAt', type: :time
+ attribute :environment_name, paths: %w(labels gitlab_environment_name)
+ attribute :generator_url, paths: %w(generatorURL)
+ attribute :gitlab_y_label,
+ paths: [%w(annotations gitlab_y_label),
+ %w(annotations title),
+ %w(annotations summary),
+ %w(labels alertname)]
+ attribute :runbook, paths: %w(annotations runbook)
+ attribute :starts_at,
+ paths: 'startsAt',
+ type: :time,
+ fallback: -> { Time.current.utc }
+ attribute :status, paths: 'status'
+ attribute :title,
+ paths: [%w(annotations title),
+ %w(annotations summary),
+ %w(labels alertname)]
+
+ attribute :starts_at_raw,
+ paths: [%w(startsAt)]
+ private :starts_at_raw
+
+ METRIC_TIME_WINDOW = 30.minutes
+
+ def monitoring_tool
+ Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
+ end
+
+ # Parses `g0.expr` from `generatorURL`.
+ #
+ # Example: http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1
+ def full_query
+ return unless generator_url
+
+ uri = URI(generator_url)
+
+ Rack::Utils.parse_query(uri.query).fetch('g0.expr')
+ rescue URI::InvalidURIError, KeyError
+ end
+
+ def environment
+ return unless environment_name
+
+ EnvironmentsFinder.new(project, nil, { name: environment_name })
+ .find
+ &.first
+ end
+
+ def metrics_dashboard_url
+ return unless environment && full_query && title
+
+ metrics_dashboard_project_environment_url(
+ project,
+ environment,
+ embed_json: dashboard_json,
+ embedded: true,
+ **alert_embed_window_params
+ )
+ end
+
+ private
+
+ def plain_gitlab_fingerprint
+ [starts_at_raw, title, full_query].join('/')
+ end
+
+ # Formatted for parsing by JS
+ def alert_embed_window_params
+ {
+ start: (starts_at - METRIC_TIME_WINDOW).utc.strftime('%FT%TZ'),
+ end: (starts_at + METRIC_TIME_WINDOW).utc.strftime('%FT%TZ')
+ }
+ end
+
+ def dashboard_json
+ {
+ panel_groups: [{
+ panels: [{
+ type: 'area-chart',
+ title: title,
+ y_label: gitlab_y_label,
+ metrics: [{
+ query_range: full_query
+ }]
+ }]
+ }]
+ }.to_json
+ end
+ end
+ end
+ end
+end