Last active
August 10, 2020 14:21
-
-
Save felginep/a04c0b772d532c200e81ef9c862ae424 to your computer and use it in GitHub Desktop.
Automatically post a PR comment if a license warning is found
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'octokit' | |
module Fastlane | |
module Actions | |
class AddLicenseMessageAction < Action | |
def self.run(params) | |
repo_name = params[:repository_name] | |
api_token = params[:api_token] | |
issue_number = params[:issue_number] | |
client = Octokit::Client.new(:access_token => api_token) | |
files = client.pull_request_files(repo_name, issue_number) | |
if !pull_request_files_contains_podfile(files) | |
UI.message "No changes detected in your Podfile. Stopping here." | |
return | |
else | |
UI.message "Podfile detected in pull request files." | |
end | |
pull_request = client.pull_request(repo_name, issue_number) | |
podfile = podfile_content(client, repo_name, pull_request) | |
lockfile = lockfile_content(client, repo_name, pull_request) | |
changes = lockfile.detect_changes_with_podfile(podfile) | |
updated_pods = changes[:added] + changes[:changed] | |
if updated_pods.empty? | |
UI.message "No pods were added or updated. Stopping here." | |
return | |
else | |
UI.message "#{updated_pods.count} change(s) detected in Podfile (#{formatted_pods(updated_pods)})." | |
end | |
other_action.ad_licenselint(only: updated_pods) | |
summary = lane_context[:AD_LICENSE_LINT_SUMMARY] | |
if summary.empty? | |
UI.message "No license warning was detected. Stopping here." | |
return | |
else | |
UI.message "Found some warnings in licenses. Posting comment." | |
end | |
comment = message(updated_pods, repo_name, issue_number, summary) | |
client.add_comment(repo_name, issue_number, comment) | |
end | |
##################################################### | |
# @!group Helpers | |
##################################################### | |
def self.pull_request_files_contains_podfile(files) | |
filenames = files.map(&:filename) | |
filenames.include?("Podfile") && filenames.include?("Podfile.lock") | |
end | |
def self.podfile_content(client, repo_name, pull_request) | |
json = client.content(repo_name, path: "Podfile", query: { ref: pull_request.head.ref }) | |
write_to_file(Base64.decode64(json.content)) { |path| | |
Pod::Podfile.from_file(path) | |
} | |
end | |
def self.lockfile_content(client, repo_name, pull_request) | |
json = client.content(repo_name, path: "Podfile.lock", query: { ref: pull_request.base.ref }) | |
write_to_file(Base64.decode64(json.content)) { |path| | |
Pod::Lockfile.from_file(Pathname(path)) | |
} | |
end | |
def self.write_to_file(content) | |
result = nil | |
Tempfile.create { |f| | |
f.write(content) | |
f.rewind | |
result = yield(f.path) | |
} | |
result | |
end | |
def self.message(pods, repo_name, issue_number, summary) | |
comment = [] | |
comment << "*We detected changes in your [Podfile](#{podfile_url(repo_name, issue_number)}):* #{formatted_pods(pods)}." | |
comment << "" | |
comment << "Verify licenses in your project:" | |
comment << summary | |
comment.join("\n") | |
end | |
def self.formatted_pods(pods) | |
pods | |
.map { |s| "`#{s}`"} | |
.join(", ") | |
end | |
def self.podfile_url(repo_name, issue_number) | |
file_hash = Digest::MD5.hexdigest("Podfile") | |
"https://github.com/#{repo_name}/pull/#{issue_number}/files#diff-#{file_hash}" | |
end | |
##################################################### | |
# @!group Documentation | |
##################################################### | |
def self.description | |
"If a Podfile change is detected, this will create a new comment on Github if some licenses issues are detected." | |
end | |
def self.details | |
[ | |
"Creates a new comment on GitHub. You must provide your GitHub Personal token (get one from [https://github.com/settings/tokens/new](https://github.com/settings/tokens/new)), the repository name and issue number.", | |
"Out parameters provide the comment's id, which can be used for later editing and the comment link to GitHub." | |
].join("\n") | |
end | |
def self.available_options | |
[ | |
FastlaneCore::ConfigItem.new(key: :repository_name, | |
env_name: "AD_ADD_LICENSE_MESSAGE_REPOSITORY_NAME", | |
description: "The path to your repo, e.g. 'fastlane/fastlane'", | |
verify_block: proc do |value| | |
UI.user_error!("Please only pass the path, e.g. 'fastlane/fastlane'") if value.include?("github.com") | |
UI.user_error!("Please only pass the path, e.g. 'fastlane/fastlane'") if value.split('/').count != 2 | |
end), | |
FastlaneCore::ConfigItem.new(key: :api_token, | |
env_name: "AD_ADD_LICENSE_MESSAGE_API_TOKEN", | |
description: "Personal API Token for GitHub - generate one at https://github.com/settings/tokens", | |
sensitive: true, | |
code_gen_sensitive: true, | |
is_string: true, | |
default_value: ENV["GITHUB_API_TOKEN"], | |
default_value_dynamic: true, | |
optional: false), | |
FastlaneCore::ConfigItem.new(key: :issue_number, | |
env_name: "AD_ADD_LICENSE_MESSAGE_ISSUE_NUMBER", | |
description: "Pass in the issue number", | |
is_string: true, | |
optional: false), | |
] | |
end | |
def self.output | |
[] | |
end | |
def self.return_value | |
[ | |
"A hash containing all relevant information of this comment" | |
].join("\n") | |
end | |
def self.authors | |
["felginep"] | |
end | |
def self.is_supported?(platform) | |
true | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'octokit' | |
module Fastlane | |
module Actions | |
class AddLicenseMessageAction < Action | |
def self.run(params) | |
repo_name = params[:repository_name] | |
api_token = params[:api_token] | |
server_url = params[:server_url] | |
issue_number = params[:issue_number] | |
client = Octokit::Client.new(:access_token => api_token) | |
files = client.pull_request_files(repo_name, issue_number) | |
if !pull_request_files_contains_podfile(files) | |
UI.message "No changes detected in your Podfile. Stopping here." | |
return | |
else | |
UI.message "Podfile detected in pull request files." | |
end | |
pull_request = client.pull_request(repo_name, issue_number) | |
podfile = podfile_content(client, repo_name, pull_request) | |
lockfile = lockfile_content(client, repo_name, pull_request) | |
changes = lockfile.detect_changes_with_podfile(podfile) | |
updated_pods = changes[:added] + changes[:changed] | |
if updated_pods.empty? | |
UI.message "No pods were added or updated. Stopping here." | |
return | |
else | |
UI.message "#{updated_pods.count} change(s) detected in Podfile (#{updated_pods.join(", ")})." | |
end | |
other_action.ad_licenselint(only: updated_pods) | |
report = lane_context[:AD_LICENSE_LINT_REPORT] | |
pod_comments = pending_comments(client, repo_name, pull_request, report) | |
if pod_comments.empty? | |
UI.message "No license warning was detected. Stopping here." | |
return | |
else | |
UI.message "Found #{pod_comments.count} warning(s). Posting comments." | |
end | |
pod_comments.each { |comment| | |
post_podfile_comment( | |
client, | |
repo_name, | |
issue_number, | |
pull_request.head.sha, | |
comment | |
) | |
} | |
end | |
##################################################### | |
# @!group Helpers | |
##################################################### | |
def self.pending_comments(client, repo_name, pull_request, report) | |
podfile_content = podfile_string(client, repo_name, pull_request) | |
posted_comments = client.pull_request_comments(repo_name, pull_request.number) | |
report[:entries] | |
.map { |pod_report| | |
line = line_for_content(pod_report[:pod_name], podfile_content) | |
existing_comment = license_comment(posted_comments, line) | |
next nil unless existing_comment.nil? | |
{ comment: comment_for_infos(pod_report), line: line } | |
} | |
.compact | |
end | |
def self.license_comment(comments, line) | |
comments.find { |comment| | |
comment.path == "Podfile" && comment.body.include?("License linter") && comment.line == line + 1 | |
} | |
end | |
def self.comment_for_infos(infos) | |
comment = [] | |
comment << "*License linter*" | |
comment << "" | |
comment << "The license `#{infos[:license_name]}` for the pod [#{infos[:pod_name]}](#{infos[:source_url]}) has not been automatically validated." | |
comment << "Verify license below:" | |
comment << "<details>" | |
comment << "<summary>License</summary>" | |
comment << "" | |
comment << "```" | |
comment << infos[:license_content] | |
comment << "```" | |
comment << "</details>" | |
comment.join("\n") | |
end | |
def self.post_podfile_comment(client, repo, issue_number, commit, comment) | |
client.post( | |
"#{Octokit::Repository.path repo}/pulls/#{issue_number}/comments", | |
{ | |
commit_id: commit, | |
path: 'Podfile', | |
body: comment[:comment], | |
line: comment[:line] + 1, | |
side: 'RIGHT', | |
} | |
) | |
end | |
def self.line_for_content(subcontent, full_content) | |
lines = full_content.split("\n") | |
matching_line = nil | |
lines.each_with_index { |line_content, index| | |
matching_line = index if line_content.include?(subcontent) | |
} | |
matching_line | |
end | |
def self.pull_request_files_contains_podfile(files) | |
filenames = files.map(&:filename) | |
filenames.include?("Podfile") && filenames.include?("Podfile.lock") | |
end | |
def self.podfile_string(client, repo_name, pull_request) | |
json = client.content(repo_name, path: "Podfile", query: { ref: pull_request.head.ref }) | |
Base64.decode64(json.content) | |
end | |
def self.podfile_content(client, repo_name, pull_request) | |
write_to_file(podfile_string(client, repo_name, pull_request)) { |path| | |
Pod::Podfile.from_file(path) | |
} | |
end | |
def self.lockfile_content(client, repo_name, pull_request) | |
json = client.content(repo_name, path: "Podfile.lock", query: { ref: pull_request.base.ref }) | |
write_to_file(Base64.decode64(json.content)) { |path| | |
Pod::Lockfile.from_file(Pathname(path)) | |
} | |
end | |
def self.write_to_file(content) | |
result = nil | |
Tempfile.create { |f| | |
f.write(content) | |
f.rewind | |
result = yield(f.path) | |
} | |
result | |
end | |
##################################################### | |
# @!group Documentation | |
##################################################### | |
def self.description | |
"If a Podfile change is detected, this will create a new comment on Github if some licenses issues are detected." | |
end | |
def self.details | |
[ | |
"Creates a new comment on GitHub. You must provide your GitHub Personal token (get one from [https://github.com/settings/tokens/new](https://github.com/settings/tokens/new)), the repository name and issue number.", | |
"Out parameters provide the comment's id, which can be used for later editing and the comment link to GitHub." | |
].join("\n") | |
end | |
def self.available_options | |
[ | |
FastlaneCore::ConfigItem.new(key: :repository_name, | |
env_name: "AD_ADD_LICENSE_MESSAGE_REPOSITORY_NAME", | |
description: "The path to your repo, e.g. 'fastlane/fastlane'", | |
verify_block: proc do |value| | |
UI.user_error!("Please only pass the path, e.g. 'fastlane/fastlane'") if value.include?("github.com") | |
UI.user_error!("Please only pass the path, e.g. 'fastlane/fastlane'") if value.split('/').count != 2 | |
end), | |
FastlaneCore::ConfigItem.new(key: :server_url, | |
env_name: "AD_ADD_LICENSE_MESSAGE_SERVER_URL", | |
description: "The server url. e.g. 'https://your.internal.github.host/api/v3' (Default: 'https://api.github.com')", | |
default_value: "https://api.github.com", | |
optional: true, | |
verify_block: proc do |value| | |
UI.user_error!("Please include the protocol in the server url, e.g. https://your.github.server/api/v3") unless value.include?("//") | |
end), | |
FastlaneCore::ConfigItem.new(key: :api_token, | |
env_name: "AD_ADD_LICENSE_MESSAGE_API_TOKEN", | |
description: "Personal API Token for GitHub - generate one at https://github.com/settings/tokens", | |
sensitive: true, | |
code_gen_sensitive: true, | |
is_string: true, | |
default_value: ENV["GITHUB_API_TOKEN"], | |
default_value_dynamic: true, | |
optional: false), | |
FastlaneCore::ConfigItem.new(key: :issue_number, | |
env_name: "AD_ADD_LICENSE_MESSAGE_ISSUE_NUMBER", | |
description: "Pass in the issue number", | |
is_string: true, | |
optional: false), | |
] | |
end | |
def self.output | |
[] | |
end | |
def self.return_value | |
[ | |
"A hash containing all relevant information of this comment" | |
].join("\n") | |
end | |
def self.authors | |
["felginep"] | |
end | |
def self.is_supported?(platform) | |
true | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
lane :lint_pull_request do |options| | |
add_license_message( | |
repository_name: "felginep/my-ios-app", | |
api_token: ENV["GITHUB_TOKEN"], | |
issue_number: options[:issue_number] | |
) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use it like this: