alerty 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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