├── init.rb ├── test ├── migrate │ ├── empty │ │ └── do_not_remove.txt │ ├── twelve │ │ ├── 011_create_roles.roles.rb │ │ ├── 012_create_users.rb │ │ └── 9_create_countries.rb │ └── nine │ │ └── 9_create_countries.rb ├── rails_6.1.gemfile ├── rails_5.0.gemfile ├── rails_6.0.gemfile ├── rails_6.2.gemfile ├── rails_5.1.gemfile ├── rails_5.2.gemfile ├── rails_edge.gemfile ├── testurl ├── init_variables ├── provision_vagrant ├── fake_smtp_server ├── setup_railsapp └── test_with_railsapp ├── lib ├── health_check │ ├── version.rb │ ├── base_health_check.rb │ ├── rabbitmq_health_check.rb │ ├── resque_health_check.rb │ ├── elasticsearch_health_check.rb │ ├── sidekiq_health_check.rb │ ├── health_check_routes.rb │ ├── redis_health_check.rb │ ├── s3_health_check.rb │ ├── health_check_controller.rb │ ├── middleware_health_check.rb │ └── utils.rb └── health_check.rb ├── .document ├── config └── routes.rb ├── .gitignore ├── Gemfile ├── Rakefile ├── Vagrantfile ├── MIT-LICENSE ├── health_check.gemspec ├── .travis.yml ├── CHANGELOG └── README.rdoc /init.rb: -------------------------------------------------------------------------------- 1 | require 'health_check' 2 | 3 | -------------------------------------------------------------------------------- /test/migrate/empty/do_not_remove.txt: -------------------------------------------------------------------------------- 1 | Don't remove -------------------------------------------------------------------------------- /lib/health_check/version.rb: -------------------------------------------------------------------------------- 1 | module HealthCheck 2 | VERSION = "3.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | unless HealthCheck::Engine.routes_explicitly_defined 2 | ::Rails.application.routes.draw do 3 | add_health_check_routes() 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/health_check/base_health_check.rb: -------------------------------------------------------------------------------- 1 | module BaseHealthCheck 2 | def create_error(check_type, error_message) 3 | "[#{check_type} - #{error_message}] " 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/migrate/twelve/011_create_roles.roles.rb: -------------------------------------------------------------------------------- 1 | class CreateRoles < ActiveRecord::Migration[5.0] 2 | def self.up 3 | create_table :roles do |t| 4 | t.column :name, :string 5 | end 6 | end 7 | 8 | def self.down 9 | drop_table :roles 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/migrate/nine/9_create_countries.rb: -------------------------------------------------------------------------------- 1 | class CreateCountries < ActiveRecord::Migration[5.0] 2 | def self.up 3 | create_table :countries do |t| 4 | t.column :name, :string 5 | end 6 | end 7 | 8 | def self.down 9 | drop_table :countries 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/migrate/twelve/012_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[5.0] 2 | def self.up 3 | create_table "users", force: true do |t| 4 | t.column :name, :string 5 | end 6 | end 7 | 8 | def self.down 9 | drop_table :users 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/migrate/twelve/9_create_countries.rb: -------------------------------------------------------------------------------- 1 | class CreateCountries < ActiveRecord::Migration[5.0] 2 | def self.up 3 | create_table :countries do |t| 4 | t.column :name, :string 5 | end 6 | end 7 | 8 | def self.down 9 | drop_table :countries 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/health_check/rabbitmq_health_check.rb: -------------------------------------------------------------------------------- 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 17 | -------------------------------------------------------------------------------- /lib/health_check/resque_health_check.rb: -------------------------------------------------------------------------------- 1 | module HealthCheck 2 | class ResqueHealthCheck 3 | extend BaseHealthCheck 4 | 5 | def self.check 6 | unless defined?(::Resque) 7 | raise "Wrong configuration. Missing 'resque' gem" 8 | end 9 | res = ::Resque.redis.ping 10 | res == 'PONG' ? '' : "Resque.redis.ping returned #{res.inspect} instead of PONG" 11 | rescue Exception => e 12 | create_error 'resque-redis', e.message 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/health_check/elasticsearch_health_check.rb: -------------------------------------------------------------------------------- 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 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Suggested by bundler 2 | *.gem 3 | *.rbc 4 | .bundle 5 | .config 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | tmp 19 | .ruby-version 20 | 21 | ## PROJECT::SPECIFIC 22 | bin/ 23 | test/bin/ 24 | railsapps/ 25 | test/*.gemfile.lock 26 | ,* 27 | .vagrant 28 | ubuntu*console.log 29 | 30 | # See: https://gist.github.com/ianheggie/9327010 31 | # for Global git ignore for OS/IDE/temp/backup files 32 | -------------------------------------------------------------------------------- /lib/health_check/sidekiq_health_check.rb: -------------------------------------------------------------------------------- 1 | module HealthCheck 2 | class SidekiqHealthCheck 3 | extend BaseHealthCheck 4 | 5 | def self.check 6 | unless defined?(::Sidekiq) 7 | raise "Wrong configuration. Missing 'sidekiq' gem" 8 | end 9 | ::Sidekiq.redis do |r| 10 | res = r.ping 11 | res == 'PONG' ? '' : "Sidekiq.redis.ping returned #{res.inspect} instead of PONG" 12 | end 13 | rescue Exception => e 14 | create_error 'sidekiq-redis', e.message 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/health_check/health_check_routes.rb: -------------------------------------------------------------------------------- 1 | module ActionDispatch::Routing 2 | class Mapper 3 | 4 | def health_check_routes(prefix = nil) 5 | HealthCheck::Engine.routes_explicitly_defined = true 6 | add_health_check_routes(prefix) 7 | end 8 | 9 | def add_health_check_routes(prefix = nil) 10 | HealthCheck.uri = prefix if prefix 11 | match "#{HealthCheck.uri}(/:checks)(.:format)", controller: 'health_check/health_check', action: :index, via: [:get, :post], defaults: { format: 'txt' } 12 | end 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in health_check.gemspec 4 | 5 | gemspec 6 | 7 | group :development, :test do 8 | if defined?(JRUBY_VERSION) 9 | gem 'jruby-openssl' 10 | gem 'activerecord-jdbcsqlite3-adapter' 11 | else 12 | gem 'sqlite3', '~> 1.3.7' 13 | end 14 | # run travis-lint to check .travis.yml 15 | gem 'travis-lint' 16 | # mime-types 2.0 requires Ruby version >= 1.9.2 17 | # mime-types 3.0 requires Ruby version >= 2.0 18 | gem 'mime-types', defined?(JRUBY_VERSION) || RUBY_VERSION < '2.0' ? '< 3' : '>= 3.0' 19 | 20 | end 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | #require 'rubygems' 4 | require 'rake' 5 | 6 | #tests as gem 7 | task :test do 8 | exec '/bin/bash', './test/test_with_railsapp' 9 | end 10 | 11 | task default: :test 12 | 13 | begin 14 | gem 'rdoc' 15 | require 'rdoc/task' 16 | 17 | Rake::RDocTask.new do |rdoc| 18 | version = HealthCheck::VERSION 19 | 20 | rdoc.rdoc_dir = 'rdoc' 21 | rdoc.title = "health_check #{version}" 22 | rdoc.rdoc_files.include('README*') 23 | rdoc.rdoc_files.include('CHANGELOG') 24 | rdoc.rdoc_files.include('MIT-LICENSE') 25 | rdoc.rdoc_files.include('lib/**/*.rb') 26 | end 27 | rescue Gem::LoadError 28 | puts "rdoc (or a dependency) not available. Install it with: gem install rdoc" 29 | end 30 | -------------------------------------------------------------------------------- /lib/health_check/redis_health_check.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HealthCheck 4 | class RedisHealthCheck 5 | extend BaseHealthCheck 6 | 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 | ) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/rails_6.1.gemfile: -------------------------------------------------------------------------------- 1 | # Gemfile for health_test testing 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby RUBY_VERSION < '2.5' ? '2.5.0' : RUBY_VERSION 6 | 7 | gem 'rails', '~> 6.1.0' 8 | gem 'rake', '>= 0.8.7' 9 | 10 | group :development, :test do 11 | if defined?(JRUBY_VERSION) 12 | gem 'jruby-openssl' 13 | gem 'activerecord-jdbcsqlite3-adapter' 14 | else 15 | gem 'sqlite3', "~> 1.3.7" 16 | end 17 | gem 'shoulda' 18 | end 19 | 20 | # redis based checks 21 | gem 'sidekiq', require: !ENV['SIDEKIQ'].nil? # REQUIRED 22 | gem 'redis', require: !ENV['REDIS_URL'].nil? # REQUIRED 23 | gem 'resque', require: !ENV['RESQUE'].nil? # REQUIRED 24 | # s3 check 25 | gem 'aws-sdk-s3', require: !ENV['AWS_ACCESS_KEY_ID'].nil? # REQUIRED 26 | 27 | gem 'webpacker', '~> 4.0.7' # REQUIRED 28 | gem 'rexml', '~> 3.2.4' # REQUIRED for ruby 3.0 29 | gem 'webrick' # REQUIRED for ruby 3.0 30 | -------------------------------------------------------------------------------- /test/rails_5.0.gemfile: -------------------------------------------------------------------------------- 1 | # Gemfile for health_test testing 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby RUBY_VERSION < '2.2.2' ? '2.2.2' : RUBY_VERSION 6 | 7 | gem 'rails', '~> 5.0.0' 8 | gem 'rake', '>= 0.8.7' 9 | 10 | gem 'listen', '<3.1.2' # REQUIRED 11 | 12 | group :development, :test do 13 | if defined?(JRUBY_VERSION) 14 | gem 'jruby-openssl' 15 | gem 'activerecord-jdbcsqlite3-adapter' 16 | else 17 | gem 'sqlite3', "~> 1.3.7" 18 | end 19 | gem 'shoulda' 20 | end 21 | 22 | # redis based checks 23 | gem 'sidekiq', '~> 5.2.9', require: !ENV['SIDEKIQ'].nil? # REQUIRED 24 | gem 'redis', '~> 4.0.3', require: !ENV['REDIS_URL'].nil? # REQUIRED 25 | gem 'resque', '~> 1.27.4', require: !ENV['RESQUE'].nil? # REQUIRED 26 | gem 'elasticsearch', '~> 6.3.1', require: !ENV['ELASTICSEARCH_URL'].nil? # REQUIRED 27 | # s3 check 28 | gem 'aws-sdk-s3', require: !ENV['AWS_ACCESS_KEY_ID'].nil? # REQUIRED 29 | 30 | -------------------------------------------------------------------------------- /test/rails_6.0.gemfile: -------------------------------------------------------------------------------- 1 | # Gemfile for health_test testing 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby RUBY_VERSION < '2.5' ? '2.5.0' : RUBY_VERSION 6 | 7 | gem 'rails', '~> 6.0.0' 8 | gem 'rake', '>= 0.8.7' 9 | 10 | gem 'listen', '<3.1.2' # REQUIRED 11 | 12 | group :development, :test do 13 | if defined?(JRUBY_VERSION) 14 | gem 'jruby-openssl' 15 | gem 'activerecord-jdbcsqlite3-adapter' 16 | else 17 | gem 'sqlite3', "~> 1.3.7" 18 | end 19 | gem 'shoulda' 20 | end 21 | 22 | # redis based checks 23 | gem 'sidekiq', '~> 5.2.9', require: !ENV['SIDEKIQ'].nil? # REQUIRED 24 | gem 'redis', '~> 4.0.3', require: !ENV['REDIS_URL'].nil? # REQUIRED 25 | gem 'resque', '~> 1.27.4', require: !ENV['RESQUE'].nil? # REQUIRED 26 | gem 'elasticsearch', '~> 6.3.1', require: !ENV['ELASTICSEARCH_URL'].nil? # REQUIRED 27 | # s3 check 28 | gem 'aws-sdk-s3', require: !ENV['AWS_ACCESS_KEY_ID'].nil? # REQUIRED 29 | 30 | gem 'webpacker', '~> 4.0.7' # REQUIRED 31 | -------------------------------------------------------------------------------- /test/rails_6.2.gemfile: -------------------------------------------------------------------------------- 1 | # Gemfile for health_test testing 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby RUBY_VERSION < '2.5' ? '2.5.0' : RUBY_VERSION 6 | 7 | gem 'rails', '~> 6.2.0' 8 | gem 'rake', '>= 0.8.7' 9 | 10 | group :development, :test do 11 | if defined?(JRUBY_VERSION) 12 | gem 'jruby-openssl' 13 | gem 'activerecord-jdbcsqlite3-adapter' 14 | else 15 | gem 'sqlite3', "~> 1.3.7" 16 | end 17 | gem 'shoulda' 18 | end 19 | 20 | # redis based checks 21 | gem 'sidekiq', '~> 5.2.9', require: !ENV['SIDEKIQ'].nil? # REQUIRED 22 | gem 'redis', '~> 4.0.3', require: !ENV['REDIS_URL'].nil? # REQUIRED 23 | gem 'resque', '~> 1.27.4', require: !ENV['RESQUE'].nil? # REQUIRED 24 | gem 'elasticsearch', '~> 6.3.1', require: !ENV['ELASTICSEARCH_URL'].nil? # REQUIRED 25 | # s3 check 26 | gem 'aws-sdk-s3', require: !ENV['AWS_ACCESS_KEY_ID'].nil? # REQUIRED 27 | 28 | gem 'webpacker', '~> 4.0.7' # REQUIRED 29 | gem 'rexml', '~> 3.2.4' # REQUIRED for ruby 3.0 30 | gem 'webrick' # REQUIRED for ruby 3.0 31 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 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 33 | -------------------------------------------------------------------------------- /test/rails_5.1.gemfile: -------------------------------------------------------------------------------- 1 | # Gemfile for health_test testing 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby RUBY_VERSION < '2.2.2' ? '2.2.2' : RUBY_VERSION 6 | 7 | gem 'rails', '~> 5.1.0' 8 | gem 'rake', '>= 0.8.7' 9 | 10 | # spring-watcher-listen was resolved to 2.0.1, which depends on 11 | # listen was resolved to 3.1.5, which depends on 12 | # ruby_dep 13 | # and ruby_dep 1.5 requires 2.2.3 or later 14 | gem 'ruby_dep', '~> 1.3.0' # REQUIRED 15 | 16 | gem 'listen', '<3.1.2' # REQUIRED 17 | 18 | group :development, :test do 19 | if defined?(JRUBY_VERSION) 20 | gem 'jruby-openssl' 21 | gem 'activerecord-jdbcsqlite3-adapter' 22 | else 23 | gem 'sqlite3', "~> 1.3.7" 24 | end 25 | gem 'shoulda' 26 | end 27 | 28 | # redis based checks 29 | gem 'sidekiq', '~> 5.2.9', require: !ENV['SIDEKIQ'].nil? # REQUIRED 30 | gem 'redis', '~> 4.0.3', require: !ENV['REDIS_URL'].nil? # REQUIRED 31 | gem 'resque', '~> 1.27.4', require: !ENV['RESQUE'].nil? # REQUIRED 32 | gem 'elasticsearch', '~> 6.3.1', require: !ENV['ELASTICSEARCH_URL'].nil? # REQUIRED 33 | # s3 check 34 | gem 'aws-sdk', require: !ENV['AWS_ACCESS_KEY_ID'].nil? # REQUIRED 35 | -------------------------------------------------------------------------------- /test/rails_5.2.gemfile: -------------------------------------------------------------------------------- 1 | # Gemfile for health_test testing 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby RUBY_VERSION < '2.2.2' ? '2.2.2' : RUBY_VERSION 6 | 7 | gem 'rails', '~> 5.2.0' 8 | gem 'rake', '>= 0.8.7' 9 | 10 | # spring-watcher-listen was resolved to 2.0.1, which depends on 11 | # listen was resolved to 3.1.5, which depends on 12 | # ruby_dep 13 | # and ruby_dep 1.5 requires 2.2.3 or later 14 | gem 'ruby_dep', '~> 1.3.0' # REQUIRED 15 | 16 | gem 'listen', '<3.1.2' # REQUIRED 17 | 18 | group :development, :test do 19 | if defined?(JRUBY_VERSION) 20 | gem 'jruby-openssl' 21 | gem 'activerecord-jdbcsqlite3-adapter' 22 | else 23 | gem 'sqlite3', "~> 1.3.7" 24 | end 25 | gem 'shoulda' 26 | end 27 | 28 | # redis based checks 29 | gem 'sidekiq', '~> 5.2.9', require: !ENV['SIDEKIQ'].nil? # REQUIRED 30 | gem 'redis', '~> 4.0.3', require: !ENV['REDIS_URL'].nil? # REQUIRED 31 | gem 'resque', '~> 1.27.4', require: !ENV['RESQUE'].nil? # REQUIRED 32 | gem 'elasticsearch', '~> 6.3.1', require: !ENV['ELASTICSEARCH_URL'].nil? # REQUIRED 33 | # s3 check 34 | gem 'aws-sdk', require: !ENV['AWS_ACCESS_KEY_ID'].nil? # REQUIRED 35 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2013 Ian Heggie 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/rails_edge.gemfile: -------------------------------------------------------------------------------- 1 | # Gemfile for health_test testing 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Bundle edge Rails instead: 6 | 7 | ruby RUBY_VERSION < '2.2.2' ? '2.2.2' : RUBY_VERSION 8 | 9 | gem 'rails' 10 | gem 'rake' 11 | gem 'rack' 12 | 13 | group :development, :test do 14 | if defined?(JRUBY_VERSION) 15 | gem 'jruby-openssl' 16 | gem 'activerecord-jdbcsqlite3-adapter' 17 | else 18 | gem 'sqlite3' 19 | end 20 | gem 'shoulda' 21 | end 22 | 23 | # redis based checks 24 | gem 'sidekiq', '~> 5.2.9', require: !ENV['SIDEKIQ'].nil? # REQUIRED 25 | gem 'redis', '~> 4.0.3', require: !ENV['REDIS_URL'].nil? # REQUIRED 26 | gem 'resque', '~> 1.27.4', require: !ENV['RESQUE'].nil? # REQUIRED 27 | gem 'elasticsearch', '~> 6.3.1', require: !ENV['ELASTICSEARCH_URL'].nil? # REQUIRED 28 | # s3 check 29 | gem 'aws-sdk-s3', require: !ENV['AWS_ACCESS_KEY_ID'].nil? # REQUIRED 30 | 31 | # Initial Gemfile has therubyracer commented out 32 | gem 'therubyrhino', platform: :jruby # REQUIRED 33 | gem 'therubyracer', platform: :ruby # REQUIRED 34 | 35 | gem 'webpacker', '~> 4.0.7' # REQUIRED 36 | gem 'rexml', '~> 3.2.4' # REQUIRED for ruby 3.0 37 | gem 'webrick' # REQUIRED for ruby 3.0 38 | -------------------------------------------------------------------------------- /health_check.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'health_check/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "health_check" 8 | gem.version = HealthCheck::VERSION 9 | gem.required_rubygems_version = Gem::Requirement.new(">= 0") if gem.respond_to? :required_rubygems_version= 10 | gem.authors = ["Ian Heggie"] 11 | gem.email = ["ian@heggie.biz"] 12 | gem.summary = %q{Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard etc.} 13 | gem.description = <<-EOF 14 | Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard etc. 15 | EOF 16 | gem.homepage = "https://github.com/ianheggie/health_check" 17 | gem.license = "MIT" 18 | 19 | gem.files = `git ls-files`.split($/) 20 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 21 | gem.extra_rdoc_files = [ "README.rdoc" ] 22 | gem.require_paths = ["lib"] 23 | gem.required_ruby_version = '>= 2.2.2' 24 | gem.add_dependency(%q, [">= 5.0"]) 25 | gem.add_development_dependency(%q, [">= 0.1.0"]) 26 | gem.add_development_dependency(%q, [">= 0.8.3"]) 27 | gem.add_development_dependency(%q, ["~> 2.11.0"]) 28 | gem.add_development_dependency(%q, [">= 1.2"]) 29 | end 30 | -------------------------------------------------------------------------------- /test/testurl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'net/http' 3 | require 'uri' 4 | 5 | def open(url) 6 | parsed_uri = URI.parse(url) 7 | http = Net::HTTP.new(parsed_uri.host, parsed_uri.port) 8 | req = Net::HTTP::Get.new(parsed_uri.request_uri) 9 | req.basic_auth(ENV['AUTH_USER'], ENV['AUTH_PASSWORD'].to_s) if ENV['AUTH_USER'] 10 | http.request(req) 11 | end 12 | 13 | response = open(ARGV[0]) rescue nil 14 | unless response 15 | i=0 16 | print "waiting.." 17 | while i < 120 and not response 18 | #puts 'RESPONSE:', response.inspect 19 | print "." 20 | STDOUT.flush 21 | i += 1 22 | sleep(1) 23 | response = open(ARGV[0]) rescue nil 24 | end 25 | unless response 26 | puts "\nFAIL: timed out after waiting #{i} seconds" 27 | exit 9 28 | end 29 | puts "\n got url content after waiting #{i} seconds" 30 | end 31 | 32 | page_content = response.body 33 | 34 | puts " response code: #{response.code} #{response.message}" 35 | response.header.each_header {|key,value| puts " #{key}: #{value}" } 36 | puts " body: #{page_content}" 37 | 38 | if ARGV[1] and ARGV[1] != '' 39 | if ARGV[1].split(',').include?(response.code) 40 | puts "PASS (response code was #{response.code} which matched #{ARGV[1]})" 41 | else 42 | puts "FAIL (response code was #{response.code}, expected #{ARGV[1]})" 43 | exit 1 44 | end 45 | end 46 | 47 | if ARGV[2] and ARGV[2] != '' 48 | if response.content_type == ARGV[2] 49 | puts "PASS (content type was #{response.content_type})" 50 | else 51 | puts "FAIL (content type was #{response.content_type}, expected #{ARGV[2]})" 52 | exit 2 53 | end 54 | end 55 | 56 | if ARGV[3] and ARGV[3] != '' 57 | if page_content.to_s.include? ARGV[3] 58 | puts "PASS (found #{ARGV[3]})" 59 | else 60 | puts "FAIL (expected to find #{ARGV[3]}) - page contents:" , page_content.to_s, 'END-OF-CONTENTS' 61 | exit 3 62 | end 63 | end 64 | 65 | if ARGV[4] and ARGV[4] != '' 66 | if !page_content.to_s.include? ARGV[4] 67 | puts "PASS (did not find #{ARGV[4]})" 68 | else 69 | puts "FAIL (found #{ARGV[4]}) - page contents:" , page_content.to_s, 'END-OF-CONTENTS' 70 | exit 3 71 | end 72 | end 73 | 74 | exit 0 75 | -------------------------------------------------------------------------------- /test/init_variables: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Any failure causes exit 4 | set -eE -o functrace 5 | 6 | report_failure() { 7 | local lineno=$2 8 | local fn=$3 9 | local exitstatus=$4 10 | local msg=$5 11 | local lineno_fns=${1% 0} 12 | if [[ $lineno_fns != "0" ]] ; then 13 | lineno="${lineno} ${lineno_fns}" 14 | fi 15 | if [[ $exitstatus == 0 ]] ; then 16 | echo "${BASH_SOURCE[1]}: Finished!" 17 | else 18 | echo "${BASH_SOURCE[1]}:${fn}[${lineno}] Failed with status ${exitstatus}: $msg" 19 | fi 20 | } 21 | 22 | trap 'report_failure "${BASH_LINENO[*]}" "$LINENO" "${FUNCNAME[*]:-script}" "$?" "$BASH_COMMAND"' ERR 23 | 24 | echo Setting RAILS_ENV=test RACK_ENV=test 25 | export RAILS_ENV=test RACK_ENV=test 26 | 27 | base_dir=$PWD 28 | tmp_dir=$base_dir/tmp 29 | railsapp=$tmp_dir/railsapp 30 | custom_file="$railsapp/tmp/custom_check.ok" 31 | catchall_file="$railsapp/tmp/catchall_route.enabled" 32 | success=successful 33 | 34 | rehash='' 35 | rbenv_which='which' 36 | 37 | if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then 38 | echo "Detected user installed rvm" 39 | elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then 40 | echo "Detected root installed rvm" 41 | elif [[ -d "$HOME/.rbenv" ]] ; then 42 | echo "Detected rbenv: `rbenv version`" 43 | rehash='rbenv rehash' 44 | rbenv_which='rbenv which' 45 | else 46 | printf "Note: Neither rvm nor rbenv was not found.\n" 47 | fi 48 | 49 | echo "Checking required commands exist:" 50 | for cmd in bash gem egrep ls tail kill find cpio 51 | do 52 | echo -n " " 53 | which $cmd || ( echo "Aborting setup_railsapp: Missing $cmd command!" && exit 2 ) 54 | done 55 | for cmd in ruby gem 56 | do 57 | echo -n " " 58 | $rbenv_which $cmd || ( echo "Aborting setup_railsapp: Missing $cmd command!" && exit 2 ) 59 | done 60 | 61 | rails="rails" 62 | rake="rake" 63 | 64 | if [ -x $base_dir/test/bin/rails ] 65 | then 66 | rails="$base_dir/test/bin/rails" 67 | rake="$base_dir/test/bin/rake" 68 | export PATH="$base_dir/test/bin:$PATH" 69 | fi 70 | 71 | if [ -x $railsapp/bin/rails ] 72 | then 73 | rails="$railsapp/bin/rails" 74 | rake="$railsapp/bin/rake" 75 | export PATH="$railsapp/bin:$PATH" 76 | fi 77 | 78 | echo "Using rails=$rails, rake=$rake" 79 | 80 | -------------------------------------------------------------------------------- /lib/health_check/s3_health_check.rb: -------------------------------------------------------------------------------- 1 | module HealthCheck 2 | class S3HealthCheck 3 | extend BaseHealthCheck 4 | 5 | class << self 6 | def check 7 | unless defined?(::Aws) 8 | raise "Wrong configuration. Missing 'aws-sdk' or 'aws-sdk-s3' gem" 9 | end 10 | return create_error 's3', 'Could not connect to aws' if aws_s3_client.nil? 11 | HealthCheck.buckets.each do |bucket_name, permissions| 12 | if permissions.nil? # backward compatible 13 | permissions = [:R, :W, :D] 14 | end 15 | permissions.each do |permision| 16 | begin 17 | send(permision, bucket_name) 18 | rescue Exception => e 19 | raise "bucket:#{bucket_name}, permission:#{permision} - #{e.message}" 20 | end 21 | end 22 | end 23 | '' 24 | rescue Exception => e 25 | create_error 's3', e.message 26 | end 27 | 28 | private 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. 33 | def configure_client 34 | ::Aws.config[:s3] = { force_path_style: true } 35 | ::Aws.config[:region] ||= ENV['AWS_REGION'] || ENV['DEFAULT_AWS_REGION'] 36 | 37 | ::Aws::S3::Client.new 38 | end 39 | 40 | def aws_s3_client 41 | @aws_s3_client ||= configure_client 42 | end 43 | 44 | def R(bucket) 45 | aws_s3_client.list_objects(bucket: bucket) 46 | end 47 | 48 | def W(bucket) 49 | app_name = if Rails::VERSION::MAJOR >= 6 50 | ::Rails.application.class.module_parent_name 51 | else 52 | ::Rails.application.class.parent_name 53 | end 54 | 55 | aws_s3_client.put_object(bucket: bucket, 56 | key: "healthcheck_#{app_name}", 57 | body: Time.new.to_s) 58 | end 59 | 60 | def D(bucket) 61 | app_name = if Rails::VERSION::MAJOR >= 6 62 | ::Rails.application.class.module_parent_name 63 | else 64 | ::Rails.application.class.parent_name 65 | end 66 | aws_s3_client.delete_object(bucket: bucket, 67 | key: "healthcheck_#{app_name}") 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/provision_vagrant: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | case `id` in 4 | *root*) 5 | ;; 6 | *) 7 | exec echo Must be run as root 8 | ;; 9 | esac 10 | 11 | chruby_version=0.3.9 12 | chruby=chruby-${chruby_version} 13 | set -x 14 | set -eE -o functrace 15 | 16 | report_failure() { 17 | local lineno=$2 18 | local fn=$3 19 | local exitstatus=$4 20 | local msg=$5 21 | local lineno_fns=${1% 0} 22 | if [[ $lineno_fns != "0" ]] ; then 23 | lineno="${lineno} ${lineno_fns}" 24 | fi 25 | if [[ $exitstatus == 0 ]] ; then 26 | echo "${BASH_SOURCE[1]}: Finished!" 27 | else 28 | echo "${BASH_SOURCE[1]}:${fn}[${lineno}] Failed with status ${exitstatus}: $msg" 29 | fi 30 | } 31 | 32 | trap 'report_failure "${BASH_LINENO[*]}" "$LINENO" "${FUNCNAME[*]:-script}" "$?" "$BASH_COMMAND"' ERR 33 | 34 | 35 | id 36 | pwd 37 | export DEBIAN_FRONTEND=noninteractive 38 | find /tmp/got-apt-update -mtime -1 || ( apt-get update && touch /tmp/got-apt-update ) 39 | apt install --yes -q build-essential ruby ruby-dev sqlite3 libsqlite3-dev nodejs git git-core 40 | apt install --yes -q gcc autoconf bison libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev 41 | apt install --yes -q libgdbm3 42 | # useful when debugging 43 | apt install --yes -q silversearcher-ag vim exuberant-ctags 44 | apt install --yes -q unattended-upgrades 45 | unattended-upgrade 46 | # The following is not required for testing, install if you are doing manual tests with extra gems 47 | # apt install --yes -q mysql-client mysql-server libmysqlclient-dev 48 | # apt install --yes -q libcurl4-openssl-dev libncurses5-dev libxml2-dev libxslt1-dev 49 | ( 50 | echo Install chruby 51 | [ -s ${chruby}.tar.gz ] || wget -q -O ${chruby}.tar.gz https://github.com/postmodern/chruby/archive/v${chruby_version}.tar.gz 52 | [ -d ${chruby} ] || tar -xzf ${chruby}.tar.gz 53 | cd ${chruby}/ 54 | ./scripts/setup.sh 55 | cat > /etc/profile.d/chruby.sh <<'EOF' 56 | if [ -n "$BASH_VERSION" ] || [ -n "$ZSH_VERSION" ]; then 57 | source /usr/local/share/chruby/chruby.sh 58 | #source /usr/local/share/chruby/auto 59 | fi 60 | EOF 61 | chmod a+r /etc/profile.d/chruby.sh 62 | ) 63 | 64 | ( 65 | [ -d ruby-build ] || git clone https://github.com/rbenv/ruby-build.git 66 | which ruby-build || PREFIX=/usr/local ./ruby-build/install.sh 67 | 68 | mkdir -p /opt/rubies 69 | for v in 2.2.2 70 | do 71 | [ -x /opt/rubies/$v/bin/ruby ] || ( ruby-build $v /opt/rubies/$v ) 72 | [ -x /opt/rubies/${v}/bin/bundle ] || ( /opt/rubies/${v}/bin/gem install bundler -v '<2.0' ) 73 | done 74 | 75 | for v in 2.3.8 2.4.10 2.5.9 2.5.0 2.6.6 2.6.7 2.7.1 2.7.3 3.0.1 76 | do 77 | [ -x /opt/rubies/$v/bin/ruby ] || ( ruby-build $v /opt/rubies/$v ) 78 | [ -x /opt/rubies/$v/bin/bundle ] || ( /opt/rubies/$v/bin/gem install bundler ) 79 | done 80 | ) 81 | 82 | echo Setup system ruby 83 | which bundle || gem install bundler || gem install bundler -v '<2.0' 84 | which bundle || gem install bundler -v '< 2.0' 85 | bundle --version 86 | set +x 87 | cat < 1 16 | last_modified = Time.at((last_modified.to_f / max_age).floor * max_age).utc 17 | end 18 | is_public = (max_age > 1) && ! HealthCheck.basic_auth_username 19 | if stale?(last_modified: last_modified, public: is_public) 20 | checks = params[:checks] ? params[:checks].split('_') : ['standard'] 21 | checks -= HealthCheck.middleware_checks if HealthCheck.installed_as_middleware 22 | begin 23 | errors = HealthCheck::Utils.process_checks(checks) 24 | rescue Exception => e 25 | errors = e.message.blank? ? e.class.to_s : e.message.to_s 26 | end 27 | response.headers['Cache-Control'] = "must-revalidate, max-age=#{max_age}" 28 | if errors.blank? 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 35 | else 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 | 39 | # Log a single line as some uptime checkers only record that it failed, not the text returned 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 46 | end 47 | end 48 | end 49 | end 50 | 51 | protected 52 | 53 | def send_response(healthy, msg, text_status, obj_status) 54 | msg ||= healthy ? HealthCheck.success : HealthCheck.failure 55 | obj = { healthy: healthy, message: msg} 56 | respond_to do |format| 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' } 61 | end 62 | end 63 | 64 | def authenticate 65 | return unless HealthCheck.basic_auth_username && HealthCheck.basic_auth_password 66 | authenticate_or_request_with_http_basic('Health Check') do |username, password| 67 | username == HealthCheck.basic_auth_username && password == HealthCheck.basic_auth_password 68 | end 69 | end 70 | 71 | def check_origin_ip 72 | request_ipaddr = IPAddr.new(HealthCheck.accept_proxied_requests ? request.remote_ip : request.ip) 73 | unless HealthCheck.origin_ip_whitelist.blank? || 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' 78 | end 79 | end 80 | 81 | # turn cookies for CSRF off 82 | def protect_against_forgery? 83 | false 84 | end 85 | 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/health_check/middleware_health_check.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2021 Ian Heggie, released under the MIT license. 2 | # See MIT-LICENSE for details. 3 | require 'ipaddr' 4 | 5 | module HealthCheck 6 | class MiddlewareHealthcheck 7 | 8 | def initialize(app) 9 | @app = app 10 | end 11 | 12 | def call(env) 13 | (response_type, middleware_checks, full_stack_checks) = parse_env(env) 14 | if response_type 15 | if error_response = (ip_blocked(env) || not_authenticated(env)) 16 | return error_response 17 | end 18 | HealthCheck.installed_as_middleware = true 19 | errors = '' 20 | begin 21 | # Process the checks to be run from middleware 22 | errors = HealthCheck::Utils.process_checks(middleware_checks, true) 23 | # Process remaining checks through the full stack if there are any 24 | unless full_stack_checks.empty? 25 | return @app.call(env) 26 | end 27 | rescue => e 28 | errors = e.message.blank? ? e.class.to_s : e.message.to_s 29 | end 30 | healthy = errors.blank? 31 | msg = healthy ? HealthCheck.success : "health_check failed: #{errors}" 32 | if response_type == 'xml' 33 | content_type = 'text/xml' 34 | msg = { healthy: healthy, message: msg }.to_xml 35 | error_code = HealthCheck.http_status_for_error_object 36 | elsif response_type == 'json' 37 | content_type = 'application/json' 38 | msg = { healthy: healthy, message: msg }.to_json 39 | error_code = HealthCheck.http_status_for_error_object 40 | else 41 | content_type = 'text/plain' 42 | error_code = HealthCheck.http_status_for_error_text 43 | end 44 | [ (healthy ? 200 : error_code), { 'Content-Type' => content_type }, [msg] ] 45 | else 46 | @app.call(env) 47 | end 48 | end 49 | 50 | protected 51 | 52 | def parse_env(env) 53 | uri = env['PATH_INFO'] 54 | if uri =~ /^\/#{Regexp.escape HealthCheck.uri}(\/([-_0-9a-zA-Z]*))?(\.(\w*))?$/ 55 | checks = $2.to_s == '' ? ['standard'] : $2.split('_') 56 | response_type = $4.to_s 57 | middleware_checks = checks & HealthCheck.middleware_checks 58 | full_stack_checks = (checks - HealthCheck.middleware_checks) - ['and'] 59 | [response_type, middleware_checks, full_stack_checks ] 60 | end 61 | end 62 | 63 | def ip_blocked(env) 64 | return false if HealthCheck.origin_ip_whitelist.blank? 65 | req = Rack::Request.new(env) 66 | request_ipaddr = IPAddr.new(req.ip) 67 | unless HealthCheck.origin_ip_whitelist.any? { |addr| IPAddr.new(addr).include? request_ipaddr } 68 | [ HealthCheck.http_status_for_ip_whitelist_error, 69 | { 'Content-Type' => 'text/plain' }, 70 | [ 'Health check is not allowed for the requesting IP' ] 71 | ] 72 | end 73 | end 74 | 75 | def not_authenticated(env) 76 | return false unless HealthCheck.basic_auth_username && HealthCheck.basic_auth_password 77 | auth = MiddlewareHealthcheck::Request.new(env) 78 | if auth.provided? && auth.basic? && Rack::Utils.secure_compare(HealthCheck.basic_auth_username, auth.username) && Rack::Utils.secure_compare(HealthCheck.basic_auth_password, auth.password) 79 | env['REMOTE_USER'] = auth.username 80 | return false 81 | end 82 | [ 401, 83 | { 'Content-Type' => 'text/plain', 'WWW-Authenticate' => 'Basic realm="Health Check"' }, 84 | [ ] 85 | ] 86 | end 87 | 88 | class Request < Rack::Auth::AbstractRequest 89 | def basic? 90 | "basic" == scheme 91 | end 92 | 93 | def credentials 94 | @credentials ||= params.unpack("m*").first.split(/:/, 2) 95 | end 96 | 97 | def username 98 | credentials.first 99 | end 100 | 101 | def password 102 | credentials.last 103 | end 104 | end 105 | 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/health_check.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2013 Ian Heggie, released under the MIT license. 2 | # See MIT-LICENSE for details. 3 | 4 | module HealthCheck 5 | 6 | class Engine < ::Rails::Engine 7 | cattr_accessor :routes_explicitly_defined 8 | end 9 | 10 | # Log level 11 | mattr_accessor :log_level 12 | self.log_level = 'info' 13 | 14 | # Text output upon success 15 | mattr_accessor :success 16 | self.success = "success" 17 | 18 | # Text output upon failure 19 | mattr_accessor :failure 20 | self.failure = "health_check failed" 21 | 22 | # Timeout in seconds used when checking smtp server 23 | mattr_accessor :smtp_timeout 24 | self.smtp_timeout = 30.0 25 | 26 | # http status code used when plain text error message is output 27 | mattr_accessor :http_status_for_error_text 28 | self.http_status_for_error_text = 500 29 | 30 | # http status code used when an error object is output (json or xml) 31 | mattr_accessor :http_status_for_error_object 32 | self.http_status_for_error_object = 500 33 | 34 | # http status code used when the ip is not allowed for the request 35 | mattr_accessor :http_status_for_ip_whitelist_error 36 | self.http_status_for_ip_whitelist_error = 403 37 | 38 | # check remote_ip rather than ip for ip whitelist 39 | mattr_accessor :accept_proxied_requests 40 | self.accept_proxied_requests = false 41 | 42 | # ips allowed to perform requests 43 | mattr_accessor :origin_ip_whitelist 44 | self.origin_ip_whitelist = [] 45 | 46 | # max-age of response in seconds 47 | # cache-control is public when max_age > 1 and basic authentication is used 48 | mattr_accessor :max_age 49 | self.max_age = 1 50 | 51 | # s3 buckets 52 | mattr_accessor :buckets 53 | self.buckets = {} 54 | 55 | # rabbitmq 56 | mattr_accessor :rabbitmq_config 57 | self.rabbitmq_config = {} 58 | 59 | # health check uri path 60 | mattr_accessor :uri 61 | self.uri = 'health_check' 62 | 63 | # Basic Authentication 64 | mattr_accessor :basic_auth_username, :basic_auth_password 65 | self.basic_auth_username = nil 66 | self.basic_auth_password = nil 67 | 68 | # Array of custom check blocks 69 | mattr_accessor :custom_checks 70 | mattr_accessor :full_checks 71 | mattr_accessor :standard_checks 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'] 74 | self.standard_checks = [ 'database', 'migrations', 'custom', 'emailconf' ] 75 | 76 | # Middleware based checks 77 | mattr_accessor :middleware_checks 78 | self.middleware_checks = [ 'middleware' ] 79 | 80 | mattr_accessor :installed_as_middleware 81 | 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 = ENV['REDIS_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 111 | end 112 | 113 | def self.setup 114 | yield self 115 | end 116 | 117 | end 118 | 119 | require 'health_check/version' 120 | require 'health_check/base_health_check' 121 | require 'health_check/resque_health_check' 122 | require 'health_check/s3_health_check' 123 | require 'health_check/redis_health_check' 124 | require 'health_check/elasticsearch_health_check' 125 | require 'health_check/sidekiq_health_check' 126 | require 'health_check/utils' 127 | require 'health_check/health_check_controller' 128 | require 'health_check/health_check_routes' 129 | require 'health_check/middleware_health_check' 130 | require 'health_check/rabbitmq_health_check' 131 | 132 | # vi: sw=2 sm ai: 133 | -------------------------------------------------------------------------------- /test/fake_smtp_server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'socket' 4 | require 'openssl' 5 | 6 | class FakeSmtpServer 7 | def initialize(port) 8 | @port = port 9 | @socket = TCPServer.new(@port) 10 | @client = @orig_client = nil 11 | end 12 | 13 | def start 14 | return unless @client.nil? 15 | 16 | puts "fake_smtp_server: Waiting for one connection to port #{@port} ..." 17 | @client = @socket.accept 18 | 19 | send '220 dummy-smtp.example.com SMTP' 20 | cmd = receive 21 | 22 | while cmd !~ /^QUIT\r/ 23 | if cmd =~ /^HELO(.*)\r/ 24 | if ENV['FAIL_SMTP'] == 'HELO' 25 | send '550 Access Denied – Invalid HELO name' 26 | else 27 | send '250-Welcome to a dummy smtp server' 28 | unless ENV['SMTP_STARTTLS'] == 'DISABLED' 29 | send '250-STARTTLS' 30 | end 31 | send '250-AUTH PLAIN LOGIN' 32 | send '250 Ok' 33 | end 34 | elsif cmd =~ /^AUTH(.*)\r/ 35 | if ENV['FAIL_SMTP'] == 'AUTH' 36 | send '535 5.7.8 Authentication credentials invalid' 37 | else 38 | send '235 2.7.0 Authentication successful' 39 | end 40 | elsif cmd =~ /^STARTTLS\r/ 41 | if ENV['SMTP_STARTTLS'] == 'DISABLED' 42 | send '502 STARTTLS is disabled!' 43 | end 44 | send '220 Ready to start TLS' 45 | if ENV['FAIL_SMTP'] == 'STARTTLS' 46 | cmd = receive 47 | return close 48 | end 49 | @orig_client = @client 50 | @client = tlsconnect(@client) 51 | else 52 | send '502 I am so dumb I only understand HELO, AUTH, STARTTLS and QUIT which always return a success status' 53 | end 54 | 55 | cmd = receive 56 | end 57 | send '221 Bye Bye' 58 | 59 | close 60 | end 61 | 62 | private 63 | 64 | def close 65 | @client.close unless @client.nil? 66 | @orig_client.close unless @orig_client.nil? 67 | end 68 | 69 | def send(line) 70 | @client.puts line 71 | puts "-> #{line}" 72 | end 73 | 74 | def receive 75 | line = @client.gets 76 | puts "<- #{line}" 77 | line 78 | end 79 | 80 | def ssl_socket(client, context) 81 | OpenSSL::SSL::SSLSocket.new(client, context) 82 | end 83 | 84 | def ssl_context 85 | @_ssl_context ||= begin 86 | key, cert = generate_certificate 87 | 88 | context = OpenSSL::SSL::SSLContext.new 89 | context.key = key 90 | context.cert = cert 91 | context.verify_mode = OpenSSL::SSL::VERIFY_NONE 92 | context.min_version = nil 93 | context 94 | end 95 | end 96 | 97 | # Pass socket from TCPServer.new accept 98 | def tlsconnect(client) 99 | ssl_client = ssl_socket(client, ssl_context) 100 | puts '=> TLS connection started' 101 | ssl_client.accept 102 | puts '=> TLS connection established' 103 | 104 | ssl_client 105 | end 106 | 107 | def generate_certificate 108 | key = OpenSSL::PKey::RSA.new(2048) 109 | name = OpenSSL::X509::Name.parse('CN=localhost') 110 | 111 | cert = OpenSSL::X509::Certificate.new 112 | cert.version = 2 113 | cert.serial = 0 114 | cert.not_before = Time.now 115 | cert.not_after = Time.now + 3600 116 | 117 | cert.public_key = key.public_key 118 | cert.subject = name 119 | 120 | extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert 121 | 122 | cert.add_extension extension_factory.create_extension('basicConstraints', 'CA:FALSE', true) 123 | cert.add_extension extension_factory.create_extension('keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') 124 | cert.add_extension extension_factory.create_extension('subjectKeyIdentifier', 'hash') 125 | 126 | cert.issuer = name 127 | cert.sign key, OpenSSL::Digest::SHA256.new 128 | 129 | [key, cert] 130 | end 131 | end 132 | 133 | FakeSmtpServer.new(3555).start 134 | 135 | puts 'fake_smtp_server: Exiting now the conversation has finished.' 136 | exit 0 137 | 138 | # Tested with irb script: 139 | # require 'net/smtp' 140 | # 141 | # status = '' 142 | # begin 143 | # if @skip_external_checks 144 | # status = '250' 145 | # else 146 | # smtp = Net::SMTP.new('localhost', 3555) 147 | # smtp.enable_starttls 148 | # smtp.open_timeout = 10 149 | # smtp.read_timeout = 10 150 | # smtp.start('domain', 'user_name', 'password', :plain) do 151 | # status = smtp.helo('domain').status 152 | # end 153 | # end 154 | # rescue Exception => ex 155 | # status = ex.to_s 156 | # end 157 | # (status =~ /^250/) ? 'PASS' : "FAILED SMTP: #{status || 'unexpected error'}. " 158 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dest: xenial 2 | os: linux 3 | # Released April 2018, Support ended: April 2023 4 | # ruby 2.4.9, 2.5.3, 2.5.7, 2.6.5 (default) and 2.7.0 pre-installed on travis 5 | addons: 6 | apt: 7 | packages: 8 | - net-tools 9 | 10 | cache: bundler 11 | language: ruby 12 | notifications: 13 | email: 14 | on_success: change 15 | on_failure: always 16 | 17 | before_install: 18 | - gem update --system $RUBYGEMS_VERSION 19 | - gem --version 20 | - gem install bundler ${BUNDLER_VERSION:+-v} ${BUNDLER_VERSION} 21 | - gem install smarter_bundler 22 | - bundle --version 23 | - mkdir -p tmp/bundle 24 | 25 | bundler_args: "--binstubs" 26 | 27 | script: ./test/test_with_railsapp 28 | 29 | jobs: 30 | fast_finish: true 31 | 32 | allow_failures: 33 | 34 | - rvm: 3.0.1 35 | gemfile: test/rails_6.1.gemfile 36 | env: 37 | - RAILS_VERSION=6.1 38 | - MIDDLEWARE=YES 39 | 40 | - rvm: 3.0.1 41 | gemfile: test/rails_edge.gemfile 42 | env: 43 | - RAILS_VERSION=edge 44 | - MIDDLEWARE=YES 45 | - SMTP_STARTTLS=DISABLED 46 | 47 | - rvm: ruby-head 48 | gemfile: test/rails_6.1.gemfile 49 | env: 50 | - RAILS_VERSION=6.1 51 | - MIDDLEWARE=YES 52 | - SMTP_STARTTLS=DISABLED 53 | 54 | - rvm: ruby-head 55 | gemfile: test/rails_edge.gemfile 56 | env: 57 | - RAILS_VERSION=edge 58 | - MIDDLEWARE=YES 59 | - SMTP_STARTTLS=DISABLED 60 | 61 | 62 | include: 63 | # ------------------------------------- 64 | # Standard 65 | 66 | # ruby 5.0 Jun. 2016 67 | 68 | - rvm: 2.2.2 69 | gemfile: test/rails_5.0.gemfile 70 | env: 71 | - RAILS_VERSION=5.0 72 | - MIDDLEWARE=no 73 | - RUBYGEMS_VERSION=2.7.11 74 | - BUNDLER_VERSION=1.17.3 75 | 76 | - rvm: 2.3.6 77 | gemfile: test/rails_5.0.gemfile 78 | env: 79 | - RAILS_VERSION=5.0 80 | - MIDDLEWARE=no 81 | 82 | # rails 5.1 sometime before May 2017 83 | 84 | - rvm: 2.2.2 85 | gemfile: test/rails_5.1.gemfile 86 | env: 87 | - RAILS_VERSION=5.1 88 | - MIDDLEWARE=no 89 | - RUBYGEMS_VERSION=2.7.11 90 | - BUNDLER_VERSION=1.17.3 91 | 92 | - rvm: 2.3.6 93 | gemfile: test/rails_5.1.gemfile 94 | env: 95 | - RAILS_VERSION=5.1 96 | - MIDDLEWARE=no 97 | - BUNDLER_VERSION=1.17.3 98 | 99 | - rvm: 2.4.3 100 | gemfile: test/rails_5.1.gemfile 101 | env: 102 | - RAILS_VERSION=5.1 103 | - MIDDLEWARE=no 104 | 105 | # ------------------------------------- 106 | # Middleware 107 | 108 | # ruby 5.0 Jun. 2016 109 | 110 | - rvm: 2.2.2 111 | gemfile: test/rails_5.0.gemfile 112 | env: 113 | - RAILS_VERSION=5.0 114 | - MIDDLEWARE=yes 115 | - RUBYGEMS_VERSION=2.7.11 116 | - BUNDLER_VERSION=1.17.3 117 | 118 | - rvm: 2.3.6 119 | gemfile: test/rails_5.0.gemfile 120 | env: 121 | - RAILS_VERSION=5.0 122 | - MIDDLEWARE=yes 123 | 124 | # rails 5.1 sometime before May 2017 125 | 126 | - rvm: 2.2.2 127 | gemfile: test/rails_5.1.gemfile 128 | env: 129 | - RAILS_VERSION=5.1 130 | - MIDDLEWARE=yes 131 | - RUBYGEMS_VERSION=2.7.11 132 | - BUNDLER_VERSION=1.17.3 133 | 134 | - rvm: 2.4.3 135 | gemfile: test/rails_5.1.gemfile 136 | env: 137 | - RAILS_VERSION=5.1 138 | - MIDDLEWARE=yes 139 | 140 | 141 | # rails 5.2 April 2018 142 | # ------------------------------------- 143 | # INSTALLED AS MIDDLEWARE 144 | 145 | 146 | # rails 6.0 147 | 148 | - rvm: 2.5.0 149 | gemfile: test/rails_6.0.gemfile 150 | env: 151 | - RAILS_VERSION=6.0 152 | - MIDDLEWARE=YES 153 | 154 | - rvm: 2.6.7 155 | gemfile: test/rails_6.0.gemfile 156 | env: 157 | - RAILS_VERSION=6.0 158 | - MIDDLEWARE=YES 159 | 160 | # Not testing past recommended ruby version as listed on https://www.fastruby.io/blog/ruby/rails/versions/compatibility-table.html 161 | 162 | # -------------------------------------- 163 | - rvm: 2.5.0 164 | gemfile: test/rails_6.1.gemfile 165 | env: 166 | - RAILS_VERSION=6.1 167 | - MIDDLEWARE=YES 168 | 169 | - rvm: 2.6.6 170 | gemfile: test/rails_6.1.gemfile 171 | env: 172 | - RAILS_VERSION=6.1 173 | - MIDDLEWARE=YES 174 | 175 | - rvm: 2.7.3 176 | gemfile: test/rails_6.1.gemfile 177 | env: 178 | - RAILS_VERSION=6.1 179 | - MIDDLEWARE=YES 180 | 181 | - rvm: 3.0.1 182 | gemfile: test/rails_6.1.gemfile 183 | env: 184 | - RAILS_VERSION=6.1 185 | - MIDDLEWARE=YES 186 | - SMTP_STARTTLS=DISABLED 187 | 188 | # ------------------ 189 | # FIXME 190 | # ------------------ 191 | 192 | - rvm: 3.0.1 193 | gemfile: test/rails_6.1.gemfile 194 | env: 195 | - RAILS_VERSION=6.1 196 | - MIDDLEWARE=YES 197 | 198 | - rvm: 3.0.1 199 | gemfile: test/rails_edge.gemfile 200 | env: 201 | - RAILS_VERSION=edge 202 | - MIDDLEWARE=YES 203 | - SMTP_STARTTLS=DISABLED 204 | 205 | - rvm: ruby-head 206 | gemfile: test/rails_6.1.gemfile 207 | env: 208 | - RAILS_VERSION=6.1 209 | - MIDDLEWARE=YES 210 | - SMTP_STARTTLS=DISABLED 211 | 212 | - rvm: ruby-head 213 | gemfile: test/rails_edge.gemfile 214 | env: 215 | - RAILS_VERSION=edge 216 | - MIDDLEWARE=YES 217 | - SMTP_STARTTLS=DISABLED 218 | 219 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | = Change Log = 2 | 3 | * 3.1.0 - 26 May 2021 4 | * Updated README to clarify railsN branch status (they are intended to be stable, development to be a feature branch off them or master) 5 | * Updated README with all the settings 6 | * Updated all the branches to work with the latest travis and gem changes, as some gems needed to be locked down. 7 | * Updated to test rails 6.0 and 6.1 8 | * Got all travis tests passing 9 | * Removed old, unused branches 10 | * Merged PR Fix broken Cache-Control headers #93 from felixbuenemann 11 | * Merged PR S3 should not depend on Rails secrets file #77 by natefaerber 12 | * Merged PR RabbitMQ Health check #98 from rhuanbarreto 13 | * Merged PR Use remote_ip to accept proxy-forwarded requests #102 by alessio-signorini but made it optional 14 | * Fixed up failure setting to match previous output on error, and use it as a prefix when the error message is also output (not by default) 15 | * Always log the error to rails log even if not including in html response 16 | * Merged PR ensure REDIS connections are closed #88 from yld 17 | * Merged PR more robust cache check #90 from masciugo 18 | * Merged PR Add log_level config setting which defaults to 'info'. #97 from FloHeinle 19 | * Merged PR get rid of old school hash rockets syntax #92 from DmytroStepaniuk 20 | * Merged PR Converted whitelist testing to use IPAddr objects. #64 jordanyaker 21 | * Added on_success and on_failure callbacks 22 | * Makes sure errors are seperated by a period and a space and a period always ends the list of errors 23 | 24 | * 3.0.0 25 | * First release on rails5 branch 26 | * Depends on railties rather than rails so it can be used with trimmed down stacks 27 | * Corrected ruby version required to match rails 28 | * Cleaned up README 29 | * redis_url now defaults to nil (url determined by redis gem) 30 | * Cleaned out rails 4.0 dependent code 31 | * Cleaned up test code and updated to rails 5 standards, uses smarter_bundler to handle gem ruby version isssues 32 | * Added rails 5.1 test 33 | * Split all releases to this rails* branchs - master is only for edge development 34 | 35 | * 2.7.0 36 | * Add ability to check health of redis when url is non-standard redis url 37 | * 2.6.0 38 | * Add named custom checks 39 | * 2.5.0 40 | * Added whitelist for IP# (Thanks Fernando Alvarez) 41 | * reworked whitelist PR 42 | * Expanded tests for whitelist and basic authentication 43 | * reworked middleware, simplified error codes, added whitelist and basic authentication into middleware 44 | * Removed unit tests as they where aonly applicable under rails 2.3 when installed in vendor/plugins 45 | * #55 by mnoack - correct binstubs arg in test 46 | * #54 by gkop - Lograge config snippet works with Rails 4.2.7.1, lograge 0.4.1 47 | * Used ideas from #52 - use middleware to catch Rails stack exceptions 48 | * #51 by tokenshift - Fixing NameError on `basic_auth_username`. 49 | * Changes to address #50 by fillphafftek - allow standard check to be run from middleware if configured to do so, removed requirement for "middleware" to be passed in url for middleware tests 50 | * 2.4.0 51 | * Added tests for middleware 52 | * Changed contributed middleware code to conform to existing url scheme 53 | * Allow both GET and POST calls 54 | * Prefer config.uri for changing route prefix 55 | * 2.3.0 56 | * Fix route reload issue 57 | * Various fixes to get tests working with bundle/jruby and other gem issues 58 | * Document additional branches 59 | * Fix route reload issue (auto routing previosuly conflicted with devise) 60 | * Removed ref to rails 2.3, 3.* 61 | 62 | * 2.2.1 63 | * Adjust private/public cache-control based on max_age set 64 | * 2.2.0 65 | * Add max_age so we can control the caching of responses, don't run tests if Last-modified still matches 66 | * Added basic auth - Thanks omadahealth 67 | * A few macinations due to gem changes and avoidning triggering deprecation notices 68 | * Add single quote to README to make the configuration file valid - Thanks Ryan Selk 69 | * Fixed README formatting 70 | * 2.1.0 71 | * Updated contributed tests so there is both the forced check and a *-if-present check which tests if the gem's class is loaded 72 | * Added resque-redis check - Thanks filiphaftek 73 | * In addition to adding a test file to S3, we will also try to delete it to confirm that delete operations are possible - Thanks Anton Dimitrov 74 | * Added redis, sidekiq-redis and s3 health-checks - Thanks Filip 75 | * Fix render options - Thanks Yuji Hanamura 76 | * Fix to always return a 200 status code on success rather than 304 (adds Last-Modified) - Thanks macgregordennis 77 | * Added Rails 5.0 tests 78 | 79 | * 2.0.0 - Removed silence - recommend to use a log filtering gem instead 80 | * 1.4.1 - Rails 4 and route changes 81 | * Now handles routes being generated multiple times by some gem / rails / ruby combinations - Previously multiple calls to health_check_routes where ignored, now explicit calls to health_check_route always adds the route but flags that it doesn't have to be added again on the end of the list 82 | * Uses ActiveRecord::Migration.check_pending! if available and returns the message if an exception is raised (Rails 4.0+) 83 | * Simplified routing rules down to one rule for Rails 3.0+ 84 | * Includes some changes for rails 4.1 (edge) - but still a work in progress 85 | * 1.3.1 - Include changes from contributers: 86 | * Migrations with dots are now handled 87 | * the list of checks for "full" / "all" can be configured 88 | * 1.2.0 - The gem can now be configured, including timeouts, status codes and text returned on success 89 | - Customn checks can be added via initializer like config.add_custom_check { CustomCheckClass.a_custom_check } 90 | - You can now request the response to be json or xml (via url or Content-accepted header) 91 | - reduced tests to the versions of ruby recomended for the different versions of rails 92 | * 1.1.2 - Change to bundler support for building gems, as jeweler gem was broken by v2.0.0 of rubygems 93 | * 1.1.0 - Include cache check (Thanks to https://github.com/mgomes1 ) and some changes to test setup to workaround and diagnose test failures under rvm 94 | * 1.0.2 - Included travis config and gemfiles used in travis tests in gem and changes to test setup so that gem test 95 | * 1.x - Includes Rails 3.x suppprt as an Engine 96 | * 0.x - Rails 2.3 97 | -------------------------------------------------------------------------------- /lib/health_check/utils.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2013 Ian Heggie, released under the MIT license. 2 | # See MIT-LICENSE for details. 3 | 4 | module HealthCheck 5 | class Utils 6 | 7 | @@default_smtp_settings = 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 16 | } 17 | 18 | cattr_accessor :default_smtp_settings 19 | 20 | # process an array containing a list of checks 21 | def self.process_checks(checks, called_from_middleware = false) 22 | errors = '' 23 | checks.each do |check| 24 | case check 25 | when 'and', 'site' 26 | # do nothing 27 | when "database" 28 | HealthCheck::Utils.get_database_version 29 | when "email" 30 | errors << HealthCheck::Utils.check_email 31 | when "emailconf" 32 | errors << HealthCheck::Utils.check_email if HealthCheck::Utils.mailer_configured? 33 | when "migrations", "migration" 34 | if defined?(ActiveRecord::Migration) and ActiveRecord::Migration.respond_to?(:check_pending!) 35 | # Rails 4+ 36 | begin 37 | ActiveRecord::Migration.check_pending! 38 | rescue ActiveRecord::PendingMigrationError => ex 39 | errors << ex.message 40 | end 41 | else 42 | database_version = HealthCheck::Utils.get_database_version 43 | migration_version = HealthCheck::Utils.get_migration_version 44 | if database_version.to_i != migration_version.to_i 45 | errors << "Current database version (#{database_version}) does not match latest migration (#{migration_version}). " 46 | end 47 | end 48 | when 'cache' 49 | errors << HealthCheck::Utils.check_cache 50 | when 'resque-redis-if-present' 51 | errors << HealthCheck::ResqueHealthCheck.check if defined?(::Resque) 52 | when 'sidekiq-redis-if-present' 53 | errors << HealthCheck::SidekiqHealthCheck.check if defined?(::Sidekiq) 54 | when 'redis-if-present' 55 | errors << HealthCheck::RedisHealthCheck.check if defined?(::Redis) 56 | when 's3-if-present' 57 | errors << HealthCheck::S3HealthCheck.check if defined?(::Aws) 58 | when 'elasticsearch-if-present' 59 | errors << HealthCheck::ElasticsearchHealthCheck.check if defined?(::Elasticsearch) 60 | when 'resque-redis' 61 | errors << HealthCheck::ResqueHealthCheck.check 62 | when 'sidekiq-redis' 63 | errors << HealthCheck::SidekiqHealthCheck.check 64 | when 'redis' 65 | errors << HealthCheck::RedisHealthCheck.check 66 | when 's3' 67 | errors << HealthCheck::S3HealthCheck.check 68 | when 'elasticsearch' 69 | errors << HealthCheck::ElasticsearchHealthCheck.check 70 | when 'rabbitmq' 71 | errors << HealthCheck::RabbitMQHealthCheck.check 72 | when "standard" 73 | errors << HealthCheck::Utils.process_checks(HealthCheck.standard_checks, called_from_middleware) 74 | when "middleware" 75 | errors << "Health check not called from middleware - probably not installed as middleware." unless called_from_middleware 76 | when "custom" 77 | HealthCheck.custom_checks.each do |name, list| 78 | list.each do |custom_check| 79 | errors << custom_check.call(self) 80 | end 81 | end 82 | when "all", "full" 83 | errors << HealthCheck::Utils.process_checks(HealthCheck.full_checks, called_from_middleware) 84 | else 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 92 | end 93 | errors << '. ' unless errors == '' || errors.end_with?('. ') 94 | end 95 | return errors.strip 96 | rescue => e 97 | return e.message 98 | end 99 | 100 | def self.db_migrate_path 101 | # Lazy initialisation so Rails.root will be defined 102 | @@db_migrate_path ||= File.join(::Rails.root, 'db', 'migrate') 103 | end 104 | 105 | def self.db_migrate_path=(value) 106 | @@db_migrate_path = value 107 | end 108 | 109 | def self.mailer_configured? 110 | defined?(ActionMailer::Base) && (ActionMailer::Base.delivery_method != :smtp || HealthCheck::Utils.default_smtp_settings != ActionMailer::Base.smtp_settings) 111 | end 112 | 113 | def self.get_database_version 114 | ActiveRecord::Migrator.current_version if defined?(ActiveRecord) 115 | end 116 | 117 | def self.get_migration_version(dir = self.db_migrate_path) 118 | latest_migration = nil 119 | Dir[File.join(dir, "[0-9]*_*.rb")].each do |f| 120 | l = f.scan(/0*([0-9]+)_[_.a-zA-Z0-9]*.rb/).first.first rescue -1 121 | latest_migration = l if !latest_migration || l.to_i > latest_migration.to_i 122 | end 123 | latest_migration 124 | end 125 | 126 | def self.check_email 127 | case ActionMailer::Base.delivery_method 128 | when :smtp 129 | HealthCheck::Utils.check_smtp(ActionMailer::Base.smtp_settings, HealthCheck.smtp_timeout) 130 | when :sendmail 131 | HealthCheck::Utils.check_sendmail(ActionMailer::Base.sendmail_settings) 132 | else 133 | '' 134 | end 135 | end 136 | 137 | def self.check_sendmail(settings) 138 | File.executable?(settings[:location]) ? '' : 'no sendmail executable found. ' 139 | end 140 | 141 | def self.check_smtp(settings, timeout) 142 | status = '' 143 | begin 144 | if @skip_external_checks 145 | status = '250' 146 | else 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 153 | end 154 | end 155 | rescue Exception => ex 156 | status = ex.to_s 157 | end 158 | (status =~ /^250/) ? '' : "SMTP: #{status || 'unexpected error'}. " 159 | end 160 | 161 | def self.check_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 182 | end 183 | 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = health_check gem 2 | 3 | Simple health check of Rails 5.x and 6.x apps for use with Pingdom, NewRelic, EngineYard etc. 4 | 5 | The basic goal is to quickly check that rails is up and running and that it has access to correctly configured resources (database, email gateway) 6 | 7 | Check the latest README {master}[https://github.com/ianheggie/health_check/tree/master] for other versions 8 | 9 | Use gem versions for stable releases: 10 | * ~> 3.x for Rails 5.x and 6.x (etc until there is a proven need to split off another branch), 11 | * ~> 2.x for Rails 4.x, 12 | * ~> 1.7 for Rails 3.x, 13 | * ~> 1.6.3 for Rails 2.3 14 | 15 | Note: it is best to pin to a specific commit if using a development branch as sometimes tests break. 16 | 17 | health_check provides various monitoring URIs, for example: 18 | 19 | curl localhost:3000/health_check 20 | success 21 | 22 | curl localhost:3000/health_check/all.json 23 | {"healthy":true,"message":"success"} 24 | 25 | curl localhost:3000/health_check/database_cache_migration.xml 26 | 27 | 28 | true 29 | success 30 | 31 | 32 | You may also issue POST calls instead of GET to these urls. 33 | 34 | On failure (detected by health_check) a 500 http status is returned with a simple explanation of the failure 35 | (if include_error_in_response_body is true) 36 | 37 | curl localhost:3000/health_check/fail 38 | health_check failed: invalid argument to health_test. 39 | 40 | The health_check controller disables sessions for versions that eagerly load sessions. 41 | 42 | == Checks 43 | 44 | * standard (default) - site, database and migrations checks are run plus email if ActionMailer is defined and it is not using the default configuration 45 | * all / full - all checks are run (can be overriden in config block) 46 | * cache - checks that a value can be written to the cache 47 | * custom - runs checks added via config.add_custom_check 48 | * database - checks that the current migration level can be read from the database 49 | * email - basic check of email - :test returns true, :sendmail checks file is present and executable, :smtp sends HELO command to server and checks response 50 | * migration - checks that the database migration level matches that in db/migrations 51 | * rabbitmq - RabbitMQ Health Check 52 | * redis / redis-if-present - checks Redis connectivity 53 | * resque-redis / resque-redis-if-present - checks Resque connectivity to Redis 54 | * s3 / s3-if-present - checks proper permissions to s3 buckets 55 | * sidekiq-redis / sidekiq-redis-if-present - checks Sidekiq connectivity to Redis 56 | * elasticsearch / elasticsearch-if-present - checks Elasticsearch connectivity 57 | * site - checks rails is running sufficiently to render text 58 | 59 | Some checks have a *-if-present form, which only runs the check if the corresponding library has been required. 60 | 61 | The email gateway is not checked unless the smtp settings have been changed. 62 | Specify full or include email in the list of checks to verify the smtp settings 63 | (eg use 127.0.0.1 instead of localhost). 64 | 65 | Note: rails also checks migrations by default in development mode and throws an ActiveRecord::PendingMigrationError exception (http error 500) if there is an error 66 | 67 | == Installation 68 | 69 | Add the following line to Gemfile (after the rails gems are listed) 70 | 71 | gem 'health_check' 72 | 73 | And then execute 74 | 75 | bundle 76 | 77 | Or install it yourself as: 78 | 79 | gem install health_check 80 | 81 | == Configuration 82 | 83 | To change the configuration of health_check, create a file `config/initializers/health_check.rb` and add a configuration block like: 84 | 85 | HealthCheck.setup do |config| 86 | 87 | # uri prefix (no leading slash) 88 | config.uri = 'health_check' 89 | 90 | # Text output upon success 91 | config.success = 'success' 92 | 93 | # Text output upon failure 94 | config.failure = 'health_check failed' 95 | 96 | # Disable the error message to prevent /health_check from leaking 97 | # sensitive information 98 | config.include_error_in_response_body = false 99 | 100 | # Log level (success or failure message with error details is sent to rails log unless this is set to nil) 101 | config.log_level = 'info' 102 | 103 | # Timeout in seconds used when checking smtp server 104 | config.smtp_timeout = 30.0 105 | 106 | # http status code used when plain text error message is output 107 | # Set to 200 if you want your want to distinguish between partial (text does not include success) and 108 | # total failure of rails application (http status of 500 etc) 109 | 110 | config.http_status_for_error_text = 500 111 | 112 | # http status code used when an error object is output (json or xml) 113 | # Set to 200 if you want to distinguish between partial (healthy property == false) and 114 | # total failure of rails application (http status of 500 etc) 115 | 116 | config.http_status_for_error_object = 500 117 | 118 | # bucket names to test connectivity - required only if s3 check used, access permissions can be mixed 119 | config.buckets = {'bucket_name' => [:R, :W, :D]} 120 | 121 | # You can customize which checks happen on a standard health check, eg to set an explicit list use: 122 | config.standard_checks = [ 'database', 'migrations', 'custom' ] 123 | 124 | # Or to exclude one check: 125 | config.standard_checks -= [ 'emailconf' ] 126 | 127 | # You can set what tests are run with the 'full' or 'all' parameter 128 | config.full_checks = ['database', 'migrations', 'custom', 'email', 'cache', 'redis', 'resque-redis', 'sidekiq-redis', 's3'] 129 | 130 | # Add one or more custom checks that return a blank string if ok, or an error message if there is an error 131 | config.add_custom_check do 132 | CustomHealthCheck.perform_check # any code that returns blank on success and non blank string upon failure 133 | end 134 | 135 | # Add another custom check with a name, so you can call just specific custom checks. This can also be run using 136 | # the standard 'custom' check. 137 | # You can define multiple tests under the same name - they will be run one after the other. 138 | config.add_custom_check('sometest') do 139 | CustomHealthCheck.perform_another_check # any code that returns blank on success and non blank string upon failure 140 | end 141 | 142 | # max-age of response in seconds 143 | # cache-control is public when max_age > 1 and basic_auth_username is not set 144 | # You can force private without authentication for longer max_age by 145 | # setting basic_auth_username but not basic_auth_password 146 | config.max_age = 1 147 | 148 | # Protect health endpoints with basic auth 149 | # These default to nil and the endpoint is not protected 150 | config.basic_auth_username = 'my_username' 151 | config.basic_auth_password = 'my_password' 152 | 153 | # Whitelist requesting IPs by a list of IP and/or CIDR ranges, either IPv4 or IPv6 (uses IPAddr.include? method to check) 154 | # Defaults to blank which allows any IP 155 | config.origin_ip_whitelist = %w(123.123.123.123 10.11.12.0/24 2400:cb00::/32) 156 | 157 | # Use ActionDispatch::Request's remote_ip method when behind a proxy to pick up the real remote IP for origin_ip_whitelist check 158 | # Otherwise uses Rack::Request's ip method (the default, and always used by Middleware), which is more susceptable to spoofing 159 | # See https://stackoverflow.com/questions/10997005/whats-the-difference-between-request-remote-ip-and-request-ip-in-rails 160 | config.accept_proxied_requests = false 161 | 162 | # http status code used when the ip is not allowed for the request 163 | config.http_status_for_ip_whitelist_error = 403 164 | 165 | # rabbitmq 166 | config.rabbitmq_config = {} 167 | 168 | # When redis url/password is non-standard 169 | config.redis_url = 'redis_url' # default ENV['REDIS_URL'] 170 | # Only included if set, as url can optionally include passwords as well 171 | config.redis_password = 'redis_password' # default ENV['REDIS_PASSWORD'] 172 | 173 | # Failure Hooks to do something more ... 174 | # checks lists the checks requested 175 | config.on_failure do |checks, msg| 176 | # log msg somewhere 177 | end 178 | 179 | config.on_success do |checks| 180 | # flag that everything is well 181 | end 182 | 183 | 184 | end 185 | 186 | You may call add_custom_check multiple times with different tests. These tests will be included in the default list ("standard"). 187 | 188 | If you have a catchall route then add the following line above the catch all route (in `config/routes.rb`): 189 | health_check_routes 190 | 191 | === Installing As Middleware 192 | 193 | Install health_check as middleware if you want to sometimes ignore exceptions from later parts of the Rails middleware stack, 194 | eg DB connection errors from QueryCache. The "middleware" check will fail if you have not installed health_check as middleware. 195 | 196 | To install health_check as middleware add the following line to the config/application.rb: 197 | config.middleware.insert_after Rails::Rack::Logger, HealthCheck::MiddlewareHealthcheck 198 | 199 | Note: health_check is installed as a full rails engine even if it has been installed as middleware. This is so the 200 | remaining checks continue to run through the complete rails stack. 201 | 202 | You can also adjust what checks are run from middleware, eg if you want to exclude the checking of the database etc, then set 203 | config.middleware_checks = ['middleware', 'standard', 'custom'] 204 | config.standard_checks = ['middleware', 'custom'] 205 | 206 | Middleware checks are run first, and then full stack checks. 207 | When installed as middleware, exceptions thrown when running the full stack tests are formatted in the standard way. 208 | 209 | == Uptime Monitoring 210 | 211 | Use a website monitoring service to check the url regularly for the word "success" (without the quotes) rather than just a 200 http status so 212 | that any substitution of a different server or generic information page should also be reported as an error. 213 | 214 | If an error is encounted, the text "health_check failed: some error message/s" will be returned and the http status will be 500. 215 | 216 | See 217 | 218 | * Pingdom Website Monitoring - https://www.pingdom.com 219 | * NewRelic Availability Monitoring - http://newrelic.com/docs/features/availability-monitoring-faq 220 | * Engine Yard's guide - https://support.cloud.engineyard.com/entries/20996821-monitor-application-uptime (although the guide is based on fitter_happier plugin it will also work with this gem) 221 | * Nagios check_http (with -s success) - https://www.nagios-plugins.org/doc/man/check_http.html 222 | * Any other montoring service that can be set to check for the word success in the text returned from a url 223 | 224 | === Requesting Json and XML responses 225 | 226 | Health_check will respond with an encoded hash object if json or xml is requested. 227 | Either set the HTTP Accept header or append .json or .xml to the url. 228 | 229 | The hash contains two keys: 230 | * healthy - true if requested checks pass (boolean) 231 | * message - text message ("success" or error message) 232 | 233 | The following commands 234 | 235 | curl -v localhost:3000/health_check.json 236 | curl -v localhost:3000/health_check/email.json 237 | curl -v -H "Accept: application/json" localhost:3000/health_check 238 | 239 | Will return a result with Content-Type: application/json and body like: 240 | 241 | {"healthy":true,"message":"success"} 242 | 243 | These following commands 244 | 245 | curl -v localhost:3000/health_check.xml 246 | curl -v localhost:3000/health_check/migration_cache.xml 247 | curl -v -H "Accept: text/xml" localhost:3000/health_check/cache 248 | 249 | Will return a result with Content-Type: application/xml and body like: 250 | 251 | 252 | 253 | true 254 | success 255 | 256 | 257 | See https://github.com/ianheggie/health_check/wiki/Ajax-Example for an Ajax example 258 | 259 | == Silencing log output 260 | 261 | It is recomended that you use silencer, lograge or one of the other log filtering gems. 262 | 263 | For example, with lograge use the following to exclude health_check from being logged: 264 | 265 | config.lograge.ignore_actions = ["HealthCheck::HealthCheckController#index"] 266 | 267 | Likewise you will probably want to exclude health_check from monitoring systems like newrelic. 268 | 269 | == Caching 270 | 271 | Cache-control is set with 272 | * public if max_age is > 1 and basic_auth_username is not set (otherwise private) 273 | * no-cache 274 | * must-revalidate 275 | * max-age (default 1) 276 | 277 | Last-modified is set to the current time (rounded down to a multiple of max_age when max_age > 1) 278 | 279 | == Known Issues 280 | 281 | * See https://github.com/ianheggie/health_check/issues 282 | * No inline documentation for methods 283 | * rvm gemsets breaks the test - specifically rvm use 1.9.3 works but rvm gemset use ruby-1.9.3-p385@health_check --create triggers a "Could not find gem 'coffee-rails (~> 3.2.1) ruby' in the gems available on this machine." error in the last call to bundle (installing health_check as a gem via a path into the temp railsapp) 284 | 285 | == Similar projects 286 | 287 | * fitter_happier plugin by atmos - plugin with similar goals, but not compatible with uptime, and does not check email gateway 288 | * HealthBit - inspired by this gem but with a fresh start as a simpler rack only application, no travis CI tests (yet?) but looks interesting. 289 | 290 | == Testing 291 | 292 | === Automated testing and other checks 293 | 294 | * {Gem Version}[http://badge.fury.io/rb/health_check] - Latest Gem 295 | * {}[https://travis-ci.org/ianheggie/health_check] - Travis CI 296 | * {}[https://codeclimate.com/github/ianheggie/health_check] - Code quality 297 | * {}[https://gemnasium.com/ianheggie/health_check] - Gem dependencies 298 | 299 | === Manual testing 300 | 301 | The instructions have been changed to using a vagrant virtual box for consistent results. 302 | 303 | Install vagrant 1.9.7 or later and virtual_box or other local virtual machine provider. Add the vagrant plugin called vbguest. 304 | 305 | vagrant plugin install vagrant-vbguest 306 | 307 | Create a temp directory for throw away testing, and clone the health_check gem into it 308 | 309 | mkdir -p ~/tmp 310 | cd ~/tmp 311 | git clone https://github.com/ianheggie/health_check.git ~/tmp/health_check 312 | 313 | The Vagrantfile includes provisioning rules to install chruby (ruby version control), 314 | ruby-build will also be installed and run to build various rubies under /opt/rubies. 315 | 316 | Use vagrant ssh to connect to the virtual box and run tests. 317 | 318 | The test script will package up and install the gem under a temporary path, create a dummy rails app configured for sqlite, 319 | install the gem, and then run up tests against the server. 320 | This will require TCP port 3456 to be free. 321 | 322 | Cd to the checked out health_check directory and then run the test as follows: 323 | 324 | cd ~/tmp/health_check 325 | 326 | vagrant up # this will also run vagrant provision and take some time 327 | # chruby and various ruby versions will be installed 328 | 329 | vagrant ssh 330 | 331 | cd /vagrant # the current directory on your host is mounted here on the virtual machine 332 | 333 | chruby 2.2.2 # or some other ruby version (run chruby with no arguments to see the current list) 334 | 335 | test/test_with_railsapp 336 | 337 | exit # from virtual machine when finished 338 | 339 | The script will first call `test/setup_railsapp` to setup a rails app with health_check installed and then 340 | run up the rails server and perform veraious tests. 341 | 342 | The script `test/setup_railsapp` will prompt you for which gemfile under test you wish to use to install the appropriate rails version, and then 343 | setup tmp/railsapp accordingly. 344 | 345 | The command `rake test` will also launch these tests, except it cannot install the bundler and rake gems if they are missing first (unlike test/test_with_railsapp) 346 | 347 | == Copyright 348 | 349 | Copyright (c) 2010-2021 Ian Heggie, released under the MIT license. 350 | See MIT-LICENSE for details. 351 | 352 | == Contributors 353 | 354 | Thanks go to the various people who have given feedback and suggestions via the issues list and pull requests. 355 | 356 | === Contributing 357 | 358 | Use gem versions for stable releases, or github branch / commits for development versions: 359 | * for Rails 5.x and 6.x use feature branched off master {master}[https://github.com/ianheggie/health_check/tree/master] for development; 360 | * for Rails 4.x use feature branches off the {rails4}[https://github.com/ianheggie/health_check/tree/rails4] stable branch for development; 361 | * for Rails 3.x use feature branches off the {rails3}[https://github.com/ianheggie/health_check/tree/rails3] stable branch for development; 362 | * for Rails 2.3 use feature branches off the {rails2.3}[https://github.com/ianheggie/health_check/tree/rails2.3] stable branch for development; 363 | 364 | 1. Fork it 365 | 2. Create your feature branch (`git checkout -b my-new-feature`) 366 | 3. Create a test that confirms your changes work 367 | 4. Update README.rdoc to explain enhancements, or add succinct comment in code when fixing bugs 368 | 5. Commit your changes (`git commit -am 'Add some feature'`) 369 | 6. Push to the branch (`git push origin my-new-feature`) 370 | 7. Create new Pull Request (Code with BDD tests and documentation are highly favoured) 371 | 372 | Feedback welcome! Especially with suggested replacement code, tests and documentation 373 | -------------------------------------------------------------------------------- /test/setup_railsapp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | route_prefix=medical_check 4 | 5 | # Any failure causes exit 6 | set -eE -o functrace 7 | 8 | report_failure() { 9 | local lineno=$2 10 | local fn=$3 11 | local exitstatus=$4 12 | local msg=$5 13 | local lineno_fns=${1% 0} 14 | if [[ $lineno_fns != "0" ]] ; then 15 | lineno="${lineno} ${lineno_fns}" 16 | fi 17 | if [[ $exitstatus == 0 ]] ; then 18 | echo "${BASH_SOURCE[1]}: Finished!" 19 | else 20 | echo "${BASH_SOURCE[1]}:${fn}[${lineno}] Failed with status ${exitstatus}: $msg" 21 | fi 22 | } 23 | 24 | trap 'report_failure "${BASH_LINENO[*]}" "$LINENO" "${FUNCNAME[*]:-script}" "$?" "$BASH_COMMAND"' ERR 25 | 26 | 27 | case "$1" in 28 | [0-9]*) 29 | export BUNDLE_GEMFILE=$PWD/test/rails_$1.gemfile 30 | ;; 31 | esac 32 | while : 33 | do 34 | case "$BUNDLE_GEMFILE" in 35 | */test/rails_[Qq].gemfile) 36 | echo "Aborting..." 37 | exit 2 38 | ;; 39 | */test/rails_edge.gemfile|*/test/rails_[0-9].[0-9]*.gemfile) 40 | if [ -f "$BUNDLE_GEMFILE" ]; then 41 | break 42 | fi 43 | ;; 44 | esac 45 | echo "== SELECT GEMFILE ==" 46 | echo 47 | echo "Please select the gemfile for the required rails series:" 48 | (cd test ; ls rails*gemfile | ruby -p -e '$_.sub!(/rails_(.*).gemfile/, " \\1")' ) 49 | echo 50 | echo -n "Enter choice (or q to quit): " 51 | read x 52 | export BUNDLE_GEMFILE=$PWD/test/rails_$x.gemfile 53 | done 54 | 55 | if [ -z "$MIDDLEWARE" ]; then 56 | echo -n "Add as middleware [N/y] : " 57 | read MIDDLEWARE 58 | export MIDDLEWARE 59 | fi 60 | rm -rf tmp/Gemfile* tmp/railsapp tmp/bin tmp/gems test/bin test/rails*.gemfile.lock 61 | 62 | mkdir -p tmp/gems 63 | 64 | . test/init_variables 65 | 66 | if $rbenv_which bundle ; then 67 | echo Bundler is installed 68 | else 69 | gem install bundler ${BUNDLER_VERSION:+-v ${BUNDLER_VERSION}} 70 | $rehash 71 | fi 72 | 73 | echo "Running bundle with BUNDLE_GEMFILE=$BUNDLE_GEMFILE ..." 74 | if ! smarter_bundle ; then 75 | echo "Test aborted (missing required gems)" 76 | exit 2 77 | elif [ ! -s $BUNDLE_GEMFILE.lock ] ; then 78 | echo "Error: smarter_bundler return OK status BUT lock file ($BUNDLE_GEMFILE.lock) is missing!" 79 | exit 3 80 | else 81 | echo bundle passed - lock file contains: 82 | cat $BUNDLE_GEMFILE.lock 83 | echo 84 | fi 85 | $rehash 86 | 87 | rails="$base_dir/test/bin/rails" 88 | rake="$base_dir/test/bin/rake" 89 | 90 | echo Checking $rails is present ... 91 | [ -f $rails -a -f $rake ] || bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} exec rake rails:update:bin || echo '(ignored rake rails:update:bin exit status)' 92 | [ -f $rails ] || bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} binstubs railties || echo '(ignored bundle exit status)' 93 | [ -f $rails ] || bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} binstubs rails || echo '(ignored bundle exit status)' 94 | if [ ! -f $rails ]; then 95 | echo "Test aborted (unable to create $rails)" 96 | exit 2 97 | fi 98 | 99 | if [ ! -f $rake ]; then 100 | echo "Running bundle binstubs rake ..." 101 | if ! bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} binstubs rake || [ ! -f $rake ]; then 102 | echo "Test aborted (unable to create $rake)" 103 | exit 2 104 | fi 105 | fi 106 | 107 | actual_rails_version=`$rails -v` 108 | 109 | [ -d lib/health_check ] || exec echo setup_railsapp MUST be executed in the base of the health_check gem/clone of git repository 110 | 111 | export GEM_PATH="$tmp_dir/gems:`gem environment gempath`" 112 | echo Set GEM_PATH=$GEM_PATH 113 | 114 | echo Installing health_check as a gem into $tmp_dir/gems 115 | rm -f pkg/health_check-*.gem 116 | if env GEM_HOME=$tmp_dir/gems $rake install 117 | then 118 | echo rake install passed 119 | else 120 | echo rake install failed! running gem install pkg/health_check-*.gem manually to see error message: 121 | env GEM_HOME=$tmp_dir/gems gem install pkg/health_check-*.gem 122 | echo gem install worked, but flagging it as a FAIL anyway since rake install failed! 123 | exit 2 124 | fi 125 | 126 | echo Gems in tmp/gems: 127 | ls tmp/gems 128 | 129 | echo Environment: 130 | env | egrep 'TRAVIS|RAILS|RUBY|_ENV|GEM|BUNDLE' || echo "No relevant variables set" 131 | 132 | cd $tmp_dir 133 | 134 | case `ruby -e 'puts JRUBY_VERSION' 2> /dev/null` in 135 | [0-9]*) 136 | db=jdbcsqlite3 137 | # Appears to need a bit extra time 138 | ;; 139 | *) 140 | db=sqlite3 141 | ;; 142 | esac 143 | 144 | echo "Creating $actual_rails_version app in $tmp_dir/railsapp using adapter $db" 145 | case "$actual_rails_version" in 146 | *' '[345].*) 147 | args="--skip-bundle -d $db" 148 | case "$BUNDLE_GEMFILE" in 149 | *rails_edge.gemfile) 150 | $rails new railsapp $args --edge 151 | ;; 152 | *) 153 | $rails new railsapp $args 154 | ;; 155 | esac 156 | ;; 157 | *' '[6].*) 158 | args="--skip-bundle -d $db --skip-git --skip-bootsnap" 159 | case "$BUNDLE_GEMFILE" in 160 | *rails_edge.gemfile) 161 | $rails new railsapp $args --edge 162 | ;; 163 | *) 164 | $rails new railsapp $args 165 | ;; 166 | esac 167 | ;; 168 | *) 169 | echo "Unknown rails version" 170 | ;; 171 | esac 172 | 173 | cd $railsapp 174 | 175 | [ -z "$rehash" ] || rbenv local `rbenv version-name` 176 | 177 | echo "Changed current directory to railsapp root: $railsapp" 178 | 179 | echo "Fixing rdoc require in Rakefile if needed" 180 | ruby -p -i.bak -e '$_.gsub!(/rake.rdoctask/, "rdoc/task")' Rakefile 181 | 182 | echo "Configuring mailer to point to fake_smtp_server port 3555" 183 | cat >> config/environment.rb <<'!EOF!' 184 | 185 | ActionMailer::Base.delivery_method = :smtp 186 | ActionMailer::Base.smtp_settings = { address: "localhost", port: 3555 } 187 | 188 | !EOF! 189 | 190 | echo Adding an initializer for health_check gem ... 191 | mkdir -p config/initializers 192 | tee config/initializers/health_check.rb <> Gemfile 243 | 244 | case "$RAILS_SERVER" in 245 | webrick|'') 246 | echo "Using default webrick server" 247 | ;; 248 | *) 249 | echo "Adding $RAILS_SERVER gem to Gemfile (for use as server)" 250 | echo "gem '$RAILS_SERVER'" >> Gemfile 251 | ;; 252 | esac 253 | TAB=$'\t' 254 | QUOTES='"'"'" 255 | case "$actual_rails_version" in 256 | *' '5.0*) 257 | if egrep -i 'gem.*sqlite3' Gemfile ; then 258 | # Can't do this as a require as we may go back to testing JRuby 259 | echo Force sqlite to 1.3.13+ version for Rails 5.0 ... 260 | gem=sqlite3 261 | sed -i.bak -e "s/^\([ ${TAB}]*gem[ ${TAB}]*[${QUOTES}]${gem}[${QUOTES}]\)\(.*\)$/\1, '~> 1.3.13' # overriden: \2/" Gemfile 262 | fi 263 | ;; 264 | esac 265 | if egrep -q REQUIRED ${INITIAL_BUNDLE_GEMFILE} ; then 266 | sed -n "s/^[ ${TAB}]*gem[ ${TAB}]*[${QUOTES}]\([^${QUOTES}]*\)[${QUOTES}].*REQUIRED.*/\1/p" ${INITIAL_BUNDLE_GEMFILE} | while read gem 267 | do 268 | echo "Commenting out gem '$gem' line in Gemfile" 269 | sed -i.bak -e "s/^\([ ${TAB}]*gem[ ${TAB}]*[${QUOTES}]${gem}[${QUOTES}].*\)$/# overriden by REQUIRED below: \1/" Gemfile 270 | done 271 | echo Adding Required gems 272 | egrep REQUIRED ${INITIAL_BUNDLE_GEMFILE} | tee -a Gemfile 273 | else 274 | echo No required gems to be added to Gemfile 275 | fi 276 | 277 | echo 278 | echo ================= $PWD/Gemfile =================== 279 | cat Gemfile 280 | echo 281 | echo ================================================== 282 | echo running smarter_bundle install 283 | smarter_bundle install 284 | $rehash 285 | 286 | if egrep webpacker Gemfile && [ ! -f config/webpacker.yml ] ; then 287 | echo completing setup by running rails webpacker:install ... 288 | $rails webpacker:install 289 | fi 290 | 291 | echo "Using binstubs in $railsapp/bin for rails and rake commands" 292 | rails="$railsapp/bin/rails" 293 | rake="$railsapp/bin/rake" 294 | 295 | echo Checking $rails is present ... 296 | [ -f $rails -a -f $rake ] || bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} exec rake rails:update:bin || echo '(ignored rake rails:update:bin exit status)' 297 | [ -f $rails ] || bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} binstubs railties || echo '(ignored bundle exit status)' 298 | [ -f $rails ] || bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} binstubs rails || echo '(ignored bundle exit status)' 299 | if [ ! -f $rails ]; then 300 | echo "Test aborted (unable to create $rails)" 301 | exit 2 302 | fi 303 | 304 | echo Checking $rake is present ... 305 | [ -f $rake ] || bundle ${BUNDLER_VERSION:+_${BUNDLER_VERSION}_} binstubs rake || echo '(ignored bundle exit status)' 306 | if [ ! -f $rake ]; then 307 | echo "Test aborted (unable to create $rake)" 308 | exit 2 309 | fi 310 | 311 | $rehash 312 | # Fix for rvm, otherwise bundle run from rails create fails 313 | export PATH="`pwd`/bin:$PATH" 314 | echo ================= $PWD/Gemfile.lock =================== 315 | cat Gemfile.lock 316 | echo ================================================== 317 | echo 318 | 319 | for e in test ${RAILS_ENV2:-production} 320 | do 321 | if [ -f config/environments/$e.rb ]; then 322 | echo ======== config/environments/$e.rb ================ 323 | sed -i.bak -e 's/config.serve_static_assets = false/config.serve_static_assets = true # NOTE: health_check test: changed to true/' \ 324 | -e 's/config.active_record.migration_error = :page_load/# & # NOTE: health_check test: commented out/' \ 325 | config/environments/$e.rb 326 | cat config/environments/$e.rb || true 327 | echo 328 | fi 329 | done 330 | 331 | if egrep -q 'bootsnap.setup' config/boot.rb ; then 332 | echo Commenting out bootsnap from config/boot.rb 333 | sed -i.bak -e 's/^\(.*bootsnap.setup\)/# \1/' config/boot.rb 334 | echo "============== $PWD/config/boot.rb =============" 335 | cat config/boot.rb 336 | echo ================================================== 337 | fi 338 | rm -rf tmp/cache/bootsnap* 339 | echo 340 | 341 | echo "============== $PWD/config/environment.rb =============" 342 | cat config/environment.rb 343 | echo 344 | 345 | echo ========================================================= 346 | case $db in 347 | jdbcsqlite3) 348 | for e in test ${RAILS_ENV2:-production} 349 | do 350 | echo 351 | export RAILS_ENV=$e RACK_ENV=$e 352 | echo "Jruby requires the database to be created before the server is started: running RAILS_ENV=$e rake db:migrate" 353 | $rake db:migrate 354 | echo 355 | done 356 | ;; 357 | esac 358 | 359 | echo STATIC-FILE > public/static.txt 360 | 361 | cat > public/ajax_example.html <<'EOF' 362 | 363 | 364 | Example static and dynamic calls to health_check 365 | 366 | 390 | 391 | 392 |

Static calls

393 | 403 |

Dynamic calls

404 |
    405 |
  • Minimal health check should always work 406 | (json, 407 | xml, 408 | html) 409 | 410 |
  • Force health check to fail! 411 | (json, 412 | xml, 413 | html) 414 |
  • Last results sent to success:
      415 |
    • Data: 416 |
    • result.status: 417 |
    • result.responseText: 418 |
    419 |
  • Last results sent to error:
      420 |
    • result.status: 421 |
    • result.responseText: 422 |
    423 |
  • Last results sent to complete:
      424 |
    • result.status: 425 |
    • result.responseText: 426 |
    427 |
428 | 429 | 430 | EOF 431 | 432 | cat > app/controllers/example_controller.rb <<'EOF' 433 | class ExampleController < ApplicationController 434 | 435 | def index 436 | render plain: 'example page' 437 | end 438 | 439 | def catchall 440 | render plain: 'catch all route' 441 | end 442 | 443 | end 444 | 445 | EOF 446 | 447 | echo ======================================= 448 | 449 | case "$MIDDLEWARE" in 450 | [Yy]*) 451 | if [ -s config/application.rb ]; then 452 | mv -f config/application.rb config/application.rb-old 453 | ( 454 | ruby -n -e 'print if not /^ *end/..9999' config/application.rb-old | tee /tmp/t$$ 455 | if [ ! -s /tmp/t$$ ]; then 456 | echo "WARNING: ruby -n -e failed silently - using sed instead!! (rbx-19mode has that problem)" >&3 457 | sed -e '/^ *end/,$d' config/application.rb-old 458 | fi 459 | 460 | echo " # -----------------------------------------" 461 | echo " # START OF SECTION FOR TESTING HEALTH_CHECK" 462 | echo " config.middleware.insert_after Rails::Rack::Logger, HealthCheck::MiddlewareHealthcheck" 463 | echo " # END OF SECTION FOR TESTING HEALTH_CHECK" 464 | echo " # ---------------------------------------" 465 | ruby -n -e 'print if /^ *end/..9999' config/application.rb-old | tee /tmp/t$$ 466 | if [ ! -s /tmp/t$$ ]; then 467 | echo "WARNING: ruby -n -e failed silently - using sed instead!! (rbx-19mode has that problem)" >&3 468 | sed -n -e '/^ *end/,$p' config/application.rb-old 469 | fi 470 | ) 3>&1 > config/application.rb 471 | 472 | #echo =============== config/application.rb-old ======================== 473 | #cat config/application.rb-old 474 | echo =============== $PWD/config/application.rb ======================== 475 | cat config/application.rb 476 | else 477 | echo FAILED: NO config/application.rb file!! 478 | exit 2 479 | fi 480 | 481 | echo ======================================= 482 | 483 | ;; 484 | esac 485 | 486 | if [ -s config/routes.rb ]; then 487 | mv -f config/routes.rb config/routes.rb-old 488 | ( 489 | ruby -n -e 'print if not /^end/..9999' config/routes.rb-old | tee /tmp/t$$ 490 | if [ ! -s /tmp/t$$ ]; then 491 | echo "WARNING: ruby -n -e failed silently - using sed instead!! (rbx-19mode has that problem)" >&3 492 | sed -e '/^end/,$d' config/routes.rb-old 493 | fi 494 | 495 | # rails 3.0+ 496 | echo " # -----------------------------------------" 497 | echo " # START OF SECTION FOR TESTING HEALTH_CHECK" 498 | echo " get 'example/catchall(.:format)', controller: :example, action: :catchall" 499 | echo " get 'example(.:format)', controller: :example, action: :index" 500 | echo " if File.exists?('$catchall_file')" 501 | echo " health_check_routes" 502 | echo " # CATCH ALL ROUTE" 503 | echo " get '*path', controller: :example, action: :catchall" 504 | echo " end" 505 | echo " # END OF SECTION FOR TESTING HEALTH_CHECK" 506 | echo " # ---------------------------------------" 507 | ruby -n -e 'print if /^end/..9999' config/routes.rb-old | tee /tmp/t$$ 508 | if [ ! -s /tmp/t$$ ]; then 509 | echo "WARNING: ruby -n -e failed silently - using sed instead!! (rbx-19mode has that problem)" >&3 510 | sed -n -e '/^end/,$p' config/routes.rb-old 511 | fi 512 | ) 3>&1 > config/routes.rb 513 | 514 | #echo =============== config/routes.rb-old ======================== 515 | #cat config/routes.rb-old 516 | echo =============== config/routes.rb ======================== 517 | cat config/routes.rb 518 | else 519 | echo FAILED: NO config/routes.rb file!! 520 | exit 2 521 | fi 522 | echo ======================================= 523 | 524 | echo 525 | echo "Created $actual_rails_version app in $railsapp using adapter $db" 526 | echo -n "Using " 527 | ruby --version 528 | -------------------------------------------------------------------------------- /test/test_with_railsapp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | route_prefix=medical_check 4 | 5 | # Any failure causes exit 6 | set -eE -o functrace 7 | 8 | report_failure() { 9 | local lineno=$2 10 | local fn=$3 11 | local exitstatus=$4 12 | local msg=$5 13 | local lineno_fns=${1% 0} 14 | if [[ $lineno_fns != "0" ]] ; then 15 | lineno="${lineno} ${lineno_fns}" 16 | fi 17 | if [[ $exitstatus == 0 ]] ; then 18 | echo "${BASH_SOURCE[1]}: Finished!" 19 | else 20 | echo "${BASH_SOURCE[1]}:${fn}[${lineno}] Failed with status ${exitstatus}: $msg" 21 | fi 22 | } 23 | 24 | trap 'report_failure "${BASH_LINENO[*]}" "$LINENO" "${FUNCNAME[*]:-script}" "$?" "$BASH_COMMAND"' ERR 25 | 26 | 27 | # Any failure causes exit 28 | set -eE -o functrace 29 | 30 | report_failure() { 31 | local lineno=$2 32 | local fn=$3 33 | local exitstatus=$4 34 | local msg=$5 35 | local lineno_fns=${1% 0} 36 | if [[ $lineno_fns != "0" ]] ; then 37 | lineno="${lineno} ${lineno_fns}" 38 | fi 39 | if [[ $exitstatus == 0 ]] ; then 40 | echo "${BASH_SOURCE[1]}: Finished!" 41 | else 42 | echo "${BASH_SOURCE[1]}:${fn}[${lineno}] Failed with status ${exitstatus}: $msg" 43 | fi 44 | } 45 | 46 | trap 'report_failure "${BASH_LINENO[*]}" "$LINENO" "${FUNCNAME[*]:-script}" "$?" "$BASH_COMMAND"' ERR 47 | 48 | export DISABLE_SPRING=1 49 | 50 | cleanup_db() 51 | { 52 | echo Dropping database ... 53 | $rake db:drop 54 | echo Removing migrations ... 55 | rm -f db/migrate/* db/schema.rb 56 | case `ruby -e 'puts JRUBY_VERSION' 2> /dev/null` in 57 | [0-9]*) 58 | echo 'Jruby requires the database to be created before the server is started: running rake db:migrate' 59 | $rake db:migrate 60 | ;; 61 | esac 62 | } 63 | 64 | case "$1" in 65 | redo) 66 | . test/init_variables 67 | cd $railsapp 68 | cleanup_db 69 | actual_rails_version=`$rails -v` 70 | ;; 71 | *) 72 | . test/setup_railsapp $1 73 | ;; 74 | esac 75 | 76 | run_test=$2 77 | 78 | cd $railsapp 79 | date > $custom_file 80 | rm -f $catchall_file 81 | 82 | case `egrep '^[^#]*MiddlewareHealthcheck' config/application.rb` in 83 | '') 84 | export has_middleware=false 85 | ;; 86 | ?*) 87 | export has_middleware=true 88 | ;; 89 | esac 90 | 91 | 92 | testurl="$base_dir/test/testurl" 93 | fake_smtp_server="$base_dir/test/fake_smtp_server" 94 | 95 | server_pid='' 96 | fake_smtp_pid='' 97 | 98 | pick_a_port() 99 | { 100 | while : 101 | do 102 | port=`expr 10000 + $RANDOM` 103 | # Check Tcp ports in Listen mode with No address resolution 104 | if (netstat -tln | egrep ":${port} .*:"); then 105 | echo "(Skipping used port)" 106 | else 107 | break 108 | fi 109 | done 110 | } 111 | 112 | 113 | start_server() 114 | { 115 | # restart migration list 116 | rm -rf db/migrate db/schema.rb 117 | mkdir -p db/migrate 118 | 119 | # Increment port each time to make sure we have not trouble with address/port already allocated 120 | pick_a_port 121 | host=http://127.0.0.1:${port} 122 | bundle_prefix='' 123 | if [ -f Gemfile ] 124 | then 125 | bundle_prefix='bundle exec' 126 | fi 127 | server_arg=${RAILS_SERVER:-webrick} 128 | case "$actual_rails_version" in 129 | *' '[12345].*) 130 | ;; 131 | *) 132 | server_arg="-u $server_arg" 133 | ;; 134 | esac 135 | echo "start_server called using: `env | egrep '^RAILS|^RACK|^PATH='` $bundle_prefix $server_arg" 136 | case "$server_arg" in 137 | *puma) 138 | $bundle_prefix puma -b tcp://127.0.0.1:$port & 139 | ;; 140 | *passenger) 141 | $bundle_prefix passenger start -p $port & 142 | ;; 143 | *thin) 144 | $bundle_prefix thin start -p $port & 145 | ;; 146 | *unicorn) 147 | $bundle_prefix unicorn_rails -l 127.0.0.1:$port & 148 | ;; 149 | *) 150 | if [ -x script/server ] 151 | then 152 | echo Starting server on port $port using $bundle_prefix ./script/server ... 153 | $bundle_prefix ./script/server $server_arg -p $port & 154 | else 155 | echo Starting server on port $port using $rails s ... 156 | $bundle_prefix $rails server $server_arg -p $port & 157 | fi 158 | ;; 159 | esac 160 | server_pid=$! 161 | echo Server pid: $server_pid 162 | sleep 3 163 | echo 164 | echo 'Checking server is up ...' 165 | for i in 1 2 3 4 5 6 166 | do 167 | if $testurl ${host}/static.txt ; then 168 | break 169 | fi 170 | if kill -0 $server_pid ; then 171 | echo "waiting ${i} ..." 172 | else 173 | echo "ERROR: Server has died!!" 174 | exit 3 175 | fi 176 | done 177 | } 178 | 179 | stop_server() 180 | { 181 | case "$server_pid" in 182 | [0-9]*) 183 | echo ======================================================== 184 | echo "Killing rails server [pid: $server_pid]" 185 | kill -QUIT $server_pid || echo server has already exited .. 186 | if [ -x bin/spring ] ; then 187 | echo Stopping spring ... 188 | bin/spring stop || echo spring had already exited .. 189 | fi 190 | sleep 1 191 | kill -9 $server_pid || echo server had already exited ... 192 | sleep 1 193 | # needed for unicorn - it doesn't die when it is supposed to 194 | killall "$server_arg" || echo server and child processes had already stopped ... 195 | ;; 196 | esac 197 | case "$fake_smtp_pid" in 198 | [0-9]*) 199 | echo ======================================================== 200 | echo "Killing fake smtp server [pid: $fake_smtp_pid]" 201 | kill -QUIT $fake_smtp_pid || echo fake_smtp had already exited .. 202 | sleep 2 203 | kill -9 $fake_smtp_pid || echo fake_smtp had already exited .. 204 | ;; 205 | esac 206 | server_pid='' 207 | fake_smtp_pid='' 208 | ps -f 209 | echo Waiting for sub processes to complete ... 210 | wait 211 | echo Finished waiting for sub processes, sleeping 2 seconds ... 212 | sleep 2 213 | } 214 | 215 | finish() 216 | { 217 | set +e 218 | echo ======================================================== 219 | echo TEST ${1:-FAILED} 220 | echo ======================================================== 221 | echo Result of: ls -lR $railsapp/log $railsapp/db 222 | ls -lR $railsapp/log $railsapp/db 223 | 224 | if [ -s $railsapp/log/test.log ] 225 | then 226 | echo ======================================================== 227 | echo Last 50 lines of test log 228 | tail -50 $railsapp/log/test.log 229 | fi 230 | 231 | if [ -s $railsapp/log/production.log ] 232 | then 233 | echo ======================================================== 234 | echo Last 50 lines of production log 235 | tail -50 $railsapp/log/production.log 236 | fi 237 | 238 | stop_server 239 | trap "" 0 240 | echo ======================================================== 241 | ps uxf || echo ps failed 242 | echo ======================================================== 243 | echo TEST ${1:-FAILED}, exiting with status ${2:-2} 244 | echo ======================================================== 245 | exit ${2:-2} 246 | } 247 | 248 | trap "finish FAILED 1" 0 249 | 250 | common_tests() 251 | { 252 | 253 | test_no=$1 254 | 255 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 256 | echo "${test_no}[line $LINENO]: CHECKING routes exist..." 257 | $rake routes | tee /tmp/t$$ 258 | echo 259 | case `egrep ${route_prefix} /tmp/t$$ || true` in 260 | '') 261 | echo WARNING - routes for ${route_prefix} not listed! 262 | ;; 263 | esac 264 | echo 265 | fi 266 | 267 | test_no=`expr 1 + $test_no` 268 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 269 | echo "${test_no}[line $LINENO]: TESTING can get a static file ..." 270 | case "$RAILS_ENV=`egrep '^\s*config.serve_static_[asetfil]* *= *false' config/environments/${RAILS_ENV}.rb`" in 271 | production*static*false*) 272 | echo " SKIPPED (disabled in production)" 273 | ;; 274 | *) 275 | grep serve_static_files config/environments/${RAILS_ENV}.rb config/[a-z]*.rb || echo no serve_static_files entry 276 | $testurl ${host}/static.txt 200 text/plain STATIC-FILE 277 | ;; 278 | esac 279 | echo 280 | fi 281 | 282 | rm -f tmp/health_check_success.txt tmp/health_check_failure.txt 283 | 284 | test_no=`expr 1 + $test_no` 285 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 286 | echo "${test_no}[line $LINENO]: TESTING can get an example controller ..." 287 | $testurl ${host}/example 200 text/plain 'example page' 288 | echo 289 | fi 290 | 291 | test_no=`expr 1 + $test_no` 292 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 293 | echo "${test_no}[line $LINENO]: TESTING direct call to catchall method on example controller ..." 294 | $testurl ${host}/example/catchall 200 text/plain 'catch all route' 295 | echo 296 | fi 297 | 298 | if [ -f tmp/health_check_success.txt ] ; then 299 | echo "FAIL tmp/health_check_success.txt exists on line $LINENO" 300 | else 301 | echo "PASS tmp/health_check_success.txt is missing as expected on line $LINENO" 302 | fi 303 | if [ -f tmp/health_check_failure.txt ] ; then 304 | echo "FAIL tmp/health_check_failure.txt exists on line $LINENO" 305 | else 306 | echo "PASS tmp/health_check_failure.txt is missing as expected on line $LINENO" 307 | fi 308 | 309 | test_no=`expr 1 + $test_no` 310 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 311 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/migration should pass with no database migrations ..." 312 | ls db/migrate 313 | rm -f tmp/health_check_success.txt tmp/health_check_failure.txt 314 | $testurl ${host}/${route_prefix}/migration 200 text/plain $success 315 | if [ -f tmp/health_check_success.txt ] ; then 316 | echo "PASS tmp/health_check_success.txt exists as expected on line $LINENO" 317 | cat tmp/health_check_success.txt 318 | else 319 | echo "FAIL tmp/health_check_success.txt is missing on line $LINENO" 320 | fi 321 | if [ -f tmp/health_check_failure.txt ] ; then 322 | echo "FAIL tmp/health_check_failure.txt exists on line $LINENO" 323 | else 324 | echo "PASS tmp/health_check_failure.txt is missing as expected on line $LINENO" 325 | fi 326 | echo 327 | fi 328 | 329 | test_no=`expr 1 + $test_no` 330 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 331 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/migration should fail without initial database migration ..." 332 | cp $base_dir/test/migrate/nine/* db/migrate 333 | ls db/migrate 334 | rm -f tmp/health_check_success.txt tmp/health_check_failure.txt 335 | $testurl ${host}/${route_prefix}/migration 550 text/plain failed 336 | if [ -f tmp/health_check_success.txt ] ; then 337 | echo "FAIL tmp/health_check_success.txt exists on line $LINENO" 338 | else 339 | echo "PASS tmp/health_check_success.txt is missing as expected on line $LINENO" 340 | fi 341 | if [ -f tmp/health_check_failure.txt ] ; then 342 | echo "PASS tmp/health_check_failure.txt exists as expected on line $LINENO" 343 | cat tmp/health_check_failure.txt 344 | else 345 | echo "FAIL tmp/health_check_failure.txt is missing on line $LINENO" 346 | fi 347 | echo 348 | fi 349 | 350 | test_no=`expr 1 + $test_no` 351 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 352 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/database should pass without initial database migration (since it ignores the difference) ..." 353 | $testurl ${host}/${route_prefix}/database 200 text/plain $success 354 | echo 355 | fi 356 | 357 | 358 | 359 | test_no=`expr 1 + $test_no` 360 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 361 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/site should pass ..." 362 | $testurl ${host}/${route_prefix}/site 200 text/plain $success 363 | echo 364 | fi 365 | 366 | test_no=`expr 1 + $test_no` 367 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 368 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/migration should pass after initial database migration ..." 369 | $rake db:migrate 370 | $testurl ${host}/${route_prefix}/migration 200 text/plain $success 371 | echo 372 | fi 373 | 374 | #test with coruppted DB 375 | rm -rf db.bak 376 | cp -R db db.bak 377 | for f in db/*.sqlite3 378 | do 379 | echo CORRUPTED > $f 380 | done 381 | 382 | test_no=`expr 1 + $test_no` 383 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 384 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/database should fail if the database has been corrupted ..." 385 | 386 | $testurl ${host}/${route_prefix}/database 550 text/plain failed: 387 | echo 388 | fi 389 | 390 | export HIDE_ERROR_RESPONSE=true 391 | stop_server 392 | start_server 393 | 394 | test_no=`expr 1 + $test_no` 395 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 396 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/database should have response body 'health_check failed' but no error details if include_error_in_response_body is false" 397 | $testurl ${host}/${route_prefix}/database 550 text/plain 'health_check failed' failed: 398 | echo 399 | fi 400 | 401 | unset HIDE_ERROR_RESPONSE 402 | stop_server 403 | start_server 404 | 405 | test_no=`expr 1 + $test_no` 406 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 407 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/site should pass ..." 408 | $testurl ${host}/${route_prefix}/site 200 text/plain $success 409 | if $has_middleware; then 410 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/middleware_site should pass ..." 411 | $testurl ${host}/${route_prefix}/middleware_site 200 text/plain $success 412 | fi 413 | echo 414 | fi 415 | 416 | # Restore database 417 | cp -f db.bak/*.sqlite3 db/ 418 | 419 | test_no=`expr 1 + $test_no` 420 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 421 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/migration should fail without all migrations ..." 422 | cp $base_dir/test/migrate/twelve/* db/migrate 423 | 424 | ls db/migrate 425 | $testurl ${host}/${route_prefix}/migration 550 text/plain failed 426 | echo 427 | fi 428 | 429 | test_no=`expr 1 + $test_no` 430 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 431 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/migration should pass after both database migrations ..." 432 | $rake db:migrate 433 | $testurl ${host}/${route_prefix}/migration 200 text/plain $success 434 | echo 435 | fi 436 | 437 | test_no=`expr 1 + $test_no` 438 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 439 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/migration should pass after both database migrations ..." 440 | $rake db:migrate 441 | $testurl ${host}/${route_prefix}/migration 200 text/plain $success 442 | echo 443 | fi 444 | 445 | test_no=`expr 1 + $test_no` 446 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 447 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/email should fail without smtp available ..." 448 | $testurl ${host}/${route_prefix}/email 550 text/plain failed 449 | echo 450 | fi 451 | 452 | test_no=`expr 1 + $test_no` 453 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 454 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/email should pass with smtp available ..." 455 | $fake_smtp_server & 456 | fake_smtp_pid=$! 457 | sleep 5 458 | $testurl ${host}/${route_prefix}/email 200 text/plain $success 459 | echo 460 | fi 461 | 462 | test_no=`expr 1 + $test_no` 463 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 464 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix} (all) should fail without smtp available ..." 465 | $testurl ${host}/${route_prefix} 550 text/plain failed 466 | echo 467 | fi 468 | 469 | test_no=`expr 1 + $test_no` 470 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 471 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/all should fail without smtp available ..." 472 | $testurl ${host}/${route_prefix} 550 text/plain failed 473 | echo 474 | fi 475 | 476 | kill -9 $fake_smtp_pid || echo fake_smtp_server had finished as expected 477 | test_no=`expr 1 + $test_no` 478 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 479 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix} (all) should pass with smtp available ..." 480 | $fake_smtp_server & 481 | fake_smtp_pid=$! 482 | sleep 5 483 | $testurl ${host}/${route_prefix} 200 text/plain $success 484 | echo 485 | fi 486 | 487 | kill -9 $fake_smtp_pid || echo fake_smtp_server had finished as expected 488 | test_no=`expr 1 + $test_no` 489 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 490 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/all should pass with smtp available ..." 491 | $fake_smtp_server & 492 | fake_smtp_pid=$! 493 | sleep 5 494 | $testurl ${host}/${route_prefix}/all 200 text/plain $success 495 | echo 496 | fi 497 | 498 | test_no=`expr 1 + $test_no` 499 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 500 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/pass should pass ..." 501 | $testurl ${host}/${route_prefix}/pass 200 text/plain $success 502 | echo 503 | fi 504 | 505 | test_no=`expr 1 + $test_no` 506 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 507 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/custom should pass ..." 508 | $testurl ${host}/${route_prefix}/custom 200 text/plain $success 509 | echo 510 | fi 511 | 512 | test_no=`expr 1 + $test_no` 513 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 514 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/custom.html should pass (returning plain text) ..." 515 | $testurl ${host}/${route_prefix}/custom.html 200 text/plain $success 516 | echo 517 | fi 518 | 519 | test_no=`expr 1 + $test_no` 520 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 521 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/custom.json should pass ..." 522 | $testurl ${host}/${route_prefix}/custom.json 200 application/json '"healthy":true' 523 | $testurl ${host}/${route_prefix}/custom.json 200 application/json "\"message\":\"$success\"" 524 | echo 525 | fi 526 | 527 | test_no=`expr 1 + $test_no` 528 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 529 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/custom.xml should pass ..." 530 | $testurl ${host}/${route_prefix}/custom.xml 200 application/xml 'true' 531 | $testurl ${host}/${route_prefix}/custom.xml 200 application/xml "$success" 532 | echo 533 | fi 534 | 535 | test_no=`expr 1 + $test_no` 536 | rm -f $custom_file 537 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 538 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/custom should fail when custom returns string ..." 539 | $testurl ${host}/${route_prefix}/custom 550 text/plain failed 540 | echo 541 | fi 542 | 543 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 544 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/pass should pass even if other custom test returns string ..." 545 | $testurl ${host}/${route_prefix}/pass 200 text/plain $success 546 | echo 547 | fi 548 | 549 | test_no=`expr 1 + $test_no` 550 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 551 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix} (all) should fail when custom check fails ..." 552 | $testurl ${host}/${route_prefix} 550 text/plain "$custom_file is missing!" 553 | echo 554 | fi 555 | 556 | test_no=`expr 1 + $test_no` 557 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 558 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}.json (all) should fail when custom check fails ..." 559 | $testurl ${host}/${route_prefix}.json 555 application/json '"healthy":false' 560 | $testurl ${host}/${route_prefix}.json 555 application/json "$custom_file is missing!" 561 | echo 562 | fi 563 | 564 | test_no=`expr 1 + $test_no` 565 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 566 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}.xml (all) should fail when custom check fails ..." 567 | $testurl ${host}/${route_prefix}.xml 555 application/xml 'false' 568 | echo 569 | fi 570 | 571 | test_no=`expr 1 + $test_no` 572 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 573 | if $has_middleware; then 574 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/middleware_site should pass ..." 575 | $testurl ${host}/${route_prefix}/middleware_site 200 text/plain $success 576 | else 577 | echo "${test_no}[line $LINENO]: TESTING ${route_prefix}/middleware_site should fail ..." 578 | $testurl ${host}/${route_prefix}/middleware_site 550 text/plain failed 579 | fi 580 | echo 581 | fi 582 | 583 | test_no=`expr 1 + $test_no` 584 | if [ -z "$run_test" ] || [ $test_no == "$run_test" ]; then 585 | echo "${test_no}[line $LINENO]: TESTING log files to check for deprecation warnings ..." 586 | if egrep ' is deprecated|DEPRECATION WARNING' $railsapp/log/[tp][er][so][td]*.log 587 | then 588 | echo Found deprecation warnings - failed test 589 | exit 99 590 | fi 591 | fi 592 | 593 | date > $custom_file 594 | } 595 | 596 | # required for rails 4.1+ in production mode 597 | export SECRET_KEY_BASE=cf2f49c38a3fe67416ddf680f4f3187c0fce7dd1b9b117b34d195df75b274e08a04877e23803b2fdf1aa9a655269d94bc4888aa325cf7e721cc47368cfe56a80 598 | 599 | # required for rails 5 to server static files 600 | export RAILS_SERVE_STATIC_FILES=on 601 | 602 | export IP_WHITELIST='123.123.123.123' 603 | unset AUTH_USER 604 | unset AUTH_PASSWORD 605 | 606 | case "$run_test" in 607 | ''|[12]) 608 | echo ======================================================== 609 | echo TESTING whitelist ban WITHOUT CATCHALL in test env 610 | echo ======================================================== 611 | export RAILS_ENV=test RACK_ENV=test 612 | 613 | start_server 614 | 615 | echo '1: TESTING controller prohibited by ip...' 616 | expected_status=403 617 | $testurl ${host}/${route_prefix}/site $expected_status 618 | 619 | if $has_middleware; then 620 | echo 621 | echo '2: TESTING middleware prohibited by ip...' 622 | expected_status=403 623 | $testurl ${host}/${route_prefix}/middleware $expected_status 624 | fi 625 | ;; 626 | esac 627 | 628 | export IP_WHITELIST='' 629 | export AUTH_USER='someone' 630 | export AUTH_PASSWORD='secret' 631 | 632 | case "$run_test" in 633 | ''|[3456789]|10) 634 | echo ======================================================== 635 | echo TESTING basic auth, no whitelist WITHOUT CATCHALL in test env 636 | echo ======================================================== 637 | export RAILS_ENV=test RACK_ENV=test 638 | 639 | case "$run_trest" in 640 | '') 641 | stop_server 642 | cleanup_db 643 | ;; 644 | esac 645 | 646 | start_server 647 | 648 | expected_status=401 649 | echo '3: TESTING controller without authentication insists on authentication ...' 650 | AUTH_USER= $testurl ${host}/${route_prefix}/site $expected_status 651 | 652 | echo '4: TESTING controller with wrong password insists on authentication ...' 653 | AUTH_PASSWORD=wrong $testurl ${host}/${route_prefix}/site $expected_status 654 | 655 | echo '5: TESTING controller with wrong user insists on authentication ...' 656 | AUTH_USER=wrong $testurl ${host}/${route_prefix}/site $expected_status 657 | 658 | echo '6: TESTING controller with authentication works ...' 659 | expected_status=200 660 | $testurl ${host}/${route_prefix}/site $expected_status 661 | 662 | if $has_middleware; then 663 | echo 664 | echo '7: TESTING middleware without authentication insists on authentication ...' 665 | expected_status=401 666 | AUTH_USER= $testurl ${host}/${route_prefix}/middleware $expected_status 667 | 668 | echo 669 | echo '8: TESTING middleware with wrong password insists on authentication ...' 670 | AUTH_PASSWORD=wrong $testurl ${host}/${route_prefix}/middleware $expected_status 671 | 672 | echo 673 | echo '9: TESTING middleware with wrong user insists on authentication ...' 674 | AUTH_USER=wrong $testurl ${host}/${route_prefix}/middleware $expected_status 675 | 676 | echo 677 | echo '10: TESTING middleware with authentication works ...' 678 | expected_status=200 679 | $testurl ${host}/${route_prefix}/middleware $expected_status 680 | else 681 | echo 682 | echo "Skipped middleware tests as it is not configured..." 683 | fi 684 | ;; 685 | esac 686 | 687 | unset AUTH_USER 688 | unset AUTH_PASSWORD 689 | 690 | case "$run_test" in 691 | ''|1??) 692 | echo ======================================================== 693 | echo TESTING WITHOUT CATCHALL, no whitelist or user in test env 694 | echo ======================================================== 695 | export RAILS_ENV=test RACK_ENV=test 696 | 697 | case "$run_trest" in 698 | '') 699 | stop_server 700 | cleanup_db 701 | ;; 702 | esac 703 | 704 | start_server 705 | 706 | # get a static file 707 | 708 | echo 709 | echo 'TESTING no catchall route active ...' 710 | expected_status=404,500,502 711 | $testurl ${host}/another/url $expected_status 712 | 713 | echo 'TESTING default route has been overriden ...' 714 | expected_status=404,500,502 715 | $testurl ${host}/health_check/site $expected_status 716 | 717 | common_tests 100 718 | ;; 719 | esac 720 | 721 | export IP_WHITELIST='127.0.0.1' 722 | export AUTH_USER='someone' 723 | export AUTH_PASSWORD='secret' 724 | 725 | case "$run_test" in 726 | ''|2??) 727 | echo ======================================================== 728 | echo TESTING WITH CATCHALL with whitelist and user in ${RAILS_ENV2:-production} env 729 | echo ======================================================== 730 | export RAILS_ENV=${RAILS_ENV2:-production} RACK_ENV=${RAILS_ENV2:-production} 731 | 732 | case "$run_trest" in 733 | '') 734 | stop_server 735 | cleanup_db 736 | ;; 737 | esac 738 | 739 | date > $catchall_file 740 | 741 | start_server 742 | 743 | echo 744 | echo 'TESTING catchall route active ...' 745 | $testurl ${host}/another/url 200 text/plain 'catch all route' 746 | echo 747 | 748 | common_tests 200 749 | ;; 750 | esac 751 | 752 | rm -f $catchall_file 753 | 754 | finish PASSED 0 755 | exit 0 756 | 757 | # vi: sw=4 ai sm: 758 | --------------------------------------------------------------------------------