class HomeController < ApplicationController
layout 'frontend'
STATUS = Hash.new('warning')
STATUS[true] = 'ok'
STATUS[false] = 'error'
OS_VERSION = "Description:\tUbuntu 18.04"
PG_VERSION = 'PostgreSQL 12'.freeze
POSTGIS_VERSION = '3.0'.freeze
CDB_VALID_VERSION = '0.36.0'.freeze
CDB_LATEST_VERSION = '0.36.0'.freeze
REDIS_VERSION = '4'.freeze
RUBY_BIN_VERSION = 'ruby 2.2'.freeze
NODE_VERSION = 'v6.9.2'.freeze
GEOS_VERSION = '3.5.0'.freeze
GDAL_VERSION = '2.2'.freeze
WINDSHAFT_VALID_VERSION = '6.1'.freeze
WINDSHAFT_LATEST_VERSION = '6.1.1'.freeze
RUN_WINDSHAFT_INSTRUCTIONS = 'Run Windshaft: cd /Windshaft-cartodb && node app.js development'\
''.freeze
SQL_API_VALID_VERSION = '2.1'.freeze
SQL_API_LATEST_VERSION = '2.1.1'.freeze
RUN_SQL_API_INSTRUCTIONS = 'Run SQL API cd /CartoDB-SQL-API; node app.js development'.freeze
RUN_RESQUE_INSTRUCTIONS = 'Run Resque bundle exec script/resque'.freeze
skip_before_filter :browser_is_html5_compliant?, only: :app_status
# Don't force org urls
skip_before_filter :ensure_org_url_if_org_user
def app_status
return head(503) if Cartodb.config[:disable_file] && File.exists?(File.expand_path(Cartodb.config[:disable_file]))
db_ok = check_db
redis_ok = check_redis
api_ok = true
head (db_ok && redis_ok && api_ok) ? 200 : 500
rescue StandardError => e
log_info(message: 'status method failed', exception: e)
head 500
end
def app_diagnosis
return head(400) if Cartodb.config[:cartodb_com_hosted] == false
@diagnosis = [
diagnosis_output('Configuration') { configuration_diagnosis },
diagnosis_output('Operating System') { single_line_command_version_diagnosis('lsb_release -a', OS_VERSION, 1) },
diagnosis_output('Ruby') { single_line_command_version_diagnosis('ruby --version', RUBY_BIN_VERSION) },
diagnosis_output('Node') { single_line_command_version_diagnosis('node --version', minor_version: NODE_VERSION) },
diagnosis_output('PostgreSQL') { pg_diagnosis },
diagnosis_output('PostGIS') { extension_diagnosis('postgis', POSTGIS_VERSION) },
diagnosis_output('CartoDB extension') { extension_diagnosis('cartodb', CDB_VALID_VERSION, CDB_LATEST_VERSION) },
diagnosis_output('Database connection') { db_diagnosis },
diagnosis_output('Redis') { redis_diagnosis },
diagnosis_output('Redis connection') { redis_connection_diagnosis },
diagnosis_output('Windshaft', RUN_WINDSHAFT_INSTRUCTIONS) {
windshaft_diagnosis(WINDSHAFT_VALID_VERSION, WINDSHAFT_LATEST_VERSION) },
diagnosis_output('SQL API', RUN_SQL_API_INSTRUCTIONS) {
sql_api_diagnosis(SQL_API_VALID_VERSION, SQL_API_LATEST_VERSION) },
diagnosis_output('Resque') { resque_diagnosis(RUN_RESQUE_INSTRUCTIONS) },
diagnosis_output('GEOS') do
single_line_command_version_diagnosis('geos-config --version', minor_version: GEOS_VERSION)
end,
diagnosis_output('GDAL') { single_line_command_version_diagnosis('gdal-config --version', GDAL_VERSION) },
]
end
private
def configuration_diagnosis
# favor displaying an organization user if any present
organization = Carto::Organization.first
user = organization ? organization.owner : Carto::User.first
['', [
"Environment: #{environment}",
"Subdomainless URLs: #{Cartodb.config[:subdomainless_urls]}",
"Sample Editor URL: #{CartoDB.url(self, 'datasets_index', user: user)}",
"Sample Editor APIs URL: #{CartoDB.url(self, 'api_v1_visualizations_index', user: user)}"
]]
end
def environment
Rails.env
end
def pg_diagnosis
version_diagnosis(PG_VERSION) {
version = SequelRails.connection.fetch('select version()').first.values[0]
[version, [version]]
}
end
def db_diagnosis
[STATUS[check_db], []]
end
def redis_diagnosis
version_diagnosis(REDIS_VERSION) {
version = $tables_metadata.info['redis_version']
[version, [version]]
}
end
def redis_connection_diagnosis
[STATUS[check_redis], []]
end
def windshaft_diagnosis(supported_version, latest_version)
config = Cartodb.config[:tiler]
url_config_key = 'internal'
endpoint_prefix = ""
version_key = 'windshaft_cartodb'
api_service_diagnosis(config, url_config_key, supported_version, latest_version, endpoint_prefix, version_key)
end
def sql_api_diagnosis(supported_version, latest_version)
config = Cartodb.config[:sql_api]
url_config_key = 'private'
endpoint_prefix = "api/v1/"
version_key = 'cartodb_sql_api'
api_service_diagnosis(config, url_config_key, supported_version, latest_version, endpoint_prefix, version_key)
end
def api_service_diagnosis(config, url_config_key, supported_version, latest_version, endpoint_prefix, version_key)
service_url = configuration_url(config[url_config_key])
info = safe_json_get("#{service_url}/#{endpoint_prefix}version")
version = info[version_key]
messages = ["Service url: #{service_url}"]
messages << "Full config: #{config}"
messages.concat info.to_a.map { |s, v| "#{s}: #{v}" }
valid = valid?(supported_version, latest_version, version)
if valid != false
messages << "Currently we support #{supported_version}. Latest: #{latest_version}" unless valid
health = safe_json_get("#{service_url}/#{endpoint_prefix}health")
unless health['enabled'] == true
health['instructions'] = "Enable health checking at config/environments/#{environment}.js"
end
health_ok = health['ok'] == true
messages.concat(health.reject { |k, v| k == 'result' }
.to_a
.map { |s, v| "Health #{s}: #{v}" })
end
[STATUS[response.response_code == 200 && valid && health_ok], messages]
end
# true: latest
# nil: supported
# false: not supported
def valid?(supported_version, latest_version, version)
if (version =~ /\A#{latest_version}/ ? true : nil)
true
else
version =~ /\A#{supported_version}/ ? nil : false
end
end
def resque_diagnosis(help)
Open3.popen3('ps xah | grep "[s]cript/resque"') do |stdin, stdout, stderr, process|
output = stdout.read
status = output != nil && output != ''
messages = output.split("\n")
[STATUS[status], messages.append(status ? ("Running pids: #{running_import_ids}") : help)]
end
end
def running_import_ids
Resque::Worker.all.map do |worker|
next unless worker.job['queue'] == 'imports'
worker.job['payload']['args'].first['job_id'] rescue nil
end.compact
end
def check_db
SequelRails.connection.select('OK').first.values.include?('OK')
end
def check_redis
$tables_metadata.dbsize != nil
end
def http_client
@http_client ||= Carto::Http::Client.get('diagnosis')
end
def diagnosis_output(title, help = nil)
[title, safe_diagnosis(help) { yield } ].flatten(1)
end
def safe_diagnosis(help = nil)
yield
rescue StandardError => e
[ STATUS[false], [ help, e.to_s ].compact ]
end
def extension_diagnosis(extension, supported_version, latest_version = nil)
version = SequelRails.connection.fetch("select default_version from pg_available_extensions where name = '#{extension}'").first.values[0]
status_and_messages(version, [], supported_version, latest_version)
end
def version_diagnosis(supported_version, latest_version = nil, minor_version: nil)
version_and_messages = yield
version = version_and_messages[0]
messages = version_and_messages[1]
status_and_messages(version, messages, supported_version, latest_version, minor_version: minor_version)
end
def status_and_messages(version, messages, supported_version, latest_version, minor_version: nil)
valid = if minor_version.present?
Gem::Version.new(version.delete('v')) >= Gem::Version.new(minor_version.delete('v'))
else
version =~ /\A#{supported_version}/ ? true : false
end
messages = ["Installed version: #{version}"]
unless valid
latest = latest_version.nil? ? '' : "Latest version: #{latest_version}"
messages << "Current supported version: #{supported_version}.#{latest}"
end
if latest_version && valid
latest = version =~ /\A#{latest_version}/ ? true : false
messages << "Latest version: #{latest_version}" unless latest
[STATUS[latest || 'supported'], messages]
else
[STATUS[valid], messages]
end
end
def single_line_command_version_diagnosis(
command,
supported_version = nil,
line_index = 0,
latest_version = nil,
minor_version: nil
)
version_diagnosis(supported_version, latest_version, minor_version: minor_version) do
stdin, stdout, stderr, process = Open3.popen3(command)
output = stdout.read.split("\n")
if latest_version.nil?
[output[line_index], output]
end
end
end
def configuration_url(conf)
"#{conf['protocol']}://#{conf['domain']}:#{conf['port']}"
end
def safe_json_get(url)
JSON.parse(http_client.get(url).body)
rescue StandardError => e
{ 'error fetching info' => e.message }
end
end