health_check 2.5.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.travis.yml +162 -111
  4. data/CHANGELOG +36 -0
  5. data/README.rdoc +109 -43
  6. data/Rakefile +1 -1
  7. data/Vagrantfile +32 -0
  8. data/config/routes.rb +1 -1
  9. data/health_check.gemspec +7 -6
  10. data/lib/health_check.rb +51 -6
  11. data/lib/health_check/elasticsearch_health_check.rb +15 -0
  12. data/lib/health_check/health_check_controller.rb +32 -25
  13. data/lib/health_check/health_check_routes.rb +1 -1
  14. data/lib/health_check/middleware_health_check.rb +6 -1
  15. data/lib/health_check/rabbitmq_health_check.rb +16 -0
  16. data/lib/health_check/redis_health_check.rb +20 -7
  17. data/lib/health_check/s3_health_check.rb +9 -14
  18. data/lib/health_check/utils.rb +55 -33
  19. data/lib/health_check/version.rb +1 -1
  20. data/test/fake_smtp_server +143 -24
  21. data/test/init_variables +19 -1
  22. data/test/migrate/nine/9_create_countries.rb +1 -1
  23. data/test/migrate/twelve/011_create_roles.roles.rb +1 -1
  24. data/test/migrate/twelve/012_create_users.rb +2 -2
  25. data/test/migrate/twelve/9_create_countries.rb +1 -1
  26. data/test/provision_vagrant +103 -0
  27. data/test/rails_5.0.gemfile +10 -7
  28. data/test/rails_5.1.gemfile +34 -0
  29. data/test/rails_5.2.gemfile +34 -0
  30. data/test/rails_6.0.gemfile +30 -0
  31. data/test/rails_6.1.gemfile +29 -0
  32. data/test/rails_6.2.gemfile +30 -0
  33. data/test/rails_edge.gemfile +14 -9
  34. data/test/setup_railsapp +175 -95
  35. data/test/test_with_railsapp +152 -46
  36. data/test/testurl +9 -0
  37. metadata +45 -22
  38. data/test/rails_4.0.gemfile +0 -33
  39. data/test/rails_4.1.gemfile +0 -33
  40. data/test/rails_4.2.gemfile +0 -30
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ task :test do
8
8
  exec '/bin/bash', './test/test_with_railsapp'
9
9
  end
10
10
 
11
- task :default => :test
11
+ task default: :test
12
12
 
13
13
  begin
14
14
  gem 'rdoc'
data/Vagrantfile ADDED
@@ -0,0 +1,32 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ Vagrant.configure("2") do |config|
5
+ # For a complete reference, please see the online documentation at
6
+ # https://docs.vagrantup.com.
7
+
8
+ config.vm.box = "ubuntu/focal64"
9
+
10
+ # set auto_update to false, if you do NOT want to check the correct
11
+ # additions version when booting this machine
12
+ config.vbguest.auto_update = false
13
+
14
+ # do NOT download the iso file from a webserver
15
+ config.vbguest.no_remote = true
16
+
17
+ # provision with a shell script.
18
+ config.vm.provision "shell", path: "./test/provision_vagrant"
19
+
20
+ config.vm.provider "virtualbox" do |v|
21
+ # travis allocates 7.5 GB, but this is sufficient
22
+ v.memory = 2048
23
+ v.cpus = 2
24
+ end
25
+
26
+ # if File.file?('.git') && IO.read('.git') =~ %r{\Agitdir: (.+)/.git/worktrees.*}
27
+ # # Handle git worktrees ...
28
+ # path = $1
29
+ # config.vm.synced_folder path, path
30
+ # end
31
+
32
+ end
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  unless HealthCheck::Engine.routes_explicitly_defined
2
- Rails.application.routes.draw do
2
+ ::Rails.application.routes.draw do
3
3
  add_health_check_routes()
4
4
  end
5
5
  end
data/health_check.gemspec CHANGED
@@ -9,20 +9,21 @@ Gem::Specification.new do |gem|
9
9
  gem.required_rubygems_version = Gem::Requirement.new(">= 0") if gem.respond_to? :required_rubygems_version=
10
10
  gem.authors = ["Ian Heggie"]
11
11
  gem.email = ["ian@heggie.biz"]
12
- gem.summary = %q{Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard or uptime.openacs.org etc.}
12
+ gem.summary = %q{Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard etc.}
13
13
  gem.description = <<-EOF
14
- Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard or uptime.openacs.org etc.
14
+ Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard etc.
15
15
  EOF
16
16
  gem.homepage = "https://github.com/ianheggie/health_check"
17
+ gem.license = "MIT"
17
18
 
18
19
  gem.files = `git ls-files`.split($/)
19
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
21
  gem.extra_rdoc_files = [ "README.rdoc" ]
22
22
  gem.require_paths = ["lib"]
23
- gem.required_ruby_version = '>= 1.9.3'
24
- gem.add_dependency(%q<rails>, [">= 4.0"])
23
+ gem.required_ruby_version = '>= 2.2.2'
24
+ gem.add_dependency(%q<railties>, [">= 5.0"])
25
+ gem.add_development_dependency(%q<smarter_bundler>, [">= 0.1.0"])
25
26
  gem.add_development_dependency(%q<rake>, [">= 0.8.3"])
26
27
  gem.add_development_dependency(%q<shoulda>, ["~> 2.11.0"])
27
- gem.add_development_dependency(%q<bundler>, ["~> 1.2"])
28
+ gem.add_development_dependency(%q<bundler>, [">= 1.2"])
28
29
  end
data/lib/health_check.rb CHANGED
@@ -3,14 +3,22 @@
3
3
 
4
4
  module HealthCheck
5
5
 
6
- class Engine < Rails::Engine
7
- cattr_accessor :routes_explicitly_defined
6
+ class Engine < ::Rails::Engine
7
+ cattr_accessor :routes_explicitly_defined
8
8
  end
9
9
 
10
+ # Log level
11
+ mattr_accessor :log_level
12
+ self.log_level = 'info'
13
+
10
14
  # Text output upon success
11
15
  mattr_accessor :success
12
16
  self.success = "success"
13
17
 
18
+ # Text output upon failure
19
+ mattr_accessor :failure
20
+ self.failure = "health_check failed"
21
+
14
22
  # Timeout in seconds used when checking smtp server
15
23
  mattr_accessor :smtp_timeout
16
24
  self.smtp_timeout = 30.0
@@ -27,6 +35,10 @@ module HealthCheck
27
35
  mattr_accessor :http_status_for_ip_whitelist_error
28
36
  self.http_status_for_ip_whitelist_error = 403
29
37
 
38
+ # check remote_ip rather than ip for ip whitelist
39
+ mattr_accessor :accept_proxied_requests
40
+ self.accept_proxied_requests = false
41
+
30
42
  # ips allowed to perform requests
31
43
  mattr_accessor :origin_ip_whitelist
32
44
  self.origin_ip_whitelist = []
@@ -40,6 +52,10 @@ module HealthCheck
40
52
  mattr_accessor :buckets
41
53
  self.buckets = {}
42
54
 
55
+ # rabbitmq
56
+ mattr_accessor :rabbitmq_config
57
+ self.rabbitmq_config = {}
58
+
43
59
  # health check uri path
44
60
  mattr_accessor :uri
45
61
  self.uri = 'health_check'
@@ -53,8 +69,8 @@ module HealthCheck
53
69
  mattr_accessor :custom_checks
54
70
  mattr_accessor :full_checks
55
71
  mattr_accessor :standard_checks
56
- self.custom_checks = [ ]
57
- self.full_checks = ['database', 'migrations', 'custom', 'email', 'cache', 'redis-if-present', 'sidekiq-redis-if-present', 'resque-redis-if-present', 's3-if-present']
72
+ self.custom_checks = { }
73
+ self.full_checks = ['database', 'migrations', 'custom', 'email', 'cache', 'redis-if-present', 'sidekiq-redis-if-present', 'resque-redis-if-present', 's3-if-present', 'elasticsearch-if-present']
58
74
  self.standard_checks = [ 'database', 'migrations', 'custom', 'emailconf' ]
59
75
 
60
76
  # Middleware based checks
@@ -63,8 +79,35 @@ module HealthCheck
63
79
 
64
80
  mattr_accessor :installed_as_middleware
65
81
 
66
- def self.add_custom_check(&block)
67
- custom_checks << block
82
+ # Allow non-standard redis url and password
83
+ mattr_accessor :redis_url
84
+ self.redis_url = ENV['REDIS_URL']
85
+
86
+ mattr_accessor :redis_password
87
+ self.redis_password = 'some-password'
88
+
89
+ # Include the error in the response body.
90
+ # You should only do this where your /health_check endpoint is NOT open to the public internet
91
+ mattr_accessor :include_error_in_response_body
92
+ self.include_error_in_response_body = false
93
+
94
+ # used for on_failure and on_success
95
+ mattr_accessor :success_callbacks
96
+ mattr_accessor :failure_callbacks
97
+
98
+ def self.add_custom_check(name = 'custom', &block)
99
+ custom_checks[name] ||= [ ]
100
+ custom_checks[name] << block
101
+ end
102
+
103
+ def self.on_success(&block)
104
+ success_callbacks ||= [ ]
105
+ success_callbacks << block
106
+ end
107
+
108
+ def self.on_failure(&block)
109
+ failure_callbacks ||= [ ]
110
+ failure_callbacks << block
68
111
  end
69
112
 
70
113
  def self.setup
@@ -78,10 +121,12 @@ require 'health_check/base_health_check'
78
121
  require 'health_check/resque_health_check'
79
122
  require 'health_check/s3_health_check'
80
123
  require 'health_check/redis_health_check'
124
+ require 'health_check/elasticsearch_health_check'
81
125
  require 'health_check/sidekiq_health_check'
82
126
  require 'health_check/utils'
83
127
  require 'health_check/health_check_controller'
84
128
  require 'health_check/health_check_routes'
85
129
  require 'health_check/middleware_health_check'
130
+ require 'health_check/rabbitmq_health_check'
86
131
 
87
132
  # vi: sw=2 sm ai:
@@ -0,0 +1,15 @@
1
+ module HealthCheck
2
+ class ElasticsearchHealthCheck
3
+ extend BaseHealthCheck
4
+
5
+ def self.check
6
+ unless defined?(::Elasticsearch)
7
+ raise "Wrong configuration. Missing 'elasticsearch' gem"
8
+ end
9
+ res = ::Elasticsearch::Client.new.ping
10
+ res == true ? '' : "Elasticsearch returned #{res.inspect} instead of true"
11
+ rescue Exception => e
12
+ create_error 'elasticsearch', e.message
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,6 @@
1
- # Copyright (c) 2010-2013 Ian Heggie, released under the MIT license.
1
+ # Copyright (c) 2010-2021 Ian Heggie, released under the MIT license.
2
2
  # See MIT-LICENSE for details.
3
+ require "ipaddr"
3
4
 
4
5
  module HealthCheck
5
6
  class HealthCheckController < ActionController::Base
@@ -14,8 +15,8 @@ module HealthCheck
14
15
  if max_age > 1
15
16
  last_modified = Time.at((last_modified.to_f / max_age).floor * max_age).utc
16
17
  end
17
- public = (max_age > 1) && ! HealthCheck.basic_auth_username
18
- if stale?(:last_modified => last_modified, :public => public)
18
+ is_public = (max_age > 1) && ! HealthCheck.basic_auth_username
19
+ if stale?(last_modified: last_modified, public: is_public)
19
20
  checks = params[:checks] ? params[:checks].split('_') : ['standard']
20
21
  checks -= HealthCheck.middleware_checks if HealthCheck.installed_as_middleware
21
22
  begin
