alerty 0.0.2 → 0.0.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f1c92b7d3ec37c17af10d78fba346435c7be67e
4
- data.tar.gz: 84616ba361045f8b94e544eea7626734077f212a
3
+ metadata.gz: 37200c7df937b8b93002b2ef0e988b49f0704482
4
+ data.tar.gz: 02a87bf6ae0a11ad405f2a9e9c518cdaa3b613d8
5
5
  SHA512:
6
- metadata.gz: 0479571dc19db4fb6c52f5fa1dca7cf023c15ce6947a68207ba41770027bda7fece31b4580ea11236f82e7a6ce84d3b04256026cce205956c2013a7d19e78d2c
7
- data.tar.gz: 2547fee4e93f27d12acc0bc7eceb78ba376e329bc65850d35ae013875a2ef5032b5a1ead04571384343392fc2cd9ec3cfc839b7f3131e2609f200f09ab97d2bb
6
+ metadata.gz: 3970c46b9de3142aa6d459de8657a8ffc86c3d1e5bd9219d47700e8918e27b1b3252a5ade20b149217b047fb81ea3fea5d2f15eb5ae75a18ec625c7ef0e688f3
7
+ data.tar.gz: 6c90bdd66aa9714bfbc48cc80c7d54750e7666b4d2df64942225aa6600e15664db6e29dde59f34149e9ed9f25732f4afb05f4da929f060092b3f6d9b2d46a411
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ vendor
16
16
  pkg/
17
17
  .env
18
18
  log/alerty.log
19
+ spec/cli_spec.out
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ # 0.0.3 (2015/08/14)
2
+
3
+ Enchancements:
4
+
5
+ * Add exec plugin
6
+ * Pass `hostname` to plugins
7
+
8
+ # 0.0.3 (2015/08/14)
9
+
10
+ Enchancements:
11
+
12
+ * Pass more information to plugins
13
+ * command
14
+ * exitstatus
15
+ * output
16
+ * started_at
17
+ * duration
18
+
1
19
  # 0.0.2 (2015/08/13)
2
20
 
3
21
  Enchancements:
data/README.md CHANGED
@@ -1,10 +1,14 @@
1
1
  # alerty
2
2
 
3
- Send an alert if a given command failed.
3
+ A CLI utility to send an alert if a given command failed.
4
4
 
5
5
  ## How Useful?
6
6
 
7
- I use `alerty` to run commands in cron. With `alerty`, we can send an alert if a cron command fails.
7
+ I use `alerty` to run commands in cron to send alerts if cron commands fail.
8
+
9
+ ```
10
+ 0 * * * * alerty -c /etc/sysconfig/alerty -- /path/to/script --foo FOO --bar
11
+ ```
8
12
 
9
13
  ## Installation
10
14
 
@@ -48,16 +52,15 @@ $ bin/alerty -h
48
52
 
49
53
  Following plugins are available:
50
54
 
55
+ * [stdout](./lib/alerty/plugin/stdout.rb)
56
+ * [file](./lib/alerty/plugin/file.rb)
57
+ * [exec](./lib/alerty/plugin/exec.rb)
51
58
  * [alerty-plugin-ikachan](https://github.com/sonots/alerty-plugin-ikachan)
52
59
 
53
60
  ## ChangeLog
54
61
 
55
62
  See [CHANGELOG.md](CHANGELOG.md) for details.
56
63
 
57
- ### ToDo
58
-
59
- * Add tests
60
-
61
64
  ### Licenses
62
65
 
63
66
  See [LICENSE](LICENSE)
data/alerty.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "alerty"
3
- gem.version = '0.0.2'
3
+ gem.version = '0.0.4'
4
4
  gem.author = ['Naotoshi Seo']
5
5
  gem.email = ['sonots@gmail.com']
6
6
  gem.homepage = 'https://github.com/sonots/alerty'
data/bin/alerty CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require_relative '../lib/alerty/cli'
4
- Alerty.new.run
4
+ Alerty::CLI.new.run
data/example.yml CHANGED
@@ -1,8 +1,10 @@
1
- log_path: log/alerty.log
1
+ log_path: STDOUT
2
2
  log_level: debug
3
3
  timeout: 10
4
4
  lock_path: log/lock
5
5
  plugins:
6
6
  - type: stdout
7
7
  - type: file
8
- path: log/failure.log
8
+ path: log/file.log
9
+ - type: exec
10
+ command: cat > log/exec.log
data/lib/alerty/cli.rb CHANGED
@@ -2,54 +2,57 @@ require 'optparse'
2
2
  require_relative '../alerty'
3
3
 
4
4
  class Alerty
5
- def parse_options(argv = ARGV)
6
- op = OptionParser.new
7
- op.banner += ' -- command'
8
-
9
- (class<<self;self;end).module_eval do
10
- define_method(:usage) do |msg|
11
- puts op.to_s
12
- puts "error: #{msg}" if msg
13
- exit 1
5
+ class CLI
6
+ def parse_options(argv = ARGV)
7
+ op = OptionParser.new
8
+ op.banner += ' -- command'
9
+
10
+ (class<<self;self;end).module_eval do
11
+ define_method(:usage) do |msg|
12
+ puts op.to_s
13
+ puts "error: #{msg}" if msg
14
+ exit 1
15
+ end
14
16
  end
15
- end
16
17
 
17
- opts = {}
18
- op.on('-c', '--config CONFIG_FILE', "config file path (default: /etc/sysconfig/alerty)") {|v|
19
- opts[:config_path] = v
20
- }
21
- op.on('--log LOG_FILE', "log file path (default: STDOUT)") {|v|
22
- opts[:log_path] = v
23
- }
24
- op.on('--log-level LOG_LEVEL', "log level (default: warn)") {|v|
25
- opts[:log_level] = v
26
- }
27
- op.on('-t', '--timeout SECONDS', "timeout the command (default: no timeout)") {|v|
28
- opts[:timeout] = v.to_i
29
- }
30
- op.on('-l', '--lock LOCK_FILE', "exclusive lock file to prevent running a command duplicatedly (default: no lock)") {|v|
31
- opts[:lock_path] = v
32
- }
33
-
34
- op.parse!(argv)
35
- opts[:command] = argv.join(' ')
18
+ opts = {}
19
+ op.on('-c', '--config CONFIG_FILE', "config file path (default: /etc/sysconfig/alerty)") {|v|
20
+ opts[:config_path] = v
21
+ }
22
+ op.on('--log LOG_FILE', "log file path (default: STDOUT)") {|v|
23
+ opts[:log_path] = v
24
+ }
25
+ op.on('--log-level LOG_LEVEL', "log level (default: warn)") {|v|
26
+ opts[:log_level] = v
27
+ }
28
+ op.on('-t', '--timeout SECONDS', "timeout the command (default: no timeout)") {|v|
29
+ opts[:timeout] = v.to_i
30
+ }
31
+ op.on('-l', '--lock LOCK_FILE', "exclusive lock file to prevent running a command duplicatedly (default: no lock)") {|v|
32
+ opts[:lock_path] = v
33
+ }
34
+
35
+ op.parse!(argv)
36
+ opts[:command] = argv.join(' ')
37
+
38
+ if opts[:command].empty?
39
+ raise OptionParser::InvalidOption.new("No command is given")
40
+ end
36
41
 
37
- if opts[:command].empty?
38
- usage "No command is given"
42
+ opts
39
43
  end
40
44
 
41
- opts
42
- end
43
-
44
- def run
45
- begin
46
- opts = parse_options
47
- rescue OptionParser::InvalidOption => e
48
- usage e.message
45
+ def run
46
+ begin
47
+ opts = parse_options
48
+ rescue OptionParser::InvalidOption => e
49
+ usage e.message
50
+ end
51
+
52
+ Config.configure(opts)
53
+ Config.plugins # load plugins in early stage
54
+ command = Command.new(command: opts[:command])
55
+ command.run!
49
56
  end
50
-
51
- Config.configure(opts)
52
- Config.plugins # load plugins in early stage
53
- Command.new(command: opts[:command]).run!
54
57
  end
55
58
  end
@@ -1,20 +1,31 @@
1
1
  require 'frontkick'
2
+ require 'socket'
2
3
 
3
4
  class Alerty
4
5
  class Command
5
6
  def initialize(command:)
6
7
  @command = command
7
8
  @opts = { timeout: Config.timeout, exclusive: Config.lock_path }
9
+ @hostname = Socket.gethostname
8
10
  end
9
11
 
10
12
  def run!
13
+ started_at = Time.now
11
14
  result = Frontkick.exec("#{@command} 2>&1", @opts)
12
15
  if result.success?
13
16
  exit 0
14
17
  else
18
+ record = {
19
+ hostname: @hostname,
20
+ command: @command,
21
+ exitstatus: result.exitstatus,
22
+ output: result.stdout,
23
+ started_at: started_at.to_f,
24
+ duration: result.duration,
25
+ }
15
26
  Config.plugins.each do |plugin|
16
27
  begin
17
- plugin.alert(result.stdout)
28
+ plugin.alert(record)
18
29
  rescue => e
19
30
  Alerty.logger.warn "#{e.class} #{e.message} #{e.backtrace.join("\n")}"
20
31
  end
data/lib/alerty/config.rb CHANGED
@@ -41,6 +41,13 @@ class Alerty
41
41
  Object.const_get(class_name).new(plugin)
42
42
  end
43
43
  end
44
+
45
+ # for debug
46
+ def reset
47
+ @config_path = nil
48
+ @config = nil
49
+ @plugins = nil
50
+ end
44
51
  end
45
52
  end
46
53
  end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+ require 'frontkick'
3
+ require 'shellwords'
4
+
5
+ class Alerty
6
+ class Plugin
7
+ class Exec
8
+ def initialize(config)
9
+ raise ConfigError.new("exec: command is not configured") unless config.command
10
+ @command = config.command
11
+ end
12
+
13
+ def alert(record)
14
+ Alerty.logger.info "exec: echo #{record.to_json.shellescape} | #{@command}"
15
+ Frontkick.exec("echo #{record.to_json.shellescape} | #{@command}")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,5 @@
1
+ require 'json'
2
+
1
3
  class Alerty
2
4
  class Plugin
3
5
  class File
@@ -6,9 +8,9 @@ class Alerty
6
8
  @path = config.path
7
9
  end
8
10
 
9
- def alert(msg)
11
+ def alert(record)
10
12
  ::File.open(@path, 'a') do |io|
11
- io.puts msg
13
+ io.puts record.to_json
12
14
  end
13
15
  end
14
16
  end
@@ -1,11 +1,13 @@
1
+ require 'json'
2
+
1
3
  class Alerty
2
4
  class Plugin
3
5
  class Stdout
4
6
  def initialize(config)
5
7
  end
6
8
 
7
- def alert(msg)
8
- $stdout.puts msg
9
+ def alert(record)
10
+ $stdout.puts record.to_json
9
11
  end
10
12
  end
11
13
  end
data/lib/alerty.rb CHANGED
@@ -3,6 +3,7 @@ require_relative 'alerty/error'
3
3
  require_relative 'alerty/config'
4
4
  require_relative 'alerty/command'
5
5
  require_relative 'alerty/logger'
6
+ require_relative 'alerty/cli'
6
7
 
7
8
  class Alerty
8
9
  def self.logger
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe Alerty::CLI do
5
+ CONFIG_PATH = File.join(File.dirname(__FILE__), 'cli_spec.yml')
6
+ BIN_DIR = File.join(ROOT, 'bin')
7
+ OUTPUT_FILE = File.join(File.dirname(__FILE__), 'cli_spec.out')
8
+
9
+ describe '#parse_options' do
10
+ it 'incorrect' do
11
+ expect { Alerty::CLI.new.parse_options([]) }.to raise_error(OptionParser::InvalidOption)
12
+ end
13
+
14
+ it 'command' do
15
+ expect(Alerty::CLI.new.parse_options(['--', 'ls', '-l'])[:command]).to eql('ls -l')
16
+ end
17
+
18
+ it 'config' do
19
+ expect(Alerty::CLI.new.parse_options(['-c', 'config.yml', '--', 'ls'])[:config_path]).to eql('config.yml')
20
+ end
21
+ end
22
+
23
+ describe '#run' do
24
+ context 'with success' do
25
+ before :all do
26
+ FileUtils.rm(OUTPUT_FILE, force: true)
27
+ system("#{File.join(BIN_DIR, 'alerty')} -c #{CONFIG_PATH} -- echo foo")
28
+ sleep 0.1
29
+ end
30
+
31
+ it 'should not output' do
32
+ expect(File.size?(OUTPUT_FILE)).to be_falsey
33
+ end
34
+ end
35
+
36
+ context 'with failure' do
37
+ before :all do
38
+ FileUtils.rm(OUTPUT_FILE, force: true)
39
+ system("#{File.join(BIN_DIR, 'alerty')} -c #{CONFIG_PATH} -- [ a = b ]")
40
+ sleep 0.1
41
+ end
42
+
43
+ it 'should output' do
44
+ expect(File.size?(OUTPUT_FILE)).to be_truthy
45
+ end
46
+ end
47
+ end
48
+ end
data/spec/cli_spec.yml ADDED
@@ -0,0 +1,6 @@
1
+ log_path: STDOUT
2
+ log_level: debug
3
+ timeout: 10
4
+ plugins:
5
+ - type: file
6
+ path: spec/cli_spec.out
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Alerty::Command do
4
+ describe 'run!' do
5
+ context 'Frontkick.exec' do
6
+ before do
7
+ Alerty::Config.configure(
8
+ log_path: '/tmp/foo',
9
+ log_level: 'fatal',
10
+ timeout: 20,
11
+ lock_path: '/tmp/lock',
12
+ )
13
+ end
14
+
15
+ let(:command) { Alerty::Command.new(command: 'ls') }
16
+
17
+ it do
18
+ expect(Frontkick).to receive(:exec).with("ls 2>&1", {
19
+ timeout: 20,
20
+ exclusive: '/tmp/lock',
21
+ }).and_return(Frontkick::Result.new(exit_code: 0))
22
+ expect { command.run! }.to raise_error(SystemExit)
23
+ end
24
+ end
25
+
26
+ context 'plugins.alert' do
27
+ before do
28
+ Alerty::Config.instance_variable_set(:@config, Hashie::Mash.new(
29
+ plugins: [{
30
+ type: 'stdout',
31
+ }]
32
+ ))
33
+ Alerty::Config.configure(
34
+ log_path: '/tmp/foo',
35
+ log_level: 'fatal',
36
+ timeout: nil,
37
+ lock_path: nil,
38
+ )
39
+ end
40
+
41
+ let(:command) { Alerty::Command.new(command: 'echo foo') }
42
+
43
+ it do
44
+ expect(Frontkick).to receive(:exec).with("echo foo 2>&1", {
45
+ timeout: nil,
46
+ exclusive: nil,
47
+ }).and_return(Frontkick::Result.new(stdout: 'foo', exit_code: 1))
48
+ stdout = capture_stdout { expect { command.run! }.to raise_error(SystemExit) }
49
+ expect(JSON.parse(stdout)["output"]).to eql("foo")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Alerty::Config do
4
+ describe 'configure' do
5
+ before do
6
+ Alerty::Config.configure(
7
+ log_path: '/tmp/foo',
8
+ log_level: 'fatal',
9
+ timeout: 20,
10
+ lock_path: '/tmp/lock',
11
+ )
12
+ end
13
+
14
+ it { expect(Alerty::Config.log_path).to eql('/tmp/foo') }
15
+ it { expect(Alerty::Config.log_level).to eql('fatal') }
16
+ it { expect(Alerty::Config.timeout).to eql(20) }
17
+ it { expect(Alerty::Config.lock_path).to eql('/tmp/lock') }
18
+ end
19
+
20
+ describe 'config' do
21
+ before do
22
+ Alerty::Config.instance_variable_set(:@config, Hashie::Mash.new(
23
+ log_path: '/tmp/foo',
24
+ log_level: 'fatal',
25
+ timeout: 20,
26
+ lock_path: '/tmp/lock',
27
+ ))
28
+ end
29
+
30
+ it { expect(Alerty::Config.log_path).to eql('/tmp/foo') }
31
+ it { expect(Alerty::Config.log_level).to eql('fatal') }
32
+ it { expect(Alerty::Config.timeout).to eql(20) }
33
+ it { expect(Alerty::Config.lock_path).to eql('/tmp/lock') }
34
+ end
35
+
36
+ describe 'plugins' do
37
+ before do
38
+ Alerty::Config.reset
39
+ Alerty::Config.instance_variable_set(:@config, Hashie::Mash.new(
40
+ log_path: '/tmp/foo',
41
+ log_level: 'fatal',
42
+ timeout: 20,
43
+ lock_path: '/tmp/lock',
44
+ plugins: [
45
+ {
46
+ type: 'stdout',
47
+ },
48
+ {
49
+ type: 'file',
50
+ path: 'STDOUT',
51
+ }
52
+ ]
53
+ ))
54
+ end
55
+
56
+ it do
57
+ expect(Alerty::Config.plugins[0]).to be_a(Alerty::Plugin::Stdout)
58
+ expect(Alerty::Config.plugins[1]).to be_a(Alerty::Plugin::File)
59
+ end
60
+ end
61
+ end
data/spec/spec_helper.rb CHANGED
@@ -6,5 +6,15 @@ require 'alerty'
6
6
 
7
7
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
8
8
 
9
+ ROOT = File.dirname(File.dirname(__FILE__))
10
+ def capture_stdout
11
+ out = StringIO.new
12
+ $stdout = out
13
+ yield
14
+ return out.string
15
+ ensure
16
+ $stdout = STDOUT
17
+ end
18
+
9
19
  RSpec.configure do |config|
10
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alerty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Naotoshi Seo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-13 00:00:00.000000000 Z
11
+ date: 2015-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie
@@ -145,10 +145,15 @@ files:
145
145
  - lib/alerty/config.rb
146
146
  - lib/alerty/error.rb
147
147
  - lib/alerty/logger.rb
148
+ - lib/alerty/plugin/exec.rb
148
149
  - lib/alerty/plugin/file.rb
149
150
  - lib/alerty/plugin/stdout.rb
150
151
  - lib/alerty/string_util.rb
151
152
  - log/.gitkeep
153
+ - spec/cli_spec.rb
154
+ - spec/cli_spec.yml
155
+ - spec/command_spec.rb
156
+ - spec/config_spec.rb
152
157
  - spec/spec_helper.rb
153
158
  homepage: https://github.com/sonots/alerty
154
159
  licenses:
@@ -175,4 +180,8 @@ signing_key:
175
180
  specification_version: 4
176
181
  summary: Send an alert if a given command failed
177
182
  test_files:
183
+ - spec/cli_spec.rb
184
+ - spec/cli_spec.yml
185
+ - spec/command_spec.rb
186
+ - spec/config_spec.rb
178
187
  - spec/spec_helper.rb