I want to send out an slack alert. when a client fails a keep alive check.
What is the process to do it? can I know how to do it? I am using hiroakis/docker-sensu-server docker image.
On the slack side:
on the slack side you have to create a new incoming webhook to your desired channel.
On the sensu side:
you create a new handler that uses the webhook.
then you have to assign this handler to be used for the checks you desire in their check configuration file.
If you need a proxy to connect to the internet keep in mind to put that one in the handler as well or in a more elegant way pass it on via the config file.
eg. you can use this handler:
#!/usr/bin/env ruby
# Copyright 2014 Dan Shultz and contributors.
#
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
#
# In order to use this plugin, you must first configure an incoming webhook
# integration in slack. You can create the required webhook by visiting
# https://{your team}.slack.com/services/new/incoming-webhook
#
# After you configure your webhook, you'll need the webhook URL from the integration.
require 'rubygems' if RUBY_VERSION < '1.9.0'
require 'sensu-handler'
require 'json'
class Slack < Sensu::Handler
option :json_config,
description: 'Configuration name',
short: '-j JSONCONFIG',
long: '--json JSONCONFIG',
default: 'slack'
def slack_webhook_url
get_setting('webhook_url')
end
def slack_channel
get_setting('channel')
end
def slack_proxy_addr
get_setting('proxy_addr')
end
def slack_proxy_port
get_setting('proxy_port')
end
def slack_message_prefix
get_setting('message_prefix')
end
def slack_bot_name
get_setting('bot_name')
end
def slack_surround
get_setting('surround')
end
def markdown_enabled
get_setting('markdown_enabled') || true
end
def incident_key
#event['client']['name'] + '/' + #event['check']['name']
end
def get_setting(name)
settings[config[:json_config]][name]
end
def handle
description = #event['check']['notification'] || build_description
post_data("*Check*\n#{incident_key}\n\n*Description*\n#{description}")
end
def build_description
[
#event['check']['output'].strip,
#event['client']['address'],
#event['client']['subscriptions'].join(',')
].join(' : ')
end
def post_data(notice)
uri = URI(slack_webhook_url)
if (defined?(slack_proxy_addr)).nil?
http = Net::HTTP.new(uri.host, uri.port)
else
http = Net::HTTP::Proxy(slack_proxy_addr, slack_proxy_port).new(uri.host, uri.port)
end
http.use_ssl = true
begin
req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
text = slack_surround ? slack_surround + notice + slack_surround : notice
req.body = payload(text).to_json
response = http.request(req)
verify_response(response)
rescue Exception => e
puts "An error has ocurred when posting to slack: #{e.message}"
end
end
def verify_response(response)
case response
when Net::HTTPSuccess
true
else
fail response.error!
end
end
def payload(notice)
{
icon_url: 'http://sensuapp.org/img/sensu_logo_large-c92d73db.png',
attachments: [{
text: [slack_message_prefix, notice].compact.join(' '),
color: color
}]
}.tap do |payload|
payload[:channel] = slack_channel if slack_channel
payload[:username] = slack_bot_name if slack_bot_name
payload[:attachments][0][:mrkdwn_in] = %w(text) if markdown_enabled
end
end
def color
color = {
0 => '#36a64f',
1 => '#FFCC00',
2 => '#FF0000',
3 => '#6600CC'
}
color.fetch(check_status.to_i)
end
def check_status
#event['check']['status']
end
end
and then pass a config file like this on to it
{
"handlers": {
"slack": {
"command": "/etc/sensu/handlers/slack.rb",
"type": "pipe",
"filters": [
],
"severities": [
"ok",
"critical"
]
}
}
}
which then would also include which severities to be handled by that handler
Related
I have a rails worker using redis/sidekiq where I send some data to an API (Active Campaign), so I normally use all the http configurations to send data. I want to have it nice and clean, so it's part of a refactor thing. My worker currently looks like this:
class UpdateLeadIdWorker
include Sidekiq::Worker
BASE_URL = Rails.application.credentials.dig(:active_campaign, :url)
private_constant :BASE_URL
API_KEY = Rails.application.credentials.dig(:active_campaign, :key)
private_constant :API_KEY
def perform(ac_id, current_user_id)
lead = Lead.where(user_id: current_user_id).last
url = URI("#{BASE_URL}/api/3/contacts/#{ac_id}") #<--- need this endpoint
https = bindable_lead_client.assign(url)
pr = post_request.assign(url)
case lead.quote_type
when 'renter'
data = { contact: { fieldValues: [{ field: '5', value: lead.lead_id }] } }
when 'home'
data = { contact: { fieldValues: [{ field: '4', value: lead.lead_id }] } }
when 'auto'
data = { contact: { fieldValues: [{ field: '3', value: lead.lead_id }] } }
else
raise 'Invalid quote type'
end
pr.body = JSON.dump(data)
response = JSON.parse(https.request(pr).read_body).symbolize_keys
if response.code == '200'
Rails.logger.info "Successfully updated contact #{ac_id} with lead id #{lead.lead_id}"
else
raise "Error creating contact: #{response.body}"
end
end
def bindable_lead_client
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http
end
def post_request
post_request_ = Net::HTTP::Put.new(url)
post_request_['Accept'] = 'application/json'
post_request_['Content-Type'] = 'application/json'
post_request_['api-token'] = API_KEY
post_request_
end
end
But whenever I run this I get:
2022-07-28T00:52:08.683Z pid=24178 tid=1s1u WARN: NameError: undefined local variable or method `url' for #<UpdateLeadIdWorker:0x00007fc713442be0 #jid="e2b9ddb6d5f4b8aecffa4d8b">
Did you mean? URI
I don't want everything stuck in one method. How could I achieve to make this cleaner?
Thanks.
Pure ruby wise, The reason you get the error is because your method definition bindable_lead_client is missing the url argument. Hence undefined variable.
So def should look something like:
def bindable_lead_client (url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http
end
and call:
bindable_lead_client(url)
As for how to make this code better, falls under question being too subjective under StackOverflow guidelines, which encourage you to ask more specific questions.
I'm refactoring some code in a Rails app consisting of several microservices. The faraday_middleware gem is used for communication between services.
I managed to replace several calls to Faraday::new() in different helper files with one single call to Faraday::new() in a ServiceConnectionHelper module. All of these replaced calls had an url parameter: Faraday.new(url: url)
But there's two very similar pieces of code left that I'd like to get rid of. In these cases, there is no url parameter. This is the old (working) code:
# This code calls the connection function below
def create(resource)
params = {
resource_id: resource.to_param,
version: resource.version,
file: Faraday::UploadIO.new(resource.file.path, resource.mime_type.to_s, resource.file.original_filename)
}
res = connection(resource.authorization).post(foobar_url, params)
return res.body['id'] if [200, 201].include?(res.status)
raise UploadError, res.body['error']
end
# connection function
def connection(authorization_header = nil)
Faraday.new do |conn|
conn.use FaradayMiddleware::FollowRedirects, limit: 5
conn.request :multipart
conn.request :url_encoded
conn.use FaradayMiddleware::ParseJson, content_type: 'application/json'
conn.adapter Faraday.default_adapter
conn.headers['Accept'] = 'application/json'
conn.headers['Authorization'] = authorization_header unless authorization_header.nil?
end
end
This is the code I want to use instead. It's not working because of an error inside the create function. When I catch it and log it, e.inspect is just #<UploadError: Please specify a file>
# Small change only: Te other service's url is computed in the ServiceConnectionHelper module
def create(resource)
params = {
resource_id: resource.to_param,
version: resource.version,
file: Faraday::UploadIO.new(resource.file.path, resource.mime_type.to_s, resource.file.original_filename)
}
# This is were the error happens
res = connection(resource.authorization).post('/', params)
return res.body['id'] if [200, 201].include?(res.status)
raise UploadError, res.body['error']
end
# connection function calls the new helper module now
def connection(authorization_header = nil)
ServiceConnectionHelper.connection('foobar', authorization_header)
end
# the new module
module ServiceConnectionHelper
class << self
def connection(service, oauth_token = nil)
url = service_url(service)
Faraday.new(url: url) do |conn|
conn.use FaradayMiddleware::FollowRedirects, limit: 5
conn.request :url_encoded
conn.adapter Faraday.default_adapter
conn.request :multipart
conn.use FaradayMiddleware::ParseJson, content_type: 'application/json'
conn.headers['Accept'] = 'application/json'
conn.headers['Authorization'] = oauth_token if oauth_token
end
end
private
def service_url(service)
url = case service
when 'foobar' then 'foobar_url_as_a_string'
# the same for other services
end
url
end
end
end
What can I do to make the ServiceConnectionHelper work in this case?
Compared of your first example, the order of request changed:
conn.request :url_encoded
conn.request :multipart
It seems like we have always initialize and use the same private key when encoding and decoding a token in RSA256 algorithm:
payload = {:data => 'test'}
rsa_private = OpenSSL::PKey::RSA.generate 2048
rsa_public = rsa_private.public_key
token = JWT.encode payload, rsa_private, 'RS256'
# eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ0ZXN0IjoiZGF0YSJ9.c2FynXNyi6_PeKxrDGxfS3OLwQ8lTDbWBWdq7oMviCy2ZfFpzvW2E_odCWJrbLof-eplHCsKzW7MGAntHMALXgclm_Cs9i2Exi6BZHzpr9suYkrhIjwqV1tCgMBCQpdeMwIq6SyKVjgH3L51ivIt0-GDDPDH1Rcut3jRQzp3Q35bg3tcI2iVg7t3Msvl9QrxXAdYNFiS5KXH22aJZ8X_O2HgqVYBXfSB1ygTYUmKTIIyLbntPQ7R22rFko1knGWOgQCoYXwbtpuKRZVFrxX958L2gUWgb4jEQNf3fhOtkBm1mJpj-7BGst00o8g_3P2zHy-3aKgpPo1XlKQGjRrrxA
puts token
decoded_token = JWT.decode token, rsa_public, true, { :algorithm => 'RS256' }
# Array
# [
# {"data"=>"test"}, # payload
# {"alg"=>"RS256"} # header
# ]
puts decoded_token
But what is the best way t do that in a Rails 5.1 app ?
I figured out how to fix that.
So I have a class JsonWebTokenwith 2 class methods: encodeand decodedefined as follows:
class JsonWebToken
ALGO = 'RS256'
class << self
def encode(payload, exp = 2.hours.from_now)
# set expiry to 2 hours from creation time
payload[:exp] = exp.to_i
JWT.encode(payload, private_key, ALGO)
end
def decode(token)
body = JWT.decode(token, private_key.public_key, true, algorithm: ALGO)[0]
HashWithIndifferentAccess.new body
# rescue from expiry exception
rescue JWT::ExpiredSignature, JWT::VerificationError => e
# raise custom error to be handled by custom handler
raise ExceptionHandler::ExpiredSignature, e.message
end
private
def private_key
#rsa_private ||= OpenSSL::PKey::RSA.generate 2048
end
end
end
I just use another private static method to generate an rsa private key if needed.
I manually requesting a request token from twitter and pass the callback to the default page for a device, but get an error
Started GET "/users/auth/twitter/callback?device=mobile&oauth_token=mVpOFb1ruczKw7LzbgQYX73nq81hiw5OEBSOpob5rJk&oauth_verifier=WzBwpFdf7rYDH4DDWNbIfYPkHrIUzam9Ld6vskQrzNA" for 127.0.0.1 at 2014-02-03 18:00:03 +0400
omniauth: (twitter) Authentication failure! invalid_credentials: OAuth :: Unauthorized, 401 Unauthorized
If I log in through Devise, all without errors. Instructions took here. Why is this happening?
class Api::TwitterController < ApplicationController
def get_auth_token
consumer_key = OAUTH_KEYS[Rails.env]['twitter']['client_id'] # Obtainable from your destination site's API admin panel
consumer_secret = OAUTH_KEYS[Rails.env]['twitter']['secret_key'] # As above
callback_url = user_omniauth_callback_url(:twitter, device: :mobile)
method = 'POST'
uri = 'https://api.twitter.com/oauth/request_token'
params = set_params(consumer_key)
params['oauth_callback'] = url_encode(callback_url)
params['oauth_signature'] = url_encode(sign(consumer_secret + '&', signature_base_string(method, uri, params)))
token_data = parse_string(request_data(header(params), uri, method))
auth_token, auth_token_secret = [token_data['oauth_token'], token_data['oauth_token_secret']] # save these values, they'll be used again later
redirect_to "https://api.twitter.com/oauth/authorize?oauth_token=#{auth_token}"
end
private
# where parse_string is simply
def parse_string(str)
ret = {}
str.split('&').each do |pair|
key_and_val = pair.split('=')
ret[key_and_val[0]] = key_and_val[1]
end
ret
end
def set_params(consumer_key)
params = {
'oauth_consumer_key' => consumer_key, # Your consumer key
'oauth_nonce' => generate_nonce, # A random string, see below for function
'oauth_signature_method' => 'HMAC-SHA1', # How you'll be signing (see later)
'oauth_timestamp' => Time.now.getutc.to_i.to_s, # Timestamp
'oauth_version' => '1.0' # oAuth version
}
end
def generate_nonce(size=7)
Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, '')
end
def signature_base_string(method, uri, params)
# Join up the parameters into one long URL-safe string of key value pairs
encoded_params = params.sort.collect{ |k, v| url_encode("#{k}=#{v}") }.join('%26')
# Join the above with your method and URL-safe destination URL
method + '&' + url_encode(uri) + '&' + encoded_params
end
# I'm a PHP developer primarily, hence the name of this function!
def url_encode(string)
CGI::escape(string)
end
# where sign is:
def sign(key, base_string)
digest = OpenSSL::Digest::Digest.new('sha1')
hmac = OpenSSL::HMAC.digest(digest, key, base_string)
Base64.encode64(hmac).chomp.gsub(/\n/, '')
end
# where header is:
def header(params)
header = "OAuth "
params.each do |k, v|
header += "#{k}=\"#{v}\", "
end
header.slice(0..-3) # chop off last ", "
end
def request_data(header, base_uri, method, post_data=nil)
url = URI.parse(base_uri)
http = Net::HTTP.new(url.host, 443) # set to 80 if not using HTTPS
http.use_ssl = true # ignore if not using HTTPS
if method == 'POST'
# post_data here should be your encoded POST string, NOT an array
resp, data = http.post(url.path, post_data, { 'Authorization' => header })
else
resp, data = http.get(url.to_s, { 'Authorization' => header })
end
resp.body
end
end
Problem solved, it was necessary to add some data in the session
auth_token, auth_token_secret = [token_data['oauth_token'], token_data['oauth_token_secret']]
session['oauth'] ||= {}
session['oauth']['twitter'] ||= {}
session['oauth']['twitter']['request_token'] = auth_token
session['oauth']['twitter']['request_secret'] = auth_token_secret
session['oauth']['twitter']['callback_confirmed'] = true
Here's how some of my existing logging code with Log4r is working. As you can see in the WorkerX::a_method, any time that I log a message I want the class name and the calling method to be included (I don't want all the caller history or any other noise, which was my purpose behind LgrHelper).
class WorkerX
include LgrHelper
def initialize(args = {})
#logger = Lgr.new({:debug => args[:debug], :logger_type => 'WorkerX'})
end
def a_method
error_msg("some error went down here")
# This prints out: "WorkerX::a_method - some error went down here"
end
end
class Lgr
require 'log4r'
include Log4r
def initialize(args = {}) # args: debug boolean, logger type
#debug = args[:debug]
#logger_type = args[:logger_type]
#logger = Log4r::Logger.new(#logger_type)
format = Log4r::PatternFormatter.new(:pattern => "%l:\t%d - %m")
outputter = Log4r::StdoutOutputter.new('console', :formatter => format)
#logger.outputters = outputter
if #debug then
#logger.level = DEBUG
else
#logger.level = INFO
end
end
def debug(msg)
#logger.debug(msg)
end
def info(msg)
#logger.info(msg)
end
def warn(msg)
#logger.warn(msg)
end
def error(msg)
#logger.error(msg)
end
def level
#logger.level
end
end
module LgrHelper
# This module should only be included in a class that has a #logger instance variable, obviously.
protected
def info_msg(msg)
#logger.info(log_intro_msg(self.method_caller_name) + msg)
end
def debug_msg(msg)
#logger.debug(log_intro_msg(self.method_caller_name) + msg)
end
def warn_msg(msg)
#logger.warn(log_intro_msg(self.method_caller_name) + msg)
end
def error_msg(msg)
#logger.error(log_intro_msg(self.method_caller_name) + msg)
end
def log_intro_msg(method)
msg = class_name
msg += '::'
msg += method
msg += ' - '
msg
end
def class_name
self.class.name
end
def method_caller_name
if /`(.*)'/.match(caller[1]) then # caller.first
$1
else
nil
end
end
end
I really don't like this approach. I'd rather just use the existing #logger instance variable to print the message and be smart enough to know the context. How can this, or similar simpler approach, be done?
My environment is Rails 2.3.11 (for now!).
After posting my answer using extend, (see "EDIT", below), I thought I'd try using set_trace_func to keep a sort of stack trace like in the discussion I posted to. Here is my final solution; the set_trace_proc call would be put in an initializer or similar.
#!/usr/bin/env ruby
# Keep track of the classes that invoke each "call" event
# and the method they called as an array of arrays.
# The array is in the format: [calling_class, called_method]
set_trace_func proc { |event, file, line, id, bind, klass|
if event == "call"
Thread.current[:callstack] ||= []
Thread.current[:callstack].push [klass, id]
elsif event == "return"
Thread.current[:callstack].pop
end
}
class Lgr
require 'log4r'
include Log4r
def initialize(args = {}) # args: debug boolean, logger type
#debug = args[:debug]
#logger_type = args[:logger_type]
#logger = Log4r::Logger.new(#logger_type)
format = Log4r::PatternFormatter.new(:pattern => "%l:\t%d - %m")
outputter = Log4r::StdoutOutputter.new('console', :formatter => format)
#logger.outputters = outputter
if #debug then
#logger.level = DEBUG
else
#logger.level = INFO
end
end
def debug(msg)
#logger.debug(msg)
end
def info(msg)
#logger.info(msg)
end
def warn(msg)
#logger.warn(msg)
end
def error(msg)
#logger.error(msg)
end
def level
#logger.level
end
def invoker
Thread.current[:callstack] ||= []
( Thread.current[:callstack][-2] || ['Kernel', 'main'] )
end
end
class CallingMethodLogger < Lgr
[:info, :debug, :warn, :error].each do |meth|
define_method(meth) { |msg| super("#{invoker[0]}::#{invoker[1]} - #{msg}") }
end
end
class WorkerX
def initialize(args = {})
#logger = CallingMethodLogger.new({:debug => args[:debug], :logger_type => 'WorkerX'})
end
def a_method
#logger.error("some error went down here")
# This prints out: "WorkerX::a_method - some error went down here"
end
end
w = WorkerX.new
w.a_method
I don't know how much, if any, the calls to the proc will affect the performance of an application; if it ends up being a concern, perhaps something not as intelligent about the calling class (like my old answer, below) will work better.
[EDIT: What follows is my old answer, referenced above.]
How about using extend? Here's a quick-and-dirty script I put together from your code to test it out; I had to reorder things to avoid errors, but the code is the same with the exception of LgrHelper (which I renamed CallingMethodLogger) and the second line of WorkerX's initializer:
#!/usr/bin/env ruby
module CallingMethodLogger
def info(msg)
super("#{#logger_type}::#{method_caller_name} - " + msg)
end
def debug(msg)
super("#{#logger_type}::#{method_caller_name} - " + msg)
end
def warn(msg)
super("#{#logger_type}::#{method_caller_name} - " + msg)
end
def error(msg)
super("#{#logger_type}::#{method_caller_name} - " + msg)
end
def method_caller_name
if /`(.*)'/.match(caller[1]) then # caller.first
$1
else
nil
end
end
end
class Lgr
require 'log4r'
include Log4r
def initialize(args = {}) # args: debug boolean, logger type
#debug = args[:debug]
#logger_type = args[:logger_type]
#logger = Log4r::Logger.new(#logger_type)
format = Log4r::PatternFormatter.new(:pattern => "%l:\t%d - %m")
outputter = Log4r::StdoutOutputter.new('console', :formatter => format)
#logger.outputters = outputter
if #debug then
#logger.level = DEBUG
else
#logger.level = INFO
end
end
def debug(msg)
#logger.debug(msg)
end
def info(msg)
#logger.info(msg)
end
def warn(msg)
#logger.warn(msg)
end
def error(msg)
#logger.error(msg)
end
def level
#logger.level
end
end
class WorkerX
def initialize(args = {})
#logger = Lgr.new({:debug => args[:debug], :logger_type => 'WorkerX'})
#logger.extend CallingMethodLogger
end
def a_method
#logger.error("some error went down here")
# This prints out: "WorkerX::a_method - some error went down here"
end
end
w = WorkerX.new
w.a_method
The output is:
ERROR: 2011-07-24 20:01:40 - WorkerX::a_method - some error went down here
The downside is, via this method, the caller's class name isn't automatically figured out; it's explicit based on the #logger_type passed into the Lgr instance. However, you may be able to use another method to get the actual name of the class--perhaps something like the call_stack gem or using Kernel#set_trace_func--see this thread.