2022-11-25 23:54:43 +05:30
|
|
|
#!/usr/bin/env ruby
|
|
|
|
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
require 'optparse'
|
|
|
|
require 'json'
|
2022-11-25 23:54:43 +05:30
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
require_relative 'api/pipeline_failed_jobs'
|
2022-11-25 23:54:43 +05:30
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
class GenerateFailedPipelineSlackMessage
|
|
|
|
DEFAULT_OPTIONS = {
|
|
|
|
failed_pipeline_slack_message_file: 'failed_pipeline_slack_message.json',
|
|
|
|
incident_json_file: 'incident.json'
|
|
|
|
}.freeze
|
2022-11-25 23:54:43 +05:30
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
def initialize(options)
|
|
|
|
@incident_json_file = options.delete(:incident_json_file)
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
def execute
|
|
|
|
{
|
2022-11-25 23:54:43 +05:30
|
|
|
channel: ENV['SLACK_CHANNEL'],
|
|
|
|
username: "Failed pipeline reporter",
|
|
|
|
icon_emoji: ":boom:",
|
|
|
|
text: "*#{title}*",
|
|
|
|
blocks: [
|
|
|
|
{
|
|
|
|
type: "section",
|
|
|
|
text: {
|
|
|
|
type: "mrkdwn",
|
|
|
|
text: "*#{title}*"
|
2023-01-13 00:05:48 +05:30
|
|
|
},
|
|
|
|
accessory: {
|
|
|
|
type: "button",
|
|
|
|
text: {
|
|
|
|
type: "plain_text",
|
|
|
|
text: incident_button_text
|
|
|
|
},
|
|
|
|
url: incident_button_link
|
2022-11-25 23:54:43 +05:30
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: "section",
|
2023-01-13 00:05:48 +05:30
|
|
|
text: {
|
|
|
|
type: "mrkdwn",
|
|
|
|
text: "*Branch*: #{branch_link}"
|
|
|
|
}
|
2022-11-25 23:54:43 +05:30
|
|
|
},
|
|
|
|
{
|
|
|
|
type: "section",
|
2023-01-13 00:05:48 +05:30
|
|
|
text: {
|
|
|
|
type: "mrkdwn",
|
|
|
|
text: "*Commit*: #{commit_link}"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: "section",
|
|
|
|
text: {
|
|
|
|
type: "mrkdwn",
|
|
|
|
text: "*Triggered by* #{triggered_by_link} • *Source:* #{source} • *Duration:* #{pipeline_duration} minutes"
|
|
|
|
}
|
2022-11-25 23:54:43 +05:30
|
|
|
},
|
|
|
|
{
|
|
|
|
type: "section",
|
|
|
|
text: {
|
|
|
|
type: "mrkdwn",
|
|
|
|
text: "*Failed jobs (#{failed_jobs.size}):* #{failed_jobs_list}"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
attr_reader :incident_json_file
|
|
|
|
|
|
|
|
def failed_jobs
|
|
|
|
@failed_jobs ||= PipelineFailedJobs.new(API::DEFAULT_OPTIONS.dup.merge(exclude_allowed_to_fail_jobs: true)).execute
|
|
|
|
end
|
2022-11-25 23:54:43 +05:30
|
|
|
|
|
|
|
def title
|
2023-01-13 00:05:48 +05:30
|
|
|
"#{project_link} pipeline #{pipeline_link} failed"
|
|
|
|
end
|
|
|
|
|
|
|
|
def incident_exist?
|
|
|
|
return @incident_exist if defined?(@incident_exist)
|
|
|
|
|
|
|
|
@incident_exist = File.exist?(incident_json_file)
|
|
|
|
end
|
|
|
|
|
|
|
|
def incident
|
|
|
|
return unless incident_exist?
|
|
|
|
|
|
|
|
@incident ||= JSON.parse(File.read(incident_json_file))
|
|
|
|
end
|
|
|
|
|
|
|
|
def incident_button_text
|
|
|
|
if incident_exist?
|
|
|
|
"View incident ##{incident['iid']}"
|
|
|
|
else
|
|
|
|
'Create incident'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def incident_button_link
|
|
|
|
if incident_exist?
|
|
|
|
incident['web_url']
|
|
|
|
else
|
2023-03-04 22:38:38 +05:30
|
|
|
"#{ENV['CI_SERVER_URL']}/#{ENV['BROKEN_BRANCH_INCIDENTS_PROJECT']}/-/issues/new?" \
|
2023-01-13 00:05:48 +05:30
|
|
|
"issuable_template=incident&issue%5Bissue_type%5D=incident"
|
|
|
|
end
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def pipeline_link
|
|
|
|
"<#{ENV['CI_PIPELINE_URL']}|##{ENV['CI_PIPELINE_ID']}>"
|
|
|
|
end
|
|
|
|
|
|
|
|
def branch_link
|
|
|
|
"<#{ENV['CI_PROJECT_URL']}/-/commits/#{ENV['CI_COMMIT_REF_NAME']}|`#{ENV['CI_COMMIT_REF_NAME']}`>"
|
|
|
|
end
|
|
|
|
|
|
|
|
def pipeline_duration
|
|
|
|
((Time.now - Time.parse(ENV['CI_PIPELINE_CREATED_AT'])) / 60.to_f).round(2)
|
|
|
|
end
|
|
|
|
|
|
|
|
def commit_link
|
|
|
|
"<#{ENV['CI_PROJECT_URL']}/-/commit/#{ENV['CI_COMMIT_SHA']}|#{ENV['CI_COMMIT_TITLE']}>"
|
|
|
|
end
|
|
|
|
|
|
|
|
def source
|
2023-01-13 00:05:48 +05:30
|
|
|
"`#{ENV['CI_PIPELINE_SOURCE']}#{schedule_type}`"
|
|
|
|
end
|
|
|
|
|
|
|
|
def schedule_type
|
|
|
|
ENV['CI_PIPELINE_SOURCE'] == 'schedule' ? ": #{ENV['SCHEDULE_TYPE']}" : ''
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def project_link
|
2023-01-13 00:05:48 +05:30
|
|
|
"<#{ENV['CI_PROJECT_URL']}|#{ENV['CI_PROJECT_PATH']}>"
|
2022-11-25 23:54:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def triggered_by_link
|
|
|
|
"<#{ENV['CI_SERVER_URL']}/#{ENV['GITLAB_USER_LOGIN']}|#{ENV['GITLAB_USER_NAME']}>"
|
|
|
|
end
|
|
|
|
|
|
|
|
def failed_jobs_list
|
|
|
|
failed_jobs.map { |job| "<#{job.web_url}|#{job.name}>" }.join(', ')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
if $PROGRAM_NAME == __FILE__
|
|
|
|
options = GenerateFailedPipelineSlackMessage::DEFAULT_OPTIONS.dup
|
|
|
|
|
|
|
|
OptionParser.new do |opts|
|
|
|
|
opts.on("-i", "--incident-json-file file_path", String, "Path to a file where the incident JSON data "\
|
|
|
|
"can be found (defaults to "\
|
|
|
|
"`#{GenerateFailedPipelineSlackMessage::DEFAULT_OPTIONS[:incident_json_file]}`)") do |value|
|
|
|
|
options[:incident_json_file] = value
|
|
|
|
end
|
|
|
|
|
|
|
|
opts.on("-f", "--failed-pipeline-slack-message-file file_path", String, "Path to a file where to save the Slack "\
|
|
|
|
"message (defaults to "\
|
|
|
|
"`#{GenerateFailedPipelineSlackMessage::DEFAULT_OPTIONS[:failed_pipeline_slack_message_file]}`)") do |value|
|
|
|
|
options[:failed_pipeline_slack_message_file] = value
|
|
|
|
end
|
|
|
|
|
|
|
|
opts.on("-h", "--help", "Prints this help") do
|
|
|
|
puts opts
|
|
|
|
exit
|
|
|
|
end
|
|
|
|
end.parse!
|
|
|
|
|
|
|
|
failed_pipeline_slack_message_file = options.delete(:failed_pipeline_slack_message_file)
|
|
|
|
|
|
|
|
GenerateFailedPipelineSlackMessage.new(options).execute.tap do |message_payload|
|
|
|
|
if failed_pipeline_slack_message_file
|
|
|
|
File.write(failed_pipeline_slack_message_file, JSON.pretty_generate(message_payload))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|