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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +18 -0
- data/README.md +9 -6
- data/alerty.gemspec +1 -1
- data/bin/alerty +1 -1
- data/example.yml +4 -2
- data/lib/alerty/cli.rb +46 -43
- data/lib/alerty/command.rb +12 -1
- data/lib/alerty/config.rb +7 -0
- data/lib/alerty/plugin/exec.rb +19 -0
- data/lib/alerty/plugin/file.rb +4 -2
- data/lib/alerty/plugin/stdout.rb +4 -2
- data/lib/alerty.rb +1 -0
- data/spec/cli_spec.rb +48 -0
- data/spec/cli_spec.yml +6 -0
- data/spec/command_spec.rb +53 -0
- data/spec/config_spec.rb +61 -0
- data/spec/spec_helper.rb +10 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37200c7df937b8b93002b2ef0e988b49f0704482
|
4
|
+
data.tar.gz: 02a87bf6ae0a11ad405f2a9e9c518cdaa3b613d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3970c46b9de3142aa6d459de8657a8ffc86c3d1e5bd9219d47700e8918e27b1b3252a5ade20b149217b047fb81ea3fea5d2f15eb5ae75a18ec625c7ef0e688f3
|
7
|
+
data.tar.gz: 6c90bdd66aa9714bfbc48cc80c7d54750e7666b4d2df64942225aa6600e15664db6e29dde59f34149e9ed9f25732f4afb05f4da929f060092b3f6d9b2d46a411
|
data/.gitignore
CHANGED
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
|
-
|
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
|
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
data/bin/alerty
CHANGED
data/example.yml
CHANGED
data/lib/alerty/cli.rb
CHANGED
@@ -2,54 +2,57 @@ require 'optparse'
|
|
2
2
|
require_relative '../alerty'
|
3
3
|
|
4
4
|
class Alerty
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
38
|
-
usage "No command is given"
|
42
|
+
opts
|
39
43
|
end
|
40
44
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
data/lib/alerty/command.rb
CHANGED
@@ -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(
|
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
@@ -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
|
data/lib/alerty/plugin/file.rb
CHANGED
@@ -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(
|
11
|
+
def alert(record)
|
10
12
|
::File.open(@path, 'a') do |io|
|
11
|
-
io.puts
|
13
|
+
io.puts record.to_json
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/alerty/plugin/stdout.rb
CHANGED
data/lib/alerty.rb
CHANGED
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,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
|
data/spec/config_spec.rb
ADDED
@@ -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.
|
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-
|
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
|