@@ -23,15 +24,25 @@ module HealthCheck
23
24
  rescue Exception => e
24
25
  errors = e.message.blank? ? e.class.to_s : e.message.to_s
25
26
  end
26
- response.headers['Cache-control'] = (public ? 'public' : 'private') + ', no-cache, must-revalidate' + (max_age > 0 ? ", max-age=#{max_age}" : '')
27
+ response.headers['Cache-Control'] = "must-revalidate, max-age=#{max_age}"
27
28
  if errors.blank?
28
- send_response nil, :ok, :ok
29
+ send_response true, nil, :ok, :ok
30
+ if HealthCheck.success_callbacks
31
+ HealthCheck.success_callbacks.each do |callback|
32
+ callback.call(checks)
33
+ end
34
+ end
29
35
  else
30
- msg = "health_check failed: #{errors}"
31
- send_response msg, HealthCheck.http_status_for_error_text, HealthCheck.http_status_for_error_object
36
+ msg = HealthCheck.include_error_in_response_body ? "#{HealthCheck.failure}: #{errors}" : nil
37
+ send_response false, msg, HealthCheck.http_status_for_error_text, HealthCheck.http_status_for_error_object
38
+
32
39
  # Log a single line as some uptime checkers only record that it failed, not the text returned
33
- if logger
34
- logger.info msg
40
+ msg = "#{HealthCheck.failure}: #{errors}"
41
+ logger.send(HealthCheck.log_level, msg) if logger && HealthCheck.log_level
42
+ if HealthCheck.failure_callbacks
43
+ HealthCheck.failure_callbacks.each do |callback|
44
+ callback.call(checks, msg)
45
+ end
35
46
  end
36
47
  end
37
48
  end
@@ -39,15 +50,14 @@ module HealthCheck
39
50
 
40
51
  protected
41
52
 
42
- def send_response(msg, text_status, obj_status)
43
- healthy = !msg
44
- msg ||= HealthCheck.success
45
- obj = { :healthy => healthy, :message => msg}
53
+ def send_response(healthy, msg, text_status, obj_status)
54
+ msg ||= healthy ? HealthCheck.success : HealthCheck.failure
55
+ obj = { healthy: healthy, message: msg}
46
56
  respond_to do |format|
47
- format.html { render plain_key => msg, :status => text_status, :content_type => 'text/plain' }
48
- format.json { render :json => obj, :status => obj_status }
49
- format.xml { render :xml => obj, :status => obj_status }
50
- format.any { render plain_key => msg, :status => text_status, :content_type => 'text/plain' }
57
+ format.html { render plain: msg, status: text_status, content_type: 'text/plain' }
58
+ format.json { render json: obj, status: obj_status }
59
+ format.xml { render xml: obj, status: obj_status }
60
+ format.any { render plain: msg, status: text_status, content_type: 'text/plain' }
51
61
  end
52
62
  end
53
63
 
@@ -59,11 +69,12 @@ module HealthCheck
59
69
  end
60
70
 
61
71
  def check_origin_ip
72
+ request_ipaddr = IPAddr.new(HealthCheck.accept_proxied_requests ? request.remote_ip : request.ip)
62
73
  unless HealthCheck.origin_ip_whitelist.blank? ||
63
- HealthCheck.origin_ip_whitelist.include?(request.ip)
64
- render plain_key => 'Health check is not allowed for the requesting IP',
65
- :status => HealthCheck.http_status_for_ip_whitelist_error,
66
- :content_type => 'text/plain'
74
+ HealthCheck.origin_ip_whitelist.any? { |addr| IPAddr.new(addr).include? request_ipaddr }
75
+ render plain: 'Health check is not allowed for the requesting IP',
76
+ status: HealthCheck.http_status_for_ip_whitelist_error,
77
+ content_type: 'text/plain'
67
78
  end
68
79
  end
69
80
 
@@ -72,9 +83,5 @@ module HealthCheck
72
83
  false
73
84
  end
74
85
 
75
- def plain_key
76
- # Rails 4.0 doesn't have :plain, but it is deprecated later on
77
- Rails.version < '4.1' ? :text : :plain
78
- end
79
86
  end
80
87
  end
@@ -8,7 +8,7 @@ module ActionDispatch::Routing
8
8
 
9
9
  def add_health_check_routes(prefix = nil)
10
10
  HealthCheck.uri = prefix if prefix
11
- match "#{HealthCheck.uri}(/:checks)(.:format)", :to => 'health_check/health_check#index', via: [:get, :post], :defaults => { :format => 'txt' }
11
+ match "#{HealthCheck.uri}(/:checks)(.:format)", controller: 'health_check/health_check', action: :index, via: [:get, :post], defaults: { format: 'txt' }
12
12
  end
13
13
 
14
14
  end
@@ -1,3 +1,7 @@
1
+ # Copyright (c) 2010-2021 Ian Heggie, released under the MIT license.
2
+ # See MIT-LICENSE for details.
3
+ require 'ipaddr'
4
+
1
5
  module HealthCheck
2
6
  class MiddlewareHealthcheck
3
7
 
@@ -59,7 +63,8 @@ module HealthCheck
59
63
  def ip_blocked(env)
60
64
  return false if HealthCheck.origin_ip_whitelist.blank?
61
65
  req = Rack::Request.new(env)
62
- unless HealthCheck.origin_ip_whitelist.include?(req.ip)
66
+ request_ipaddr = IPAddr.new(req.ip)
67
+ unless HealthCheck.origin_ip_whitelist.any? { |addr| IPAddr.new(addr).include? request_ipaddr }
63
68
  [ HealthCheck.http_status_for_ip_whitelist_error,
64
69
  { 'Content-Type' => 'text/plain' },
65
70
  [ 'Health check is not allowed for the requesting IP' ]
@@ -0,0 +1,16 @@
1
+ module HealthCheck
2
+ class RabbitMQHealthCheck
3
+ extend BaseHealthCheck
4
+ def self.check
5
+ unless defined?(::Bunny)
6
+ raise "Wrong configuration. Missing 'bunny' gem"
7
+ end
8
+ connection = Bunny.new(HealthCheck.rabbitmq_config)
9
+ connection.start
10
+ connection.close
11
+ ''
12
+ rescue Exception => e
13
+ create_error 'rabbitmq', e.message
14
+ end
15
+ end
16
+ end
@@ -1,15 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HealthCheck
2
4
  class RedisHealthCheck
3
5
  extend BaseHealthCheck
4
6
 
5
- def self.check
6
- unless defined?(::Redis)
7
- raise "Wrong configuration. Missing 'redis' gem"
7
+ class << self
8
+ def check
9
+ raise "Wrong configuration. Missing 'redis' gem" unless defined?(::Redis)
10
+
11
+ client.ping == 'PONG' ? '' : "Redis.ping returned #{res.inspect} instead of PONG"
12
+ rescue Exception => err
13
+ create_error 'redis', err.message
14
+ ensure
15
+ client.close if client.connected?
16
+ end
17
+
18
+ def client
19
+ @client ||= Redis.new(
20
+ {
21
+ url: HealthCheck.redis_url,
22
+ password: HealthCheck.redis_password
23
+ }.reject { |k, v| v.nil? }
24
+ )
8
25
  end
9
- res = ::Redis.new.ping
10
- res == 'PONG' ? '' : "Redis.ping returned #{res.inspect} instead of PONG"
11
- rescue Exception => e
12
- create_error 'redis', e.message
13
26
  end
14
27
  end
15
28
  end
@@ -5,7 +5,7 @@ module HealthCheck
5
5
  class << self
6
6
  def check
7
7
  unless defined?(::Aws)
8
- raise "Wrong configuration. Missing 'aws-sdk' gem"
8
+ raise "Wrong configuration. Missing 'aws-sdk' or 'aws-sdk-s3' gem"
9
9
  end
10
10
  return create_error 's3', 'Could not connect to aws' if aws_s3_client.nil?
11
11
  HealthCheck.buckets.each do |bucket_name, permissions|
@@ -27,19 +27,14 @@ module HealthCheck
27
27
 
28
28
  private
29
29
 
30
+ # We already assume you are using Rails. Let's also assume you have an initializer
31
+ # created for your Aws config. We will set the region here so you can use an
32
+ # instance profile and simply set the region in your environment.
30
33
  def configure_client
31
- return unless defined?(Rails)
34
+ ::Aws.config[:s3] = { force_path_style: true }
35
+ ::Aws.config[:region] ||= ENV['AWS_REGION'] || ENV['DEFAULT_AWS_REGION']
32
36
 
33
- aws_configuration = {
34
- region: Rails.application.secrets.aws_default_region,
35
- credentials: ::Aws::Credentials.new(
36
- Rails.application.secrets.aws_access_key_id,
37
- Rails.application.secrets.aws_secret_access_key
38
- ),
39
- force_path_style: true
40
- }
41
-
42
- ::Aws::S3::Client.new aws_configuration
37
+ ::Aws::S3::Client.new
43
38
  end
44
39
 
45
40
  def aws_s3_client
@@ -52,13 +47,13 @@ module HealthCheck
52
47
 
53
48
  def W(bucket)
54
49
  aws_s3_client.put_object(bucket: bucket,
55
- key: "healthcheck_#{Rails.application.class.parent_name}",
50
+ key: "healthcheck_#{::Rails.application.class.parent_name}",
56
51
  body: Time.new.to_s)
57
52
  end
58
53
 
59
54
  def D(bucket)
60
55
  aws_s3_client.delete_object(bucket: bucket,
61
- key: "healthcheck_#{Rails.application.class.parent_name}")
56
+ key: "healthcheck_#{::Rails.application.class.parent_name}")
62
57
  end
63
58
  end
64
59
  end
@@ -6,13 +6,13 @@ module HealthCheck
6
6
 
7
7
  @@default_smtp_settings =
8
8
  {
9
- :address => "localhost",
10
- :port => 25,
11
- :domain => 'localhost.localdomain',
12
- :user_name => nil,
13
- :password => nil,
14
- :authentication => nil,
15
- :enable_starttls_auto => true,
9
+ address: "localhost",
10
+ port: 25,
11
+ domain: 'localhost.localdomain',
12
+ user_name: nil,
13
+ password: nil,
14
+ authentication: nil,
15
+ enable_starttls_auto: true
16
16
  }
17
17
 
18
18
  cattr_accessor :default_smtp_settings
@@ -55,6 +55,8 @@ module HealthCheck
55
55
  errors << HealthCheck::RedisHealthCheck.check if defined?(::Redis)
56
56
  when 's3-if-present'
57
57
  errors << HealthCheck::S3HealthCheck.check if defined?(::Aws)
58
+ when 'elasticsearch-if-present'
59
+ errors << HealthCheck::ElasticsearchHealthCheck.check if defined?(::Elasticsearch)
58
60
  when 'resque-redis'
59
61
  errors << HealthCheck::ResqueHealthCheck.check
60
62
  when 'sidekiq-redis'
@@ -63,28 +65,41 @@ module HealthCheck
63
65
  errors << HealthCheck::RedisHealthCheck.check
64
66
  when 's3'
65
67
  errors << HealthCheck::S3HealthCheck.check
68
+ when 'elasticsearch'
69
+ errors << HealthCheck::ElasticsearchHealthCheck.check
70
+ when 'rabbitmq'
71
+ errors << HealthCheck::RabbitMQHealthCheck.check
66
72
  when "standard"
67
73
  errors << HealthCheck::Utils.process_checks(HealthCheck.standard_checks, called_from_middleware)
68
74
  when "middleware"
69
75
  errors << "Health check not called from middleware - probably not installed as middleware." unless called_from_middleware
70
76
  when "custom"
71
- HealthCheck.custom_checks.each do |custom_check|
72
- errors << custom_check.call(self)
77
+ HealthCheck.custom_checks.each do |name, list|
78
+ list.each do |custom_check|
79
+ errors << custom_check.call(self)
80
+ end
73
81
  end
74
82
  when "all", "full"
75
83
  errors << HealthCheck::Utils.process_checks(HealthCheck.full_checks, called_from_middleware)
76
84
  else
77
- return "invalid argument to health_test."
85
+ if HealthCheck.custom_checks.include? check
86
+ HealthCheck.custom_checks[check].each do |custom_check|
87
+ errors << custom_check.call(self)
88
+ end
89
+ else
90
+ return "invalid argument to health_test."
91
+ end
78
92
  end
93
+ errors << '. ' unless errors == '' || errors.end_with?('. ')
79
94
  end
80
- return errors
95
+ return errors.strip
81
96
  rescue => e
82
97
  return e.message
83
98
  end
84
99
 
85
100
  def self.db_migrate_path
86
101
  # Lazy initialisation so Rails.root will be defined
87
- @@db_migrate_path ||= File.join(Rails.root, 'db', 'migrate')
102
+ @@db_migrate_path ||= File.join(::Rails.root, 'db', 'migrate')
88
103
  end
89
104
 
90
105
  def self.db_migrate_path=(value)
@@ -127,36 +142,43 @@ module HealthCheck
127
142
  status = ''
128
143
  begin
129
144
  if @skip_external_checks
130
- status = '221'
145
+ status = '250'
131
146
  else
132
- Timeout::timeout(timeout) do |timeout_length|
133
- t = TCPSocket.new(settings[:address], settings[:port])
134
- begin
135
- status = t.gets
136
- while status != nil && status !~ /^2/
137
- status = t.gets
138
- end
139
- t.puts "HELO #{settings[:domain]}\r"
140
- while status != nil && status !~ /^250/
141
- status = t.gets
142
- end
143
- t.puts "QUIT\r"
144
- status = t.gets
145
- ensure
146
- t.close
147
- end
147
+ smtp = Net::SMTP.new(settings[:address], settings[:port])
148
+ smtp.enable_starttls if settings[:enable_starttls_auto]
149
+ smtp.open_timeout = timeout
150
+ smtp.read_timeout = timeout
151
+ smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do
152
+ status = smtp.helo(settings[:domain]).status
148
153
  end
149
154
  end
150
- rescue Errno::EBADF => ex
151
- status = "Unable to connect to service"
152
155
  rescue Exception => ex
153
156
  status = ex.to_s
154
157
  end
155
- (status =~ /^221/) ? '' : "SMTP: #{status || 'unexpected EOF on socket'}. "
158
+ (status =~ /^250/) ? '' : "SMTP: #{status || 'unexpected error'}. "
156
159
  end
157
160
 
158
161
  def self.check_cache
159
- Rails.cache.write('__health_check_cache_test__', 'ok', :expires_in => 1.second) ? '' : 'Unable to write to cache. '
162
+ t = Time.now.to_i
163
+ value = "ok #{t}"
164
+ ret = ::Rails.cache.read('__health_check_cache_test__')
165
+ if ret.to_s =~ /^ok (\d+)$/
166
+ diff = ($1.to_i - t).abs
167
+ return('Cache expiry is broken. ') if diff > 30
168
+ elsif ret
169
+ return 'Cache is returning garbage. '
170
+ end
171
+ if ::Rails.cache.write('__health_check_cache_test__', value, expires_in: 2.seconds)
172
+ ret = ::Rails.cache.read('__health_check_cache_test__')
173
+ if ret =~ /^ok (\d+)$/
174
+ diff = ($1.to_i - t).abs
175
+ (diff < 2 ? '' : 'Out of date cache or time is skewed. ')
176
+ else
177
+ 'Unable to read from cache. '
178
+ end
179
+ else
180
+ 'Unable to write to cache. '
181
+ end
160
182
  end
161
183
 
162
184
  end