-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Capistrano 3 support #43
Changes from all commits
6b23d7f
ca6749b
bc129f1
e098222
f9cfecf
c280140
dece922
f18d43f
542e483
27cd038
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
require 'capistrano/setup' | ||
require 'capistrano/datadog' | ||
|
||
set :datadog_api_key, 'my_api_key' | ||
set :stage, :test | ||
# set :format, :pretty | ||
# set :log_level, :info | ||
# set :pty, true | ||
|
||
server "host0", roles: ["thing"] | ||
server "host1", roles: ["other_thing"] | ||
|
||
desc "Hello world" | ||
task :hello do | ||
on roles(:all) do |host| | ||
info capture('echo "$(date): Hello from $(whoami)@$(hostname) !"') | ||
end | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,44 @@ | ||
require "benchmark" | ||
require "etc" | ||
require "digest/md5" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we still use Digest anywhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
require "socket" | ||
require "time" | ||
require "timeout" | ||
require "delegate" | ||
|
||
require "dogapi" | ||
|
||
# Monkeypatch capistrano to collect data about the tasks that it's running | ||
module Capistrano | ||
class Configuration | ||
module Execution | ||
# Attempts to locate the task at the given fully-qualified path, and | ||
# execute it. If no such task exists, a Capistrano::NoSuchTaskError | ||
# will be raised. | ||
# Also, capture the time the task took to execute, and the logs it | ||
# outputted for submission to Datadog | ||
def find_and_execute_task(path, hooks = {}) | ||
task = find_task(path) or raise NoSuchTaskError, "the task `#{path}' does not exist" | ||
result = nil | ||
reporter = Capistrano::Datadog.reporter | ||
timing = Benchmark.measure(task.fully_qualified_name) do | ||
# Set the current task so that the logger knows which task to | ||
# associate the logs with | ||
reporter.current_task = task.fully_qualified_name | ||
trigger(hooks[:before], task) if hooks[:before] | ||
result = execute_task(task) | ||
trigger(hooks[:after], task) if hooks[:after] | ||
reporter.current_task = nil | ||
end | ||
# Collect the timing in a list for later reporting | ||
reporter.record_task task, timing | ||
result | ||
end | ||
end | ||
end | ||
|
||
class Logger | ||
# Make the device attribute writeable so we can swap it out | ||
# with something that captures logging out by task | ||
attr_accessor :device | ||
end | ||
end | ||
|
||
|
||
module Capistrano | ||
module Datadog | ||
# Singleton method for Reporter | ||
def self.reporter() | ||
@reporter || @reporter = Reporter.new | ||
end | ||
|
||
def self.cap_version() | ||
if @cap_version.nil? then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As well as other locations where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I won't fix that today. Maybe we can update the linting rules to take that into account and fix all the lint errors in the whole library in a single pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I've got some tricks up my sleeve for that, and was thinking we possibly defer that for the newer datadog.rb lib. |
||
if Configuration.respond_to? :instance then | ||
@cap_version = :v2 | ||
else | ||
@cap_version = :v3 | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this might be better expressed as a ternary operation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like it, looks like line noise.
I think it's just personal preference. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. STYLE WARS the only thing I like about the ternary operator is that it's clear in a single place that you want to assign to the same thing in either condition. but yea, it's not a great fit with ruby's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely a stylistic change - I honestly think one line to express v2 vs v3 is cleaner than 5 lines, but it's no blocker by any stretch of imagination. |
||
end | ||
@cap_version | ||
end | ||
|
||
def self.submit(api_key) | ||
begin | ||
if api_key | ||
dog = Dogapi::Client.new(api_key) | ||
reporter.report.each do |event| | ||
dog.emit_event event | ||
end | ||
else | ||
puts "No api key set, not submitting to Datadog" | ||
end | ||
rescue Timeout::Error => e | ||
puts "Could not submit to Datadog, request timed out." | ||
rescue => e | ||
puts "Could not submit to Datadog: #{e.inspect}\n#{e.backtrace.join("\n")}" | ||
end | ||
end | ||
|
||
# Collects info about the tasks that ran in order to submit to Datadog | ||
class Reporter | ||
attr_accessor :current_task | ||
|
@@ -62,15 +49,12 @@ def initialize() | |
@logging_output = {} | ||
end | ||
|
||
def record_task(task, timing) | ||
roles = task.options[:roles] | ||
if roles.is_a? Proc | ||
roles = roles.call | ||
end | ||
def record_task(task_name, timing, roles, stage=nil) | ||
@tasks << { | ||
:name => task.fully_qualified_name, | ||
:timing => timing.real, | ||
:roles => roles | ||
:name => task_name, | ||
:timing => timing, | ||
:roles => roles, | ||
:stage => stage | ||
} | ||
end | ||
|
||
|
@@ -93,11 +77,18 @@ def report() | |
name = task[:name] | ||
roles = Array(task[:roles]).map(&:to_s).sort | ||
tags = ["#capistrano"] + (roles.map { |t| '#role:' + t }) | ||
if !task[:stage].nil? and !task[:stage].empty? then | ||
tags << "#stage:#{task[:stage]}" | ||
end | ||
title = "%s@%s ran %s on %s with capistrano in %.2f secs" % [user, hostname, name, roles.join(', '), task[:timing]] | ||
type = "deploy" | ||
alert_type = "success" | ||
source_type = "capistrano" | ||
message = "@@@" + "\n" + (@logging_output[name] || []).join('') + "@@@" | ||
message_content = (@logging_output[name] || []).join('') | ||
message = if !message_content.empty? then | ||
# Strip out color control characters | ||
message_content = message_content.gsub(/\e\[(\d+)m/, '') | ||
"@@@\n#{message_content}@@@" else "" end | ||
|
||
Dogapi::Event.new(message, | ||
:msg_title => title, | ||
|
@@ -111,46 +102,14 @@ def report() | |
end | ||
end | ||
|
||
class LogCapture < SimpleDelegator | ||
def puts(message) | ||
Capistrano::Datadog::reporter.record_log message | ||
__getobj__.puts message | ||
end | ||
end | ||
end | ||
|
||
if Configuration.respond_to? :instance then | ||
# Capistrano v2 | ||
Configuration.instance(:must_exist).load do | ||
# Wrap the existing logging target with the Datadog capture class | ||
logger.device = Datadog::LogCapture.new logger.device | ||
|
||
# Trigger the Datadog submission once all the tasks have run | ||
on :exit, "datadog:submit" | ||
namespace :datadog do | ||
desc "Submit the tasks that have run to Datadog as events" | ||
task :submit do |ns| | ||
begin | ||
api_key = variables[:datadog_api_key] | ||
if api_key | ||
dog = Dogapi::Client.new(api_key) | ||
Datadog::reporter.report.each do |event| | ||
dog.emit_event event | ||
end | ||
else | ||
puts "No api key set, not submitting to Datadog" | ||
end | ||
rescue Timeout::Error => e | ||
puts "Could not submit to Datadog, request timed out." | ||
rescue => e | ||
puts "Could not submit to Datadog: #{e.inspect}\n#{e.backtrace.join("\n")}" | ||
end | ||
end | ||
end | ||
end | ||
else | ||
# No support yet for Capistrano v3 | ||
puts "No Datadog events will be sent since Datadog currently only supports Capistrano v2. For more info, see https://github.com/DataDog/dogapi-rb/issues/40." | ||
end | ||
end | ||
|
||
case Capistrano::Datadog::cap_version | ||
when :v2 | ||
require 'capistrano/datadog/v2' | ||
when :v3 | ||
require 'capistrano/datadog/v3' | ||
else | ||
puts "Unknown version: #{Capistrano::Datadog::cap_version.inspect}" | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
require "benchmark" | ||
require "delegate" | ||
|
||
# Capistrano v2 | ||
|
||
# Monkeypatch capistrano to collect data about the tasks that it's running | ||
module Capistrano | ||
class Configuration | ||
module Execution | ||
# Attempts to locate the task at the given fully-qualified path, and | ||
# execute it. If no such task exists, a Capistrano::NoSuchTaskError | ||
# will be raised. | ||
# Also, capture the time the task took to execute, and the logs it | ||
# outputted for submission to Datadog | ||
def find_and_execute_task(path, hooks = {}) | ||
task = find_task(path) or raise NoSuchTaskError, "the task `#{path}' does not exist" | ||
result = nil | ||
reporter = Capistrano::Datadog.reporter | ||
task_name = task.fully_qualified_name | ||
timing = Benchmark.measure(task_name) do | ||
# Set the current task so that the logger knows which task to | ||
# associate the logs with | ||
reporter.current_task = task_name | ||
trigger(hooks[:before], task) if hooks[:before] | ||
result = execute_task(task) | ||
trigger(hooks[:after], task) if hooks[:after] | ||
reporter.current_task = nil | ||
end | ||
|
||
# Record the task name, its timing and roles | ||
roles = task.options[:roles] | ||
if roles.is_a? Proc | ||
roles = roles.call | ||
end | ||
reporter.record_task(task_name, timing.real, roles, task.namespace.variables[:stage]) | ||
|
||
# Return the original result | ||
result | ||
end | ||
end | ||
end | ||
|
||
class Logger | ||
# Make the device attribute writeable so we can swap it out | ||
# with something that captures logging out by task | ||
attr_accessor :device | ||
end | ||
end | ||
|
||
|
||
module Capistrano | ||
module Datadog | ||
class LogCapture < SimpleDelegator | ||
def puts(message) | ||
Capistrano::Datadog::reporter.record_log message | ||
__getobj__.puts message | ||
end | ||
end | ||
|
||
Configuration.instance(:must_exist).load do | ||
# Wrap the existing logging target with the Datadog capture class | ||
logger.device = Datadog::LogCapture.new logger.device | ||
|
||
# Trigger the Datadog submission once all the tasks have run | ||
on :exit, "datadog:submit" | ||
namespace :datadog do | ||
desc "Submit the tasks that have run to Datadog as events" | ||
task :submit do |ns| | ||
Capistrano::Datadog.submit variables[:datadog_api_key] | ||
end | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
require "benchmark" | ||
require "sshkit/formatters/pretty" | ||
|
||
# Capistrano v3 uses Rake's DSL instead of its own | ||
|
||
module Rake | ||
class Task | ||
alias old_invoke invoke | ||
def invoke(*args) | ||
result = nil | ||
reporter = Capistrano::Datadog.reporter | ||
task_name = name | ||
reporter.current_task = task_name | ||
timing = Benchmark.measure(task_name) do | ||
result = old_invoke(*args) | ||
end | ||
reporter.record_task(task_name, timing.real, roles, | ||
Capistrano::Configuration.env.fetch(:stage)) | ||
result | ||
end | ||
end | ||
end | ||
|
||
module Capistrano | ||
module Datadog | ||
class CaptureIO | ||
def initialize(wrapped) | ||
@wrapped = wrapped | ||
end | ||
|
||
def write(*args) | ||
@wrapped.write(*args) | ||
args.each { |arg| Capistrano::Datadog.reporter.record_log(arg) } | ||
end | ||
alias :<< :write | ||
|
||
def close | ||
@wrapped.close | ||
end | ||
end | ||
end | ||
end | ||
|
||
module SSHKit | ||
module Formatter | ||
class Pretty | ||
def initialize(oio) | ||
super(Capistrano::Datadog::CaptureIO.new(oio)) | ||
end | ||
end | ||
end | ||
end | ||
|
||
at_exit do | ||
api_key = Capistrano::Configuration.env.fetch :datadog_api_key | ||
Capistrano::Datadog.submit api_key | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome