hcheck 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +98 -0
- data/LICENSE.txt +21 -0
- data/README.md +125 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +9 -0
- data/exe/hcheck +18 -0
- data/hcheck.gemspec +56 -0
- data/hcheck.sample.yml +47 -0
- data/lib/hcheck.rb +54 -0
- data/lib/hcheck/application.rb +42 -0
- data/lib/hcheck/application/helpers/responders.rb +55 -0
- data/lib/hcheck/application/views/error.haml +26 -0
- data/lib/hcheck/application/views/index.haml +38 -0
- data/lib/hcheck/checks/memcached.rb +22 -0
- data/lib/hcheck/checks/mongodb.rb +26 -0
- data/lib/hcheck/checks/mysql.rb +22 -0
- data/lib/hcheck/checks/ping.rb +38 -0
- data/lib/hcheck/checks/postgresql.rb +24 -0
- data/lib/hcheck/checks/rabbitmq.rb +29 -0
- data/lib/hcheck/checks/redis.rb +23 -0
- data/lib/hcheck/configuration.rb +57 -0
- data/lib/hcheck/configuration/service.rb +46 -0
- data/lib/hcheck/errors.rb +32 -0
- data/lib/hcheck/helper.rb +34 -0
- data/lib/hcheck/version.rb +5 -0
- metadata +316 -0
data/lib/hcheck.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'erb'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
require 'hcheck/version'
|
8
|
+
require 'hcheck/helper'
|
9
|
+
require 'hcheck/application'
|
10
|
+
require 'hcheck/configuration'
|
11
|
+
require 'hcheck/errors'
|
12
|
+
|
13
|
+
# Main Hcheck module
|
14
|
+
module Hcheck
|
15
|
+
class << self
|
16
|
+
attr_accessor :configuration, :logging
|
17
|
+
|
18
|
+
LOG_FILE_PATH = 'log/hcheck.log'
|
19
|
+
|
20
|
+
def status
|
21
|
+
if configuration
|
22
|
+
configuration.services.map(&:check)
|
23
|
+
else
|
24
|
+
[{
|
25
|
+
name: 'Hcheck',
|
26
|
+
desc: 'Hcheck',
|
27
|
+
status: 'Hcheck configuration not found'
|
28
|
+
}]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def configure(config = {})
|
33
|
+
self.configuration ||= Configuration.new(config)
|
34
|
+
end
|
35
|
+
|
36
|
+
def logger
|
37
|
+
self.logging ||= set_logger
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def set_logger
|
43
|
+
dir = File.dirname(LOG_FILE_PATH)
|
44
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
45
|
+
logger = Logger.new(LOG_FILE_PATH, 'daily')
|
46
|
+
logger.formatter = proc do |severity, datetime, _progname, msg|
|
47
|
+
log_msg = "[#{severity}] [#{datetime}] #{msg}"
|
48
|
+
puts log_msg
|
49
|
+
"#{log_msg}\n"
|
50
|
+
end
|
51
|
+
logger
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'haml'
|
3
|
+
|
4
|
+
require 'hcheck'
|
5
|
+
require 'hcheck/application/helpers/responders'
|
6
|
+
|
7
|
+
module Hcheck
|
8
|
+
# base sinatra application
|
9
|
+
class SinatraBase < Sinatra::Base
|
10
|
+
set :public_dir, File.expand_path('application/assets', __dir__)
|
11
|
+
set :views, File.expand_path('application/views', __dir__)
|
12
|
+
set :haml, format: :html5
|
13
|
+
|
14
|
+
include Hcheck::ApplicationHelpers::Responders
|
15
|
+
end
|
16
|
+
|
17
|
+
# sinatra that gets booted when run in standalone mode
|
18
|
+
class Application < SinatraBase
|
19
|
+
get('/hcheck') { h_status }
|
20
|
+
end
|
21
|
+
|
22
|
+
# sinatra when mounted to rails
|
23
|
+
class Status < SinatraBase
|
24
|
+
def initialize(app = nil)
|
25
|
+
Hcheck::Configuration.load_default
|
26
|
+
rescue Hcheck::Errors::ConfigurationError => e
|
27
|
+
@config_error = e
|
28
|
+
ensure
|
29
|
+
super(app)
|
30
|
+
end
|
31
|
+
|
32
|
+
get('/') do
|
33
|
+
if @config_error
|
34
|
+
Hcheck.logger.error @config_error.message
|
35
|
+
|
36
|
+
respond_with Hcheck::Errors::ConfigurationError::MSG, 500, :error
|
37
|
+
else
|
38
|
+
h_status
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module ApplicationHelpers
|
3
|
+
# Sinatra app controller helpers
|
4
|
+
module Responders
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def h_status
|
8
|
+
authenticate!(params) if secured_access_enabled?
|
9
|
+
|
10
|
+
@status = Hcheck.status
|
11
|
+
|
12
|
+
if @status.find { |s| s[:status] == 'bad' }
|
13
|
+
status 503
|
14
|
+
else
|
15
|
+
status 200
|
16
|
+
end
|
17
|
+
|
18
|
+
haml :index
|
19
|
+
rescue Hcheck::Errors::InvalidAuthentication, Hcheck::Errors::IncompleteAuthSetup => e
|
20
|
+
status 401
|
21
|
+
@msg = e.message
|
22
|
+
|
23
|
+
haml :error
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_with(message, status_code, view)
|
27
|
+
status status_code
|
28
|
+
@msg = message
|
29
|
+
|
30
|
+
haml view
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def secured_access_enabled?
|
36
|
+
ENV['HCHECK_SECURE']
|
37
|
+
end
|
38
|
+
|
39
|
+
def authenticate!(params)
|
40
|
+
@token = params[:token]
|
41
|
+
access_precheck!
|
42
|
+
|
43
|
+
raise Hcheck::Errors::InvalidAuthentication unless ENV['HCHECK_ACCESS_TOKEN'].eql?(@token)
|
44
|
+
end
|
45
|
+
|
46
|
+
def access_precheck!
|
47
|
+
# throw error when hcheck secure is enabled but token is not set yet
|
48
|
+
raise Hcheck::Errors::IncompleteAuthSetup unless ENV['HCHECK_ACCESS_TOKEN'].present?
|
49
|
+
|
50
|
+
# throw error when token is not sent
|
51
|
+
raise Hcheck::Errors::InvalidAuthentication unless @token.present?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
%table.table
|
2
|
+
%tbody
|
3
|
+
%tr
|
4
|
+
%td
|
5
|
+
.error= @msg
|
6
|
+
:css
|
7
|
+
body {
|
8
|
+
background: #fefefe;
|
9
|
+
font-family: sans-serif;
|
10
|
+
}
|
11
|
+
.table {
|
12
|
+
width: 1024px;
|
13
|
+
margin: 0 auto;
|
14
|
+
}
|
15
|
+
.table td, .table th {
|
16
|
+
padding: 12px;
|
17
|
+
font-weight: 200;
|
18
|
+
text-align: center;
|
19
|
+
}
|
20
|
+
.table th {
|
21
|
+
font-size: 2em;
|
22
|
+
padding: 20px;
|
23
|
+
}
|
24
|
+
.error {
|
25
|
+
color: red
|
26
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
%table.table
|
2
|
+
%thead
|
3
|
+
%tr
|
4
|
+
%th{ colspan: 3 } Hcheck Status Page
|
5
|
+
%tbody
|
6
|
+
- @status.each do |s|
|
7
|
+
%tr
|
8
|
+
%td
|
9
|
+
#{s[:name]}
|
10
|
+
%td
|
11
|
+
#{s[:desc]}
|
12
|
+
%td
|
13
|
+
- case s[:status]
|
14
|
+
- when 'ok'
|
15
|
+
%span{style: 'color: green'} Ok
|
16
|
+
- when 'bad'
|
17
|
+
%span{style: 'color: red'} Bad
|
18
|
+
- else
|
19
|
+
%span{style: 'color: orange'}= s[:status]
|
20
|
+
|
21
|
+
:css
|
22
|
+
body {
|
23
|
+
background: #fefefe;
|
24
|
+
font-family: sans-serif;
|
25
|
+
}
|
26
|
+
.table {
|
27
|
+
width: 1024px;
|
28
|
+
margin: 0 auto;
|
29
|
+
}
|
30
|
+
.table td, .table th {
|
31
|
+
padding: 12px;
|
32
|
+
text-transform: capitalize;
|
33
|
+
font-weight: 200;
|
34
|
+
}
|
35
|
+
.table th {
|
36
|
+
font-size: 2em;
|
37
|
+
padding: 20px;
|
38
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module Checks
|
3
|
+
# memcached check module
|
4
|
+
# implements status
|
5
|
+
# include memcached check dependencies
|
6
|
+
module Memcached
|
7
|
+
# @config { hosts, user, password }
|
8
|
+
def status(config)
|
9
|
+
client = Dalli::Client.new(config.delete(:url), config)
|
10
|
+
client.get('_')
|
11
|
+
'ok'
|
12
|
+
rescue Dalli::RingError => e
|
13
|
+
Hcheck.logger.error "[HCheck] Memcached::Error::NoServerAvailable #{e.message}"
|
14
|
+
'bad'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(_base)
|
18
|
+
require 'dalli'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module Checks
|
3
|
+
# mongodb check module
|
4
|
+
# implements status
|
5
|
+
# include mongodb check dependencies
|
6
|
+
module Mongodb
|
7
|
+
# @config { hosts, user, password }
|
8
|
+
def status(config)
|
9
|
+
mongo_config = config.merge(connect_timeout: 3)
|
10
|
+
hosts = mongo_config.delete(:hosts).compact
|
11
|
+
client = Mongo::Client.new(hosts, mongo_config.merge(server_selection_timeout: hosts.count * 2))
|
12
|
+
client.database_names
|
13
|
+
client.close
|
14
|
+
'ok'
|
15
|
+
rescue Mongo::Error::NoServerAvailable => e
|
16
|
+
Hcheck.logger.error "[HCheck] Mongo::Error::NoServerAvailable #{e.message}"
|
17
|
+
'bad'
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.included(_base)
|
21
|
+
require 'mongo'
|
22
|
+
Mongo::Logger.level = Logger::INFO
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module Checks
|
3
|
+
# mysql check module
|
4
|
+
# implements status
|
5
|
+
# include mysql check dependencies
|
6
|
+
module Mysql
|
7
|
+
# @config { host, port, username, password, database }
|
8
|
+
def status(config)
|
9
|
+
connection = Mysql2::Client.new(config)
|
10
|
+
connection.close
|
11
|
+
'ok'
|
12
|
+
rescue Mysql2::Error => e
|
13
|
+
Hcheck.logger.error "[HCheck] Mysql2::Error::ConnectionError #{e.message}"
|
14
|
+
'bad'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(_base)
|
18
|
+
require 'mysql2'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module Checks
|
3
|
+
# ping check module
|
4
|
+
# implements status
|
5
|
+
# include ping check dependencies
|
6
|
+
# returns ok only if the response code is 2** or 3**
|
7
|
+
module Ping
|
8
|
+
# @config { url }
|
9
|
+
def status(config)
|
10
|
+
url = URI.parse(config[:url])
|
11
|
+
request = build_request(url)
|
12
|
+
|
13
|
+
case request.request_head(url.path)
|
14
|
+
when Net::HTTPSuccess, Net::HTTPRedirection, Net::HTTPInformation
|
15
|
+
'ok'
|
16
|
+
else
|
17
|
+
'bad'
|
18
|
+
end
|
19
|
+
rescue Net::ReadTimeout, Errno::ECONNREFUSED => e
|
20
|
+
Hcheck.logger.error "[HCheck] Ping Fail #{e.message}"
|
21
|
+
'bad'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.included(_base)
|
25
|
+
require 'net/http'
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_request(url)
|
29
|
+
req = Net::HTTP.new(url.host, url.port)
|
30
|
+
req.use_ssl = true if url.scheme == 'https'
|
31
|
+
req.read_timeout = 5 # seconds
|
32
|
+
|
33
|
+
url.path = '/' if url.path.empty?
|
34
|
+
req
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module Checks
|
3
|
+
# postgresql check module
|
4
|
+
# implements status
|
5
|
+
# include postgresql check dependencies
|
6
|
+
module Postgresql
|
7
|
+
# @config { host, port, options, tty, dbname, user, password }
|
8
|
+
def status(config)
|
9
|
+
config[:user] = config.delete(:username) if config[:username]
|
10
|
+
config[:dbname] = config.delete(:database) if config[:database]
|
11
|
+
|
12
|
+
PG::Connection.new(config).close
|
13
|
+
'ok'
|
14
|
+
rescue PG::ConnectionBad => e
|
15
|
+
Hcheck.logger.error "[HCheck] PG::ConnectionBad #{e.message}"
|
16
|
+
'bad'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.included(_base)
|
20
|
+
require 'pg'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module Checks
|
3
|
+
# rabbitmq check module
|
4
|
+
# implements status
|
5
|
+
# include rabbitmq check dependencies
|
6
|
+
module Rabbitmq
|
7
|
+
# @config { host, vhost, port, user, pass }
|
8
|
+
def status(config)
|
9
|
+
connection = Bunny.new(config)
|
10
|
+
connection.start
|
11
|
+
connection.close
|
12
|
+
'ok'
|
13
|
+
rescue Bunny::TCPConnectionFailed,
|
14
|
+
Bunny::TCPConnectionFailedForAllHosts,
|
15
|
+
Bunny::NetworkFailure,
|
16
|
+
Bunny::AuthenticationFailureError,
|
17
|
+
Bunny::PossibleAuthenticationFailureError,
|
18
|
+
AMQ::Protocol::EmptyResponseError => e
|
19
|
+
|
20
|
+
Hcheck.logger.error "[HCheck] Bunny::Error #{e.message}"
|
21
|
+
'bad'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.included(_base)
|
25
|
+
require 'bunny'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Hcheck
|
2
|
+
module Checks
|
3
|
+
# redis check module
|
4
|
+
# implements status
|
5
|
+
# include redis check dependencies
|
6
|
+
module Redis
|
7
|
+
# @config { host, port, db, password }
|
8
|
+
def status(config)
|
9
|
+
config[:sentinels] = config[:sentinels].map(&:symbolize_keys) if config[:sentinels]
|
10
|
+
|
11
|
+
::Redis.new(config).ping
|
12
|
+
'ok'
|
13
|
+
rescue ::Redis::CannotConnectError => e
|
14
|
+
Hcheck.logger.error "[HCheck] Redis server unavailable #{e.message}"
|
15
|
+
'bad'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(_base)
|
19
|
+
require 'redis'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'hcheck/configuration/service'
|
6
|
+
|
7
|
+
module Hcheck
|
8
|
+
# configuration class that loads configs via various ways
|
9
|
+
# initializes service classes from config
|
10
|
+
class Configuration
|
11
|
+
attr_reader :services
|
12
|
+
DEFAULT_HCHECK_DIR = Gem.loaded_specs['rails'] ? 'config/' : ''
|
13
|
+
DEFAULT_CONFIG_PATH = [DEFAULT_HCHECK_DIR, 'hcheck.yml'].join
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@services = config.map do |key, options|
|
17
|
+
options = [options] unless options.is_a?(Array)
|
18
|
+
options.map { |o| Service.new(key, o) }
|
19
|
+
end.flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def load(config)
|
24
|
+
Hcheck.configure config
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_argv(args)
|
28
|
+
load_file(args[1].strip) if argv_config_present?(args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_file(path)
|
32
|
+
load read(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def load_default
|
36
|
+
load_file(DEFAULT_CONFIG_PATH)
|
37
|
+
end
|
38
|
+
|
39
|
+
def read(path)
|
40
|
+
YAML.safe_load(ERB.new(File.read(path)).result, [Symbol]) || {}
|
41
|
+
rescue StandardError => e
|
42
|
+
raise Hcheck::Errors::ConfigurationError, e
|
43
|
+
end
|
44
|
+
|
45
|
+
def generate_config
|
46
|
+
FileUtils.copy_file(Gem.loaded_specs['hcheck'].gem_dir + '/hcheck.sample.yml', DEFAULT_CONFIG_PATH)
|
47
|
+
puts "Generated #{DEFAULT_CONFIG_PATH}"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def argv_config_present?(argvs)
|
53
|
+
!argvs.empty? && argvs[0].match(/-+(config|c)/i)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|