Fix mattermost incoming webhook action for fastlane (#7262)

* Fix mattermost incoming webhook action for fastlane

* feedback review

* fix fastlane bundler version
This commit is contained in:
Elias Nahum
2023-04-04 10:26:21 -04:00
committed by GitHub
parent 2e0cdebfd0
commit dee100f1d9
6 changed files with 214 additions and 143 deletions

View File

@@ -283,7 +283,7 @@ lane :upload_file_to_s3 do |options|
:title => '',
:thumb_url => 'https://support.apple.com/library/content/dam/edam/applecare/images/en_US/iOS/move-to-ios-icon.png',
:msg => msg,
:default_payloads => [],
:default_payloads => [:git_branch, :last_git_commit_hash],
:success => true,
})
end

View File

@@ -2,7 +2,6 @@ source "https://rubygems.org"
gem "fastlane"
gem "nokogiri"
gem "slack-notifier", "2.3.2"
plugins_path = File.join(File.dirname(__FILE__), '.', 'Pluginfile')
eval(File.read(plugins_path), binding) if File.exist?(plugins_path)

View File

@@ -3,12 +3,12 @@ GEM
specs:
CFPropertyList (3.0.6)
rexml
addressable (2.8.1)
addressable (2.8.2)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.735.0)
aws-partitions (1.740.0)
aws-sdk-core (3.171.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
@@ -17,7 +17,7 @@ GEM
aws-sdk-kms (1.63.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.119.2)
aws-sdk-s3 (1.120.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
@@ -111,7 +111,7 @@ GEM
fastlane-plugin-find_replace_string (0.1.0)
fastlane-plugin-versioning_android (0.1.1)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.37.0)
google-apis-androidpublisher_v3 (0.38.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.0)
addressable (~> 2.5, >= 2.5.1)
@@ -191,7 +191,6 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
@@ -229,7 +228,6 @@ DEPENDENCIES
fastlane-plugin-find_replace_string
fastlane-plugin-versioning_android
nokogiri
slack-notifier (= 2.3.2)
BUNDLED WITH
2.3.26

View File

@@ -2,53 +2,216 @@
# rubocop:disable Style/MultilineTernaryOperator
# rubocop:disable Style/NestedTernaryOperator
module Fastlane
module Notification
class Mattermost
def initialize(webhook_url)
@webhook_url = webhook_url
@client = Faraday.new do | conn |
conn.use(Faraday::Response::RaiseError)
end
end
def post_incoming_webhook(channel:, username:, attachments:, icon_url:)
@client.post(@webhook_url) do | request |
request.headers['Content-Type'] = 'application/json'
request.body = {
channel: channel,
username: username,
icon_url: icon_url,
attachments: attachments
}.to_json
end
end
class LinkFormatter
HTML_PATTERN = %r{<a.*?href=['"](?<link>#{URI.regexp})['"].*?>(?<label>.+?)<\/a>}
MARKDOWN_PATTERN = /\[(?<label>[^\[\]]*?)\]\((?<link>#{URI.regexp}|mailto:#{URI::MailTo::EMAIL_REGEXP})\)/
def self.format(string)
convert_markdown_to_link(convert_html_to_link(string.scrub))
end
def self.convert_html_to_link(string)
string.gsub(HTML_PATTERN) do |match|
md_link(Regexp.last_match[:link], Regexp.last_match[:label])
end
end
def self.convert_markdown_to_link(string)
string.gsub(MARKDOWN_PATTERN) do |match|
md_link(Regexp.last_match[:link], Regexp.last_match[:label])
end
end
def self.md_link(href, text)
return "<#{href}>" if text.nil? || text.empty?
"<#{href}|#{text}>"
end
end
end
end
module Actions
class MattermostAction < Action
class Runner
def initialize(url)
@notifier = Fastlane::Notification::Mattermost.new(url)
end
def run(options)
options[:message] = self.class.trim_message(options[:message].to_s || '')
options[:message] = Fastlane::Notification::Mattermost::LinkFormatter.format(options[:message])
options[:pretext] = options[:pretext].gsub('\n', "\n") unless options[:pretext].nil?
if options[:channel].to_s.length > 0
channel = options[:channel]
channel = ('#' + channel) unless ['#', '@'].include?(channel[0]) # send message to channel by default
end
username = options[:overwrite_webhook_username_and_icon] ? nil : options[:username]
mattermost_attachment = self.class.generate_mattermost_attachments(options)
icon_url = options[:overwrite_webhook_username_and_icon] ? nil : options[:icon_url]
post_message(
channel: channel,
username: username,
attachments: [mattermost_attachment],
icon_url: icon_url,
fail_on_error: false,
)
end
def post_message(channel:, username:, attachments:, icon_url:, fail_on_error:)
@notifier.post_incoming_webhook(
channel: channel,
username: username,
icon_url: icon_url,
attachments: attachments
)
UI.success("Successfully sent Mattermost notification")
rescue => error
UI.error("Exception: #{error}")
message = "Error posting Mattermost message, maybe the integration has no permission to post to this channel? Try removing the channel parameter in your Fastfile."
if fail_on_error
UI.user_error!(message)
else
UI.error(message)
end
end
def self.generate_mattermost_attachments(options)
color = (options[:success] ? 'good' : 'danger')
should_add_payload = ->(payload_name) { options[:default_payloads].nil? || options[:default_payloads].join(" ").include?(payload_name.to_s) }
mattermost_attachment = {
fallback: options[:message],
pretext: options[:pretext],
text: options[:message],
color: color,
fields: []
}
# custom user payloads
mattermost_attachment[:fields] += options[:payload].map do |k, v|
{
title: k.to_s,
value: Fastlane::Notification::Mattermost::LinkFormatter.format(v.to_s),
short: false
}
end
# Add the lane to the Mattermost message
# This might be nil, if mattermost is called as "one-off" action
if should_add_payload[:lane] && Actions.lane_context[Actions::SharedValues::LANE_NAME]
mattermost_attachment[:fields] << {
title: 'Lane',
value: Actions.lane_context[Actions::SharedValues::LANE_NAME],
short: true
}
end
# test_result
if should_add_payload[:test_result]
mattermost_attachment[:fields] << {
title: 'Result',
value: (options[:success] ? 'Success' : 'Error'),
short: true
}
end
# git branch
if Actions.git_branch && should_add_payload[:git_branch]
mattermost_attachment[:fields] << {
title: 'Git Branch',
value: Actions.git_branch,
short: true
}
end
# git_author
if Actions.git_author_email && should_add_payload[:git_author]
if FastlaneCore::Env.truthy?('FASTLANE_MATTERMOST_HIDE_AUTHOR_ON_SUCCESS') && options[:success]
# We only show the git author if the build failed
else
mattermost_attachment[:fields] << {
title: 'Git Author',
value: Actions.git_author_email,
short: true
}
end
end
# last_git_commit
if Actions.last_git_commit_message && should_add_payload[:last_git_commit]
mattermost_attachment[:fields] << {
title: 'Git Commit',
value: Actions.last_git_commit_message,
short: false
}
end
# last_git_commit_hash
if Actions.last_git_commit_hash(true) && should_add_payload[:last_git_commit_hash]
mattermost_attachment[:fields] << {
title: 'Git Commit Hash',
value: Actions.last_git_commit_hash(short: true),
short: false
}
end
# merge additional properties
deep_merge(mattermost_attachment, options[:attachment_properties])
end
# As there is a text limit in the notifications, we are
# usually interested in the last part of the message
# e.g. for tests
def self.trim_message(message)
# We want the last 7000 characters, instead of the first 7000, as the error is at the bottom
start_index = [message.length - 7000, 0].max
message = message[start_index..-1]
message.gsub('\n', "\n")
end
# Adapted from https://stackoverflow.com/a/30225093/158525
def self.deep_merge(a, b)
merger = proc do |key, v1, v2|
Hash === v1 && Hash === v2 ?
v1.merge(v2, &merger) : Array === v1 && Array === v2 ?
v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2
end
a.merge(b, &merger)
end
end
def self.is_supported?(platform)
true
end
# As there is a text limit in the notifications, we are
# usually interested in the last part of the message
# e.g. for tests
def self.trim_message(message)
# We want the last 7000 characters, instead of the first 7000, as the error is at the bottom
start_index = [message.length - 7000, 0].max
message = message[start_index..-1]
message
end
def self.run(options)
require 'slack-notifier'
options[:message] = self.trim_message(options[:message].to_s || '')
options[:message] = Slack::Notifier::Util::LinkFormatter.format(options[:message])
username = options[:overwrite_webhook_username_and_icon] ? nil : options[:username]
icon_url = options[:overwrite_webhook_username_and_icon] ? nil : options[:icon_url]
if options[:channel].to_s.length > 0
channel = options[:channel]
channel = ('#' + channel) unless ['#', '@'].include?(channel[0]) # send message to channel by default
end
notifier = Slack::Notifier.new(options[:mattermost_url], channel: channel, username: username)
mattermost_attachment = generate_mattermost_attachments(options)
return [notifier, mattermost_attachment] if Helper.is_test? # tests will verify the mattermost attachments and other properties
results = notifier.ping '',
icon_url: icon_url,
attachments: [mattermost_attachment]
result = results.first
if result.code.to_i == 200
UI.success('Successfully sent Mattermost notification')
else
UI.verbose(result)
UI.user_error!("Error pushing Mattermost message, maybe the integration has no permission to post on this channel? Try removing the channel parameter in your Fastfile.")
end
Runner.new(options[:mattermost_url]).run(options)
end
def self.description
@@ -119,7 +282,7 @@ module Fastlane
end
def self.author
"Mattermost base on the Slack action by KrauseFx"
"Mattermost"
end
def self.example_code
@@ -153,96 +316,7 @@ module Fastlane
end
def self.details
"Create an Incoming WebHook and export this as `MATTERMOST_URL`. Can send a message to **channel** (by default), a direct message to **@username** or a message to a private group **group** with success (green) or failure (red) status."
end
#####################################################
# @!group Helper
#####################################################
def self.generate_mattermost_attachments(options)
color = (options[:success] ? 'good' : 'danger')
should_add_payload = ->(payload_name) { options[:default_payloads].nil? || options[:default_payloads].join(" ").include?(payload_name.to_s) }
mattermost_attachment = {
fallback: options[:message],
pretext: options[:pretext],
text: options[:message],
color: color,
fields: []
}
# custom user payloads
mattermost_attachment[:fields] += options[:payload].map do |k, v|
{
title: k.to_s,
value: Slack::Notifier::LinkFormatter.format(v.to_s),
short: false
}
end
# Add the lane to the Mattermost message
# This might be nil, if mattermost is called as "one-off" action
if should_add_payload[:lane] && Actions.lane_context[Actions::SharedValues::LANE_NAME]
mattermost_attachment[:fields] << {
title: 'Lane',
value: Actions.lane_context[Actions::SharedValues::LANE_NAME],
short: true
}
end
# test_result
if should_add_payload[:test_result]
mattermost_attachment[:fields] << {
title: 'Result',
value: (options[:success] ? 'Success' : 'Error'),
short: true
}
end
# git branch
if Actions.git_branch && should_add_payload[:git_branch]
mattermost_attachment[:fields] << {
title: 'Git Branch',
value: Actions.git_branch,
short: true
}
end
# git_author
if Actions.git_author_email && should_add_payload[:git_author]
if FastlaneCore::Env.truthy?('FASTLANE_MATTERMOST_HIDE_AUTHOR_ON_SUCCESS') && options[:success]
# We only show the git author if the build failed
else
mattermost_attachment[:fields] << {
title: 'Git Author',
value: Actions.git_author_email,
short: true
}
end
end
# last_git_commit
if Actions.last_git_commit_message && should_add_payload[:last_git_commit]
mattermost_attachment[:fields] << {
title: 'Git Commit',
value: Actions.last_git_commit_message,
short: false
}
end
# merge additional properties
deep_merge(mattermost_attachment, options[:attachment_properties])
end
# Adapted from https://stackoverflow.com/a/30225093/158525
def self.deep_merge(a, b)
merger = proc do |key, v1, v2|
Hash === v1 && Hash === v2 ?
v1.merge(v2, &merger) : Array === v1 && Array === v2 ?
v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2
end
a.merge(b, &merger)
"Create an Incoming WebHook and export this as `MATTERMOST_WEBHOOK_URL`. Can send a message to **channel** (by default), a direct message to **@username** or a message to a private group **group** with success (green) or failure (red) status."
end
end
end

View File

@@ -86,8 +86,8 @@ function setup() {
fi
echo "Installing Fastane"
if !gem list bundler -i --version 2.3.6 > /dev/null 2>&1; then
gem install bundler --versio 2.3.6
if !gem list bundler -i --version 2.3.26 > /dev/null 2>&1; then
gem install bundler --versio 2.3.26
fi
cd fastlane && bundle install && cd .. || exit 1
fi

View File

@@ -11,9 +11,9 @@ function cocoapods() {
}
if [[ "$OSTYPE" == "darwin"* ]]; then
if !(gem list bundler -i --version 2.3.6) > /dev/null 2>&1; then
if !(gem list bundler -i --version 2.3.26) > /dev/null 2>&1; then
echo "Installing Bundler"
gem install bundler --version 2.3.6 || exit 1
gem install bundler --version 2.3.26 || exit 1
fi
if !(gem list cocoapods -i --version 1.11.3) > /dev/null 2>&1; then