File: client.rb

package info (click to toggle)
ruby-jekyll-github-metadata 2.15.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 712 kB
  • sloc: ruby: 2,355; javascript: 107; sh: 41; makefile: 6
file content (152 lines) | stat: -rw-r--r-- 4,473 bytes parent folder | download | duplicates (3)
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