fyi 1.0.2
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.
- data/.gitignore +1 -0
- data/README.md +80 -0
- data/Rakefile +17 -0
- data/VERSION +1 -0
- data/bin/fyi +4 -0
- data/config_example.yml +20 -0
- data/fyi.gemspec +55 -0
- data/lib/fyi/config.rb +29 -0
- data/lib/fyi/core_ext.rb +44 -0
- data/lib/fyi/notifiers/email.rb +85 -0
- data/lib/fyi/notifiers/log.rb +49 -0
- data/lib/fyi.rb +70 -0
- metadata +85 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# FYI
|
2
|
+
|
3
|
+
Find out what cron is doing.
|
4
|
+
|
5
|
+
|
6
|
+
## Synopsis
|
7
|
+
|
8
|
+
fyi some_command
|
9
|
+
|
10
|
+
|
11
|
+
## Examples
|
12
|
+
|
13
|
+
fyi echo $PATH
|
14
|
+
fyi "ls -lt | grep total"
|
15
|
+
fyi "cd /var/www/apps/current && /opt/ree/bin/rake RAILS_ENV=production thinking_sphinx:index"
|
16
|
+
|
17
|
+
|
18
|
+
## Description
|
19
|
+
|
20
|
+
The `fyi` command executes `some_command` and tells you what
|
21
|
+
happened. This is useful when `some_command` is executed
|
22
|
+
asynchronously, e.g. via cron, and you want to know how it
|
23
|
+
went without cluttering up your crontab with pipe redirections.
|
24
|
+
|
25
|
+
When `fyi` executes `some_command` it captures standard out,
|
26
|
+
standard error, and whether `some_command` succeeded or failed.
|
27
|
+
These are then reported by any notifiers you have configured.
|
28
|
+
Success is defined by a 0 exit code and failure by a non-zero
|
29
|
+
exit code.
|
30
|
+
|
31
|
+
|
32
|
+
## Notifiers
|
33
|
+
|
34
|
+
The default notifier is the `Log` notifier. This writes to
|
35
|
+
`fyi.log` in the process's home directory unless you configure
|
36
|
+
it otherwise (see below).
|
37
|
+
|
38
|
+
One other notifier is currently available: the `Email` notifier.
|
39
|
+
Simply configure it (see below) to use it. You can switch
|
40
|
+
success notifications on and off and failure notifications
|
41
|
+
on and off independently. By default this notifier will only
|
42
|
+
email you when `some_command` fails.
|
43
|
+
|
44
|
+
To provide additional notifiers, e.g. Campfire / HTTP / Jabber,
|
45
|
+
add a class in `lib/notifiers/` and configure it (see below).
|
46
|
+
`fyi` will automatically instantiate it, configure it and use
|
47
|
+
it.
|
48
|
+
|
49
|
+
A notifier must:
|
50
|
+
|
51
|
+
* subclass Fyi::Notifier
|
52
|
+
* accept an options hash at initialisation (populated from
|
53
|
+
configuration).
|
54
|
+
* respond to `notify(command, result, duration, output, error = '')`
|
55
|
+
|
56
|
+
|
57
|
+
## Installation
|
58
|
+
|
59
|
+
sudo gem install fyi
|
60
|
+
|
61
|
+
|
62
|
+
## Configuration
|
63
|
+
|
64
|
+
Configure `fyi` with a YAML file at `<home>/.fyi`, where
|
65
|
+
`<home>` is the process's home directory.
|
66
|
+
|
67
|
+
Each top-level key should be the name of a notifier class.
|
68
|
+
The key-value pairs in each notifier section are passed in
|
69
|
+
a hash to the notifier class at instantiation.
|
70
|
+
|
71
|
+
|
72
|
+
## Problems
|
73
|
+
|
74
|
+
Please use GitHub's [issue tracker](http://github.com/airblade/fyi/issues).
|
75
|
+
|
76
|
+
|
77
|
+
## Intellectual Property
|
78
|
+
|
79
|
+
Copyright (c) 2009 Andy Stewart (boss@airbladesoftware.com).
|
80
|
+
Released under the MIT licence.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = 'fyi'
|
7
|
+
gemspec.summary = 'Be informed about task execution.'
|
8
|
+
gemspec.email = 'boss@airbladesoftware.com'
|
9
|
+
gemspec.homepage = 'http://github.com/airblade/fyi'
|
10
|
+
gemspec.authors = ['Andy Stewart']
|
11
|
+
gemspec.add_dependency('pony')
|
12
|
+
gemspec.add_dependency('open4')
|
13
|
+
end
|
14
|
+
Jeweler::GemcutterTasks.new
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
17
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.2
|
data/bin/fyi
ADDED
data/config_example.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#
|
2
|
+
# Move this to ~/.fyi
|
3
|
+
#
|
4
|
+
|
5
|
+
log:
|
6
|
+
file: /var/log/fyi.log
|
7
|
+
email:
|
8
|
+
# By default the email notifier will notify you about failures
|
9
|
+
# but won't notify you about successes. The line below turns
|
10
|
+
# on success notification; remove it if you don't want to know.
|
11
|
+
on_success: yes
|
12
|
+
from: fyi@yourdomain.com
|
13
|
+
to: you@yourdomain.com
|
14
|
+
smtp:
|
15
|
+
host: smtp.yourserver.com
|
16
|
+
port: 25
|
17
|
+
user: username
|
18
|
+
password: password
|
19
|
+
auth: :login
|
20
|
+
domain: yourdomain.com
|
data/fyi.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{fyi}
|
8
|
+
s.version = "1.0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Andy Stewart"]
|
12
|
+
s.date = %q{2009-11-24}
|
13
|
+
s.default_executable = %q{fyi}
|
14
|
+
s.email = %q{boss@airbladesoftware.com}
|
15
|
+
s.executables = ["fyi"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"README.md",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"bin/fyi",
|
25
|
+
"config_example.yml",
|
26
|
+
"fyi.gemspec",
|
27
|
+
"lib/fyi.rb",
|
28
|
+
"lib/fyi/config.rb",
|
29
|
+
"lib/fyi/core_ext.rb",
|
30
|
+
"lib/fyi/notifiers/email.rb",
|
31
|
+
"lib/fyi/notifiers/log.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/airblade/fyi}
|
34
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.3.5}
|
37
|
+
s.summary = %q{Be informed about task execution.}
|
38
|
+
|
39
|
+
if s.respond_to? :specification_version then
|
40
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
41
|
+
s.specification_version = 3
|
42
|
+
|
43
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
44
|
+
s.add_runtime_dependency(%q<pony>, [">= 0"])
|
45
|
+
s.add_runtime_dependency(%q<open4>, [">= 0"])
|
46
|
+
else
|
47
|
+
s.add_dependency(%q<pony>, [">= 0"])
|
48
|
+
s.add_dependency(%q<open4>, [">= 0"])
|
49
|
+
end
|
50
|
+
else
|
51
|
+
s.add_dependency(%q<pony>, [">= 0"])
|
52
|
+
s.add_dependency(%q<open4>, [">= 0"])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/lib/fyi/config.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Fyi
|
2
|
+
class Config
|
3
|
+
|
4
|
+
def notifiers
|
5
|
+
config
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def config
|
11
|
+
defaults = { 'log' => {} }
|
12
|
+
conf = YAML::load(config_file) rescue {}
|
13
|
+
defaults.merge(conf)
|
14
|
+
end
|
15
|
+
|
16
|
+
def config_file
|
17
|
+
File.read config_file_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def config_file_path
|
21
|
+
File.join home_dir, '.fyi'
|
22
|
+
end
|
23
|
+
|
24
|
+
def home_dir
|
25
|
+
ENV['HOME']
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/fyi/core_ext.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Lifted from ActiveSupport.
|
2
|
+
#
|
3
|
+
# Ruby 1.9 introduces an inherit argument for Module#const_get and
|
4
|
+
# #const_defined? and changes their default behavior.
|
5
|
+
if Module.method(:const_get).arity == 1
|
6
|
+
# Tries to find a constant with the name specified in the argument string:
|
7
|
+
#
|
8
|
+
# "Module".constantize # => Module
|
9
|
+
# "Test::Unit".constantize # => Test::Unit
|
10
|
+
#
|
11
|
+
# The name is assumed to be the one of a top-level constant, no matter whether
|
12
|
+
# it starts with "::" or not. No lexical context is taken into account:
|
13
|
+
#
|
14
|
+
# C = 'outside'
|
15
|
+
# module M
|
16
|
+
# C = 'inside'
|
17
|
+
# C # => 'inside'
|
18
|
+
# "C".constantize # => 'outside', same as ::C
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# NameError is raised when the name is not in CamelCase or the constant is
|
22
|
+
# unknown.
|
23
|
+
def constantize(camel_cased_word)
|
24
|
+
names = camel_cased_word.split('::')
|
25
|
+
names.shift if names.empty? || names.first.empty?
|
26
|
+
|
27
|
+
constant = Object
|
28
|
+
names.each do |name|
|
29
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
30
|
+
end
|
31
|
+
constant
|
32
|
+
end
|
33
|
+
else
|
34
|
+
def constantize(camel_cased_word) #:nodoc:
|
35
|
+
names = camel_cased_word.split('::')
|
36
|
+
names.shift if names.empty? || names.first.empty?
|
37
|
+
|
38
|
+
constant = Object
|
39
|
+
names.each do |name|
|
40
|
+
constant = constant.const_get(name, false) || constant.const_missing(name)
|
41
|
+
end
|
42
|
+
constant
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'pony'
|
2
|
+
|
3
|
+
class Fyi
|
4
|
+
class Notifier
|
5
|
+
# Emails the results of command execution via SMTP.
|
6
|
+
#
|
7
|
+
# By default only failures are emailed.
|
8
|
+
class Email
|
9
|
+
|
10
|
+
# Options you may supply:
|
11
|
+
#
|
12
|
+
# +from+: the from address
|
13
|
+
# +to+: the to address
|
14
|
+
# +on_success+: whether to notify when the command succeeded.
|
15
|
+
# Optional. Defaults to false.
|
16
|
+
# +on_failure+: whether to notify when the command failed.
|
17
|
+
# Optional. Defaults to true.
|
18
|
+
# +smtp+: you should supply SMTP config options under this key.
|
19
|
+
#
|
20
|
+
# SMTP config options are:
|
21
|
+
# +host+
|
22
|
+
# +port+
|
23
|
+
# +user+
|
24
|
+
# +password+
|
25
|
+
# +auth+
|
26
|
+
# +domain+
|
27
|
+
def initialize options
|
28
|
+
@from = options['from']
|
29
|
+
@to = options['to']
|
30
|
+
@smtp = symbolize_keys options['smtp']
|
31
|
+
@on_success = options['on_success']
|
32
|
+
# Notify of failures by default.
|
33
|
+
@on_failure = options.has_key?('on_failure') ? options['on_failure'] : true
|
34
|
+
end
|
35
|
+
|
36
|
+
def notify command, result, duration, output, error = ''
|
37
|
+
send_email(command, result, duration, output, error) if should_notify?(result)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def should_notify?(result)
|
43
|
+
(result == :success && @on_success) || (result == :failure && @on_failure)
|
44
|
+
end
|
45
|
+
|
46
|
+
def send_email command, result, duration, output, error
|
47
|
+
Pony.mail :to => @to,
|
48
|
+
:from => @from,
|
49
|
+
:subject => subject(command, result),
|
50
|
+
:body => body(command, duration, output, error),
|
51
|
+
:via => :smtp,
|
52
|
+
:smtp => @smtp
|
53
|
+
end
|
54
|
+
|
55
|
+
def subject command, result
|
56
|
+
"[#{result.to_s.upcase}] #{truncate command}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def body command, duration, output, error
|
60
|
+
<<END
|
61
|
+
command: #{command}
|
62
|
+
|
63
|
+
duration: #{duration}s
|
64
|
+
|
65
|
+
stdout: #{output}
|
66
|
+
|
67
|
+
stderr: #{error}
|
68
|
+
END
|
69
|
+
end
|
70
|
+
|
71
|
+
def truncate string, length = 30
|
72
|
+
if string.length > length
|
73
|
+
"#{string[0..(length - 3)]}..."
|
74
|
+
else
|
75
|
+
string
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def symbolize_keys hsh
|
80
|
+
hsh.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Fyi
|
4
|
+
class Notifier
|
5
|
+
# Logs command execution to a file.
|
6
|
+
#
|
7
|
+
# The log level depends on the execution result: INFO if the
|
8
|
+
# command succeeded, WARN if it failed.
|
9
|
+
class Log
|
10
|
+
attr_reader :logger
|
11
|
+
|
12
|
+
# Options you can supply:
|
13
|
+
# +file+: full path to a log file. Defaults to +fyi.log+ in
|
14
|
+
# the process's home directory.
|
15
|
+
def initialize options
|
16
|
+
log_file = options['file'] || default_log_file
|
17
|
+
@logger = Logger.new log_file
|
18
|
+
end
|
19
|
+
|
20
|
+
def notify command, result, duration, output, error = ''
|
21
|
+
logger.log severity(result), message(command, result, duration, output, error)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def severity result
|
27
|
+
result == :success ? Logger::INFO : Logger::WARN
|
28
|
+
end
|
29
|
+
|
30
|
+
def message command, result, duration, output, error
|
31
|
+
<<END
|
32
|
+
command: #{command}
|
33
|
+
duration: #{duration}s
|
34
|
+
status: #{result.to_s.upcase}
|
35
|
+
stdout: #{output}
|
36
|
+
stderr: #{error}
|
37
|
+
END
|
38
|
+
end
|
39
|
+
|
40
|
+
def default_log_file
|
41
|
+
File.join home_dir, 'fyi.log'
|
42
|
+
end
|
43
|
+
|
44
|
+
def home_dir
|
45
|
+
ENV['HOME']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/fyi.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'open4'
|
3
|
+
require 'fyi/config'
|
4
|
+
require 'fyi/core_ext'
|
5
|
+
|
6
|
+
#
|
7
|
+
# See /bin/fyi for documentation.
|
8
|
+
#
|
9
|
+
class Fyi
|
10
|
+
def self.run(command)
|
11
|
+
new(command).run
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(command)
|
15
|
+
@command = command
|
16
|
+
@config = Config.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
start_stopwatch
|
21
|
+
# Borrowed from CI Joe.
|
22
|
+
out, err, status = '', '', nil
|
23
|
+
status = Open4.popen4(@command) do |@pid, stdin, stdout, stderr|
|
24
|
+
err, out = stderr.read.strip, stdout.read.strip
|
25
|
+
end
|
26
|
+
status.exitstatus.to_i == 0 ? run_succeeded(out) : run_failed(out, err)
|
27
|
+
rescue Object => e
|
28
|
+
run_failed('', e.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def run_succeeded output
|
34
|
+
stop_stopwatch
|
35
|
+
notify :success, duration, output
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_failed output, error
|
39
|
+
stop_stopwatch
|
40
|
+
notify :failure, duration, output, error
|
41
|
+
end
|
42
|
+
|
43
|
+
def notify result, duration, output, error = ''
|
44
|
+
notifiers.each do |notifier|
|
45
|
+
notifier.notify @command, result, duration, output, error
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Instantiate and configure notifiers as per the config.
|
50
|
+
def notifiers
|
51
|
+
@config.notifiers.map do |klass_name, options|
|
52
|
+
require "fyi/notifiers/#{klass_name}"
|
53
|
+
klass = constantize "Fyi::Notifier::#{klass_name.capitalize}"
|
54
|
+
klass.send :new, options
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def duration
|
59
|
+
@stop - @start
|
60
|
+
end
|
61
|
+
|
62
|
+
def start_stopwatch
|
63
|
+
@start = Time.now
|
64
|
+
end
|
65
|
+
|
66
|
+
def stop_stopwatch
|
67
|
+
@stop = Time.now
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fyi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andy Stewart
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-24 00:00:00 +00:00
|
13
|
+
default_executable: fyi
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: pony
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: open4
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: boss@airbladesoftware.com
|
37
|
+
executables:
|
38
|
+
- fyi
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README.md
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- README.md
|
46
|
+
- Rakefile
|
47
|
+
- VERSION
|
48
|
+
- bin/fyi
|
49
|
+
- config_example.yml
|
50
|
+
- fyi.gemspec
|
51
|
+
- lib/fyi.rb
|
52
|
+
- lib/fyi/config.rb
|
53
|
+
- lib/fyi/core_ext.rb
|
54
|
+
- lib/fyi/notifiers/email.rb
|
55
|
+
- lib/fyi/notifiers/log.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://github.com/airblade/fyi
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options:
|
62
|
+
- --charset=UTF-8
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.5
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Be informed about task execution.
|
84
|
+
test_files: []
|
85
|
+
|