Skip to content

Instantly share code, notes, and snippets.

@felginep
Last active August 10, 2020 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felginep/a04c0b772d532c200e81ef9c862ae424 to your computer and use it in GitHub Desktop.
Save felginep/a04c0b772d532c200e81ef9c862ae424 to your computer and use it in GitHub Desktop.
Automatically post a PR comment if a license warning is found
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
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
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
@felginep
Copy link
Author

felginep commented Aug 7, 2020

Use it like this:

bundle exec fastlane lint_pull_request issue_number:123

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment