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 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
+