Skip to content

Instantly share code, notes, and snippets.

@patio11
Created November 26, 2015 09:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save patio11/d26e42fbd55d39426c40 to your computer and use it in GitHub Desktop.
Save patio11/d26e42fbd55d39426c40 to your computer and use it in GitHub Desktop.
Implementing a /healthcheck endpoint in Slack to read out consul status in a human-readable fashion
class SlackController < ApplicationController
skip_before_action :verify_authenticity_token
@@slack_security_tokens =
["copy-paste-the-token-you-get-from-Slack-when-configuring-the-integration-here"]
before_filter :bounce_access_not_from_slack
def healthcheck
services = SystemStatus.list_services
if services.blank?
render json: {
response_type: "in_channel",
text: "PlayerUI couldn't connect successfully to consul cluster. BAD NEWS."
}
else
arguments = params[:text]
node_filter = nil
if arguments.present? && services.include?(arguments.split(" ")[0])
# /healthcheck gm <-- Filters results down to only gm nodes
services = [arguments.split(" ")[0].strip]
elsif arguments.present? && (arguments.split(" ")[0].present?)
# /healthcheck 172.31.16.60 <-- Filters results down to only this IP.
node_filter = arguments.split(" ")[0].strip
end
response = []
services.each do |service|
status = SystemStatus.service_up?(service)
status_string = status ? "[UP]" : "[*DOWN*]"
nodes = SystemStatus.list_service_nodes(service)
node_details = []
nodes.each do |node|
tags = (node[:tags].length < 10) ? node[:tags].join("|") : (node[:tags][0..9].join("|") + "...")
detail = "#{node[:address]}:#{node[:port]} (#{tags})"
detail = "~#{detail}~" unless SystemStatus.service_up?(service, node[:tags].first)
node_details << detail if node_filter.blank? || (node_filter == node[:address])
end
response << [service, status_string, node_details].join(" ") if node_filter.blank? || node_details.present?
end
render json: {
response_type: "in_channel",
text: response.join("\n")
}
end
private
def bounce_access_not_from_slack
unless Rails.env.development? || @@slack_security_tokens.include?(params[:token])
raise "Not authorized: token sent doesn't match pre-configured Slack security token."
end
end
end
require 'resolv'
class SystemStatus
def self.list_services
services = JSON.parse(RestClient.get("http://localhost:8500/v1/catalog/services")) rescue nil
services ? services.keys : nil
end
def self.list_service_nodes(service)
nodes = JSON.parse(RestClient.get("http://localhost:8500/v1/catalog/service/#{service}")) rescue []
nodes.map do |node|
node[:address] = node["ServiceAddress"].present? ? node["ServiceAddress"] : node["Address"]
node[:port] = node["ServicePort"] || (node["Port"] || "0").to_i
node[:tags] = node["ServiceTags"] || []
node[:node] = node["Node"]
end
nodes
end
def self.service_up?(service, tag = nil)
if !tag
(!!dns.getaddress("#{service}.service.consul")) rescue false
else
(!!dns.getaddress("#{tag}.#{service}.service.consul")) rescue false
end
end
def self.node_up?(node)
(!!dns.getaddress("#{node}.node.consul")) rescue false
end
def self.service_statuses(bust_cache = false)
t = bust_cache ? nil : Time.now.to_i
Rails.cache.fetch("SystemStatus.service_statuses(#{t})", :expires_in => 1.minute) do
services = list_services
services.inject({}) {|acc, service| acc[service] = service_up?(service) ? "up" : "down"; acc}
end
end
def self.dns
Resolv::DNS.new(nameserver: ["127.0.0.1"])
end
end
MIT license, same as Rails. Feel free to hack the heck out of this.
@cosmith
Copy link

cosmith commented Nov 26, 2015

Needs more emoticons ✅ and ❌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment