fyi 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+ require 'fyi'
4
+ Fyi.run ARGV.join(' ')
@@ -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
@@ -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
+