github-pages-health-check 0.6.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +3 -0
  7. data/README.md +83 -0
  8. data/github-pages-health-check.gemspec +28 -0
  9. data/lib/github-pages-health-check.rb +18 -245
  10. data/lib/github-pages-health-check/checkable.rb +62 -0
  11. data/lib/github-pages-health-check/cloudflare.rb +11 -7
  12. data/lib/github-pages-health-check/domain.rb +260 -0
  13. data/lib/github-pages-health-check/error.rb +7 -6
  14. data/lib/github-pages-health-check/errors.rb +13 -0
  15. data/lib/github-pages-health-check/errors/build_error.rb +8 -0
  16. data/lib/github-pages-health-check/errors/deprecated_ip_error.rb +11 -0
  17. data/lib/github-pages-health-check/errors/invalid_a_record_error.rb +11 -0
  18. data/lib/github-pages-health-check/errors/invalid_cname_error.rb +11 -0
  19. data/lib/github-pages-health-check/errors/invalid_dns_error.rb +11 -0
  20. data/lib/github-pages-health-check/errors/invalid_domain_error.rb +11 -0
  21. data/lib/github-pages-health-check/errors/invalid_repository_error.rb +11 -0
  22. data/lib/github-pages-health-check/errors/missing_access_token_error.rb +11 -0
  23. data/lib/github-pages-health-check/errors/not_served_by_pages_error.rb +11 -0
  24. data/lib/github-pages-health-check/printer.rb +71 -0
  25. data/lib/github-pages-health-check/repository.rb +74 -0
  26. data/lib/github-pages-health-check/site.rb +32 -0
  27. data/lib/github-pages-health-check/version.rb +3 -3
  28. data/script/bootstrap +5 -0
  29. data/script/check +11 -0
  30. data/script/check-cloudflare-ips +17 -0
  31. data/script/cibuild +9 -0
  32. data/script/console +5 -0
  33. data/script/release +42 -0
  34. data/script/test +2 -0
  35. data/script/update-cloudflare-ips +14 -0
  36. metadata +60 -7
  37. data/lib/github-pages-health-check/errors/deprecated_ip.rb +0 -9
  38. data/lib/github-pages-health-check/errors/invalid_a_record.rb +0 -9
  39. data/lib/github-pages-health-check/errors/invalid_cname.rb +0 -9
  40. data/lib/github-pages-health-check/errors/invalid_dns.rb +0 -9
  41. data/lib/github-pages-health-check/errors/not_served_by_pages.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6a76a2726cf101e5e0b31295968533bc118eca9b
4
- data.tar.gz: 78b1175bfb6c4de74c3a8a4303ab25a644805d86
3
+ metadata.gz: f39bbd059617a66074ecfefd2e099a8642f8f02a
4
+ data.tar.gz: 3d515c8fa894e296b042c23ca6729849f1fcc114
5
5
  SHA512:
6
- metadata.gz: 4d121a12f58aabe94583eac1561bd76e87805ce0712aada99c31d84918c02717a46a226191b8a409adecb23f55c5007d7f23e7929099f34f3a5966b7e1dfd076
7
- data.tar.gz: c25ace49dcb5c559791dc471b5ef847f0fb4a9b3d2a7718b21c489fbee01ec31e9bce61001fac55646932562b10f5bcdbe919a2dcd9681988fe52e7751c0905e
6
+ metadata.gz: 4687d324883562a5424f698643b1beed8bd737581e8d07ba0a5090990016dd68409790c4f075ca96e4fccef7c49b94ae0063cd7eaa0aa1cd3313e0216c55aac9
7
+ data.tar.gz: d3545668fa5b774dbf5b7d74ecbb1fc37ea97d36e55242f32221e087ba1bca1cc26f315126d4b9b3059a806f99175774c2b777918e4a2f4f87c4168ca6ee93eb
@@ -0,0 +1,6 @@
1
+ /*.gem
2
+ *.lock
3
+ .bundle
4
+ vendor/gems
5
+ /bin
6
+ .env
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1 @@
1
+ 2.1.7-github
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ script: "script/cibuild"
3
+ install:
4
+ - sudo bash -c 'echo search github.com >> /etc/resolv.conf'
5
+ cache: bundler
6
+ rvm: 2.1.7
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,83 @@
1
+ # GitHub Pages Health Check
2
+
3
+ *Checks your GitHub Pages site for common DNS configuration issues*
4
+
5
+ [![Build Status](https://travis-ci.org/github/pages-health-check.svg)](https://travis-ci.org/github/pages-health-check) [![Gem Version](https://badge.fury.io/rb/github-pages-health-check.svg)](http://badge.fury.io/rb/github-pages-health-check)
6
+
7
+ ## Installation
8
+
9
+ `gem install github-pages-health-check`
10
+
11
+ ## Usage
12
+
13
+ ### Basic Usage
14
+
15
+ ```ruby
16
+ > check = GitHubPages::HealthCheck::Site.new("choosealicense.com")
17
+ => #<GitHubPages::HealthCheck::Site @domain="choosealicense.com" valid?=true>
18
+ > check.valid?
19
+ => true
20
+ ```
21
+
22
+ ### An invalid domain
23
+
24
+ ```ruby
25
+ > check = GitHubPages::HealthCheck::Site.new("foo.github.com")
26
+ > check.valid?
27
+ => false
28
+ > check.valid!
29
+ raises GitHubPages::HealthCheck::Errors::InvalidCNAMEError
30
+ ```
31
+
32
+
33
+ ### Retrieving specific checks
34
+
35
+ ``` ruby
36
+ > check.domain.should_be_a_record?
37
+ => true
38
+ > check.domain.a_record?
39
+ => true
40
+ ```
41
+
42
+ ### Getting checks in bulk
43
+
44
+ ```ruby
45
+ > check.to_hash
46
+ => {
47
+ :cloudflare_ip?=>false,
48
+ :old_ip_address?=>false,
49
+ :a_record?=>true,
50
+ :cname_record?=>false,
51
+ :valid_domain?=>true,
52
+ :apex_domain?=>true,
53
+ :should_be_a_record?=>true,
54
+ :pointed_to_github_user_domain?=>false,
55
+ :pointed_to_github_pages_ip?=>false,
56
+ :pages_domain?=>false,
57
+ :valid?=>true
58
+ }
59
+ > check.to_json
60
+ => "{\"cloudflare_ip?\":false,\"old_ip_address?\":false,\"a_record?\":true,\"cname_record?\":false,\"valid_domain?\":true,\"apex_domain?\":true,\"should_be_a_record?\":true,\"pointed_to_github_user_domain?\":false,\"pointed_to_github_pages_ip?\":false,\"pages_domain?\":false,\"valid?\":true}"
61
+ ```
62
+
63
+ ### Getting the reason a domain is invalid
64
+
65
+ ```ruby
66
+ > check = GitHubPages::HealthCheck::Site.new "developer.facebook.com"
67
+ > check.valid?
68
+ => false
69
+ > check.reason
70
+ => #<GitHubPages::HealthCheck::InvalidCNAME>
71
+ > check.reason.message
72
+ => "CNAME does not point to GitHub Pages"
73
+ ```
74
+
75
+ ### Repository checks
76
+
77
+ Repository checks require a personal access or OAuth token with `repo` or scope. This can be passed as the second argument to the Site or Repository constructors like so:
78
+
79
+ ```ruby
80
+ check = GitHubPages::HealthCheck::Site.new "github/pages-health-check", access_token: "1234
81
+ ```
82
+
83
+ You can also set `OCTOKIT_ACCESS_TOKEN` as an environmental variable, or via a `.env` file in your working directory.
@@ -0,0 +1,28 @@
1
+ require File.expand_path("../lib/github-pages-health-check/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.required_ruby_version = ">= 1.9.3"
5
+
6
+ s.name = "github-pages-health-check"
7
+ s.version = GitHubPages::HealthCheck::VERSION
8
+ s.summary = "Checks your GitHub Pages site for commons DNS configuration issues"
9
+ s.description = "Checks your GitHub Pages site for commons DNS configuration issues."
10
+ s.authors = "GitHub, Inc."
11
+ s.email = "support@github.com"
12
+ s.homepage = "https://github.com/github/github-pages-health-check"
13
+ s.license = "MIT"
14
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ s.require_paths = ['lib']
16
+
17
+ s.add_dependency("net-dns", "~> 0.8")
18
+ s.add_dependency("public_suffix", "~> 1.4")
19
+ s.add_dependency("typhoeus", "~> 0.7")
20
+ s.add_dependency("addressable", "~> 2.3")
21
+ s.add_dependency("octokit", "~> 4.0")
22
+
23
+ s.add_development_dependency("rspec", "~> 3.0")
24
+ s.add_development_dependency("pry", "~> 0.10")
25
+ s.add_development_dependency("gem-release", "~> 0.7")
26
+ s.add_development_dependency("webmock", "~> 1.21")
27
+ s.add_development_dependency("dotenv", "~> 1.0")
28
+ end
@@ -8,30 +8,25 @@ require "net/http"
8
8
  require "typhoeus"
9
9
  require "resolv"
10
10
  require "timeout"
11
+ require "octokit"
11
12
  require_relative "github-pages-health-check/version"
12
- require_relative "github-pages-health-check/cloudflare"
13
- require_relative "github-pages-health-check/error"
14
- require_relative "github-pages-health-check/errors/deprecated_ip"
15
- require_relative "github-pages-health-check/errors/invalid_a_record"
16
- require_relative "github-pages-health-check/errors/invalid_cname"
17
- require_relative "github-pages-health-check/errors/invalid_dns"
18
- require_relative "github-pages-health-check/errors/not_served_by_pages"
19
13
 
20
- class GitHubPages
21
- class HealthCheck
22
-
23
- attr_accessor :domain
14
+ if File.exists?(File.expand_path "../.env", File.dirname(__FILE__))
15
+ require 'dotenv'
16
+ Dotenv.load
17
+ end
24
18
 
25
- LEGACY_IP_ADDRESSES = %w[
26
- 207.97.227.245
27
- 204.232.175.78
28
- 199.27.73.133
29
- ]
19
+ module GitHubPages
20
+ module HealthCheck
30
21
 
31
- CURRENT_IP_ADDRESSES = %w[
32
- 192.30.252.153
33
- 192.30.252.154
34
- ]
22
+ autoload :CloudFlare, "github-pages-health-check/cloudflare"
23
+ autoload :Error, "github-pages-health-check/error"
24
+ autoload :Errors, "github-pages-health-check/errors"
25
+ autoload :Checkable, "github-pages-health-check/checkable"
26
+ autoload :Domain, "github-pages-health-check/domain"
27
+ autoload :Repository, "github-pages-health-check/repository"
28
+ autoload :Site, "github-pages-health-check/site"
29
+ autoload :Printer, "github-pages-health-check/printer"
35
30
 
36
31
  # DNS and HTTP timeout, in seconds
37
32
  TIMEOUT = 10
@@ -46,238 +41,16 @@ class GitHubPages
46
41
  }
47
42
  }
48
43
 
49
- def initialize(domain)
50
- @domain = host_from_uri(domain)
51
- end
52
-
53
- def cloudflare_ip?
54
- dns.all? do |answer|
55
- answer.class == Net::DNS::RR::A && CloudFlare.controls_ip?(answer.address)
56
- end if dns?
57
- end
58
-
59
- # Does this non-GitHub-pages domain proxy a GitHub Pages site?
60
- #
61
- # This can be:
62
- # 1. A Cloudflare-owned IP address
63
- # 2. A site that returns GitHub.com server headers, but isn't CNAME'd to a GitHub domain
64
- # 3. A site that returns GitHub.com server headers, but isn't CNAME'd to a GitHub IP
65
- def proxied?
66
- return unless dns?
67
- return true if cloudflare_ip?
68
- return false if pointed_to_github_pages_ip? || pointed_to_github_user_domain?
69
- served_by_pages?
70
- end
71
-
72
- # Returns an array of DNS answers
73
- def dns
74
- if @dns.nil?
75
- begin
76
- @dns = Timeout::timeout(TIMEOUT) do
77
- without_warnings do
78
- Net::DNS::Resolver.start(absolute_domain).answer if domain
79
- end
80
- end
81
- @dns ||= false
82
- rescue Exception
83
- @dns = false
84
- end
85
- end
86
- @dns || nil
87
- end
88
-
89
- # Are we even able to get the DNS record?
90
- def dns?
91
- !dns.nil? && !dns.empty?
92
- end
93
- alias_method :dns_resolves?, :dns
94
-
95
- # Does this domain have *any* A record that points to the legacy IPs?
96
- def old_ip_address?
97
- dns.any? do |answer|
98
- answer.class == Net::DNS::RR::A && LEGACY_IP_ADDRESSES.include?(answer.address.to_s)
99
- end if dns?
100
- end
101
-
102
- # Is this domain's first response an A record?
103
- def a_record?
104
- dns.first.class == Net::DNS::RR::A if dns?
105
- end
106
-
107
- # Is this domain's first response a CNAME record?
108
- def cname_record?
109
- dns.first.class == Net::DNS::RR::CNAME if dns?
110
- end
111
-
112
- # Is this a valid domain that PublicSuffix recognizes?
113
- # Used as an escape hatch to prevent false positives on DNS checkes
114
- def valid_domain?
115
- PublicSuffix.valid? domain
116
- end
117
-
118
- # Is this domain an apex domain, meaning a CNAME would be innapropriate
119
- def apex_domain?
120
- return @apex_domain if defined?(@apex_domain)
121
-
122
- answers = Resolv::DNS.open { |dns|
123
- dns.getresources(absolute_domain, Resolv::DNS::Resource::IN::NS)
124
- }
125
-
126
- @apex_domain = answers.any?
127
- end
128
-
129
- # Should the domain be an apex record?
130
- def should_be_a_record?
131
- !pages_domain? && apex_domain?
132
- end
133
-
134
- # Is the domain's first response a CNAME to a pages domain?
135
- def pointed_to_github_user_domain?
136
- dns.first.class == Net::DNS::RR::CNAME && pages_domain?(dns.first.cname.to_s) if dns?
137
- end
138
-
139
- # Is the domain's first response an A record to a valid GitHub Pages IP?
140
- def pointed_to_github_pages_ip?
141
- dns.first.class == Net::DNS::RR::A && CURRENT_IP_ADDRESSES.include?(dns.first.value) if dns?
142
- end
143
-
144
- # Is the given cname a pages domain?
145
- #
146
- # domain - the domain to check, generaly the target of a cname
147
- def pages_domain?(domain = domain())
148
- !!domain.match(/^[\w-]+\.github\.(io|com)\.?$/i)
149
- end
150
-
151
- # Is this domain owned by GitHub?
152
- def github_domain?
153
- !!domain.match(/\.github\.com$/)
154
- end
155
-
156
- def to_hash
157
- {
158
- :uri => uri.to_s,
159
- :dns_resolves? => dns?,
160
- :proxied? => proxied?,
161
- :cloudflare_ip? => cloudflare_ip?,
162
- :old_ip_address? => old_ip_address?,
163
- :a_record? => a_record?,
164
- :cname_record? => cname_record?,
165
- :valid_domain? => valid_domain?,
166
- :apex_domain? => apex_domain?,
167
- :should_be_a_record? => should_be_a_record?,
168
- :pointed_to_github_user_domain? => pointed_to_github_user_domain?,
169
- :pointed_to_github_pages_ip? => pointed_to_github_pages_ip?,
170
- :pages_domain? => pages_domain?,
171
- :served_by_pages? => served_by_pages?,
172
- :valid? => valid?,
173
- :reason => reason
174
- }
175
- end
176
- alias_method :to_h, :to_hash
177
-
178
- def served_by_pages?
179
- return @served_by_pages if defined? @served_by_pages
180
- @served_by_pages = begin
181
- response = Typhoeus.head(uri, TYPHOEUS_OPTIONS)
182
- # Workaround for webmock not playing nicely with Typhoeus redirects
183
- # See https://github.com/bblimke/webmock/issues/237
184
- if response.mock? && response.headers["Location"]
185
- response = Typhoeus.head(response.headers["Location"], TYPHOEUS_OPTIONS)
186
- end
187
-
188
- return false unless response.mock? || response.return_code == :ok
189
- return true if response.headers["Server"] == "GitHub.com"
190
-
191
- # Typhoeus mangles the case of the header, compare insensitively
192
- response.headers.any? { |k,v| k =~ /X-GitHub-Request-Id/i }
193
- end
194
- end
195
-
196
- def to_json
197
- to_hash.to_json
198
- end
199
-
200
- # Runs all checks, raises an error if invalid
201
- def check!
202
- raise InvalidDNS unless dns?
203
- return if proxied?
204
- raise DeprecatedIP if a_record? && old_ip_address?
205
- raise InvalidARecord if valid_domain? && a_record? && !should_be_a_record?
206
- raise InvalidCNAME if valid_domain? && !github_domain? && !apex_domain? && !pointed_to_github_user_domain?
207
- raise NotServedByPages unless served_by_pages?
208
- true
209
- end
210
- alias_method :valid!, :check!
211
-
212
- # Runs all checks, returns true if valid, otherwise false
213
- def valid?
214
- check!
215
- true
216
- rescue
217
- false
218
- end
219
-
220
- # Return the error, if any
221
- def reason
222
- check!
223
- nil
224
- rescue GitHubPages::HealthCheck::Error => e
225
- e
226
- end
227
-
228
- def inspect
229
- "#<GitHubPages::HealthCheck @domain=\"#{domain}\" valid?=#{valid?}>"
230
- end
231
-
232
- def to_s
233
- to_hash.inject(Array.new) do |all, pair|
234
- all.push pair.join(": ")
235
- end.join("\n")
236
- end
237
-
238
- private
239
-
240
44
  # surpress warn-level feedback due to unsupported record types
241
- def without_warnings(&block)
45
+ def self.without_warnings(&block)
242
46
  warn_level, $VERBOSE = $VERBOSE, nil
243
47
  result = block.call
244
48
  $VERBOSE = warn_level
245
49
  result
246
50
  end
247
51
 
248
- # Adjust `domain` so that it won't be searched for with /etc/resolv.conf's search rules.
249
- #
250
- # GitHubPages::HealthCheck.new("anything.io").absolute_domain
251
- # => "anything.io."
252
- def absolute_domain
253
- domain.end_with?(".") ? domain : "#{domain}."
254
- end
255
-
256
- def scheme
257
- @scheme ||= github_domain? ? "https" : "http"
258
- end
259
-
260
- def uri
261
- @uri ||= Addressable::URI.new(:host => domain, :scheme => scheme, :path => "/").normalize
262
- end
263
-
264
- # Parse the URI. Accept either domain names or full URI's.
265
- # Used by the initializer so we can be more flexible with inputs.
266
- #
267
- # domain - a URI or domain name.
268
- #
269
- # Examples
270
- #
271
- # host_from_uri("benbalter.github.com")
272
- # # => 'benbalter.github.com'
273
- # host_from_uri("https://benbalter.github.com")
274
- # # => 'benbalter.github.com'
275
- # host_from_uri("benbalter.github.com/help-me-im-a-path/")
276
- # # => 'benbalter.github.com'
277
- #
278
- # Return the hostname.
279
- def host_from_uri(domain)
280
- Addressable::URI.parse(domain).host || Addressable::URI.parse("http://#{domain}").host
52
+ def self.check(repository_or_domain, access_token: nil)
53
+ Site.new repository_or_domain, access_token: access_token
281
54
  end
282
55
  end
283
56
  end
@@ -0,0 +1,62 @@
1
+ module GitHubPages
2
+ module HealthCheck
3
+ class Checkable
4
+
5
+ # Array of symbolized methods to be included in the output hash
6
+ HASH_METHODS = []
7
+
8
+ def check!
9
+ raise "Not implemented"
10
+ end
11
+ alias_method :valid!, :check!
12
+
13
+ # Runs all checks, returns true if valid, otherwise false
14
+ def valid?
15
+ check!
16
+ true
17
+ rescue GitHubPages::HealthCheck::Error
18
+ false
19
+ end
20
+
21
+ # Returns the reason the check failed, if any
22
+ def reason
23
+ check!
24
+ nil
25
+ rescue GitHubPages::HealthCheck::Error => e
26
+ e
27
+ end
28
+
29
+ def to_hash
30
+ @hash ||= begin
31
+ hash = {}
32
+ self.class::HASH_METHODS.each do |method|
33
+ hash[method] = public_send(method)
34
+ end
35
+ hash
36
+ end
37
+ end
38
+ alias_method :[], :to_hash
39
+ alias_method :to_h, :to_hash
40
+
41
+ def to_json
42
+ require 'json'
43
+ to_hash.to_json
44
+ end
45
+
46
+ def to_s
47
+ printer.simple_string
48
+ end
49
+
50
+ def to_s_pretty
51
+ printer.pretty_print
52
+ end
53
+ alias_method :pretty_print, :to_s_pretty
54
+
55
+ private
56
+
57
+ def printer
58
+ @printer ||= GitHubPages::HealthCheck::Printer.new(self)
59
+ end
60
+ end
61
+ end
62
+ end