1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
# frozen_string_literal: true
require "digest"
module Jekyll
module GitHubMetadata
class Client
InvalidMethodError = Class.new(NoMethodError)
BadCredentialsError = Class.new(StandardError)
# Whitelisted API calls.
API_CALLS = Set.new(%w(
repository
organization
user
repository?
pages
contributors
releases
latest_release
list_repos
organization_public_members
))
def initialize(options = nil)
@client = build_octokit_client(options)
end
def safe_require(gem_name)
require gem_name
true
rescue LoadError
false
end
def default_octokit_options
{
:api_endpoint => Jekyll::GitHubMetadata::Pages.api_url,
:web_endpoint => Jekyll::GitHubMetadata::Pages.github_url,
:auto_paginate => true,
}
end
def build_octokit_client(options = nil)
options ||= {}
options.merge!(pluck_auth_method) unless options.key?(:access_token)
Octokit::Client.new(default_octokit_options.merge(options))
end
def accepts_client_method?(method_name)
API_CALLS.include?(method_name.to_s) && @client.respond_to?(method_name)
end
def respond_to_missing?(method_name, include_private = false)
accepts_client_method?(method_name) || super
end
def method_missing(method_name, *args, &block)
method = method_name.to_s
if accepts_client_method?(method_name)
key = cache_key(method_name, args)
GitHubMetadata.log :debug, "Calling @client.#{method}(#{args.map(&:inspect).join(", ")})"
cache[key] ||= save_from_errors { @client.public_send(method_name, *args, &block) }
elsif @client.respond_to?(method_name)
raise InvalidMethodError, "#{method_name} is not whitelisted on #{inspect}"
else
super
end
end
# NOTE: Faraday's error classes has been promoted to under Faraday module from v1.0.0.
# This patch aims to prevent on locking specific version of Faraday.
FARADAY_FAILED_CONNECTION =
begin
Faraday::Error::ConnectionFailed
rescue NameError
Faraday::ConnectionFailed
end
def save_from_errors(default = false)
unless internet_connected?
GitHubMetadata.log :warn, "No internet connection. GitHub metadata may be missing or incorrect."
return default
end
yield @client
rescue Octokit::Unauthorized
raise BadCredentialsError, "The GitHub API credentials you provided aren't valid."
rescue FARADAY_FAILED_CONNECTION, Octokit::TooManyRequests => e
GitHubMetadata.log :warn, e.message
default
rescue Octokit::NotFound
default
end
def inspect
"#<#{self.class.name} @client=#{client_inspect} @internet_connected=#{internet_connected?}>"
end
def authenticated?
!@client.access_token.to_s.empty?
end
def internet_connected?
return @internet_connected if defined?(@internet_connected)
require "resolv"
begin
Resolv::DNS.open do |dns|
dns.timeouts = 2
dns.getaddress("api.github.com")
end
@internet_connected = true
rescue Resolv::ResolvError
@internet_connected = false
end
end
private
def client_inspect
if @client.nil?
"nil"
else
"#<#{@client.class.name} (#{"un" unless authenticated?}authenticated)>"
end
end
# rubocop:disable Metrics/CyclomaticComplexity
def pluck_auth_method
if ENV["JEKYLL_GITHUB_TOKEN"] || Octokit.access_token
{ :access_token => ENV["JEKYLL_GITHUB_TOKEN"] || Octokit.access_token }
elsif !ENV["NO_NETRC"] && File.exist?(File.join(ENV["HOME"], ".netrc")) && safe_require("netrc")
{ :netrc => true }
else
GitHubMetadata.log :warn, "No GitHub API authentication could be found." \
" Some fields may be missing or have incorrect data."
{}.freeze
end
end
# rubocop:enable Metrics/CyclomaticComplexity
def cache_key(method, *args)
Digest::SHA1.hexdigest(method.to_s + args.join(", "))
end
def cache
@cache ||= {}
end
end
end
end
|