lib/github_service.rb
module GithubService
##
# GithubService is miq-bot's interface to the Github API. It acts as a
# wrapper around Octokit, delegating calls directly to the Octokit client as
# well as providing a space to keep useful augmentations of the interface for
# our own use cases.
#
# You can find the official Octokit documentation at http://octokit.github.io/octokit.rb
#
# Please check the documentation first before adding helper methods, as they
# may already be well handled by Octokit itself.
#
class << self
def bot_name
Settings.github_credentials.username
end
def add_comments(fq_repo_name, issue_number, comments)
Array(comments).each do |comment|
add_comment(fq_repo_name, issue_number, comment)
end
end
def delete_comments(fq_repo_name, comment_ids)
Array(comment_ids).each do |comment_id|
delete_comment(fq_repo_name, comment_id)
end
end
# Deletes the issue comments found by the provided block, then creates new
# issue comments from those provided.
def replace_comments(fq_repo_name, issue_number, new_comments)
raise "no block given" unless block_given?
to_delete = issue_comments(fq_repo_name, issue_number).select { |c| yield c }
delete_comments(fq_repo_name, to_delete.map(&:id))
add_comments(fq_repo_name, issue_number, new_comments)
end
def issue(*args)
Issue.new(service.issue(*args))
end
def list_issues(*args)
service.list_issues(*args).map { |issue| Issue.new(issue) }
end
alias issues list_issues
def search_issues(*args)
service.search_issues(*args).items.map { |issue| Issue.new(issue) }
end
def issue_comments(*args)
service.issue_comments(*args).map do |comment|
IssueComment.new(comment)
end
end
def repository_notifications(*args)
service.repository_notifications(*args).map do |notification|
Notification.new(notification)
end
end
# Overrides Octokit.add_labels_to_an_issue
# Note: This method creates labels on the repo if they don't exist, and assumes
# that the labels being passed in have already been validated if you don't
# want that behavior.
#
# For example, the notification monitor responds to users about any labels
# being requested to be added that aren't valid, before calling this method.
#
def add_labels_to_an_issue(fq_repo_name, issue_number, requested_labels)
issue(fq_repo_name, issue_number).add_labels(requested_labels)
end
# Overrides Octokit.remove_label
# Github raises an exception if the label isn't present on the issue already.
# This removes that error, making it a no-op, as Issue checks for the labels presence first.
def remove_label(fq_repo_name, issue_number, label)
issue(fq_repo_name, issue_number).remove_label(label)
end
def labels(fq_name)
labels_cache[fq_name] ||= Set.new(service.labels(fq_name).map(&:name))
end
def valid_label?(fq_name, label_text)
labels(fq_name).include?(label_text)
end
def refresh_labels(fq_name)
labels_cache.delete(fq_name)
end
def milestones(fq_name)
milestones_cache[fq_name] ||= Hash[service.list_milestones(fq_name, :state => :all).map { |m| [m.title, m.number] }]
end
def valid_milestone?(fq_name, milestone)
milestones(fq_name).include?(milestone)
end
def refresh_milestones(fq_name)
milestones_cache.delete(fq_name)
end
def assignees(fq_name)
assignees_cache[fq_name] ||= Set.new(service.repo_assignees(fq_name).map(&:login))
end
def valid_assignee?(fq_name, user)
assignees(fq_name).include?(user)
end
def refresh_assignees(fq_name)
assignees_cache.delete(fq_name)
end
def username_lookup(username)
if username_lookup_cache.key?(username)
username_lookup_cache[username]
else
username_lookup_cache[username] ||= begin
case Net::HTTP.new("github.com", 443).tap { |h| h.use_ssl = true }.request_head("/#{username}")
when Net::HTTPNotFound then nil # invalid username
when Net::HTTPOK then service.user(username)[:id]
else
raise "Error on GitHub with username lookup"
end
end
end
end
private
def service
@service ||=
begin
require 'octokit'
unless Rails.env.test?
Octokit.configure do |c|
c.login = bot_name
c.password = Settings.github_credentials.password
c.auto_paginate = true
c.middleware = Faraday::RackBuilder.new do |builder|
builder.use GithubService::Response::RatelimitLogger
builder.use Octokit::Response::RaiseError
builder.use Octokit::Response::FeedParser
builder.adapter Faraday.default_adapter
end
end
end
Octokit::Client.new
end
end
def labels_cache
@labels_cache ||= {}
end
def milestones_cache
@milestones_cache ||= {}
end
def assignees_cache
@assignees_cache ||= {}
end
def username_lookup_cache
@username_lookup_cache ||= {}
end
def respond_to_missing?(method_name, include_private = false)
service.respond_to?(method_name, include_private)
end
def method_missing(method_name, *args, &block)
if service.respond_to?(method_name)
service.send(method_name, *args, &block)
else
super
end
end
end
end