watch_list 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +99 -0
- data/Rakefile +5 -0
- data/bin/watch_list +122 -0
- data/lib/watch_list.rb +27 -0
- data/lib/watch_list/client.rb +156 -0
- data/lib/watch_list/constants.rb +16 -0
- data/lib/watch_list/driver.rb +146 -0
- data/lib/watch_list/dsl.rb +11 -0
- data/lib/watch_list/dsl/context.rb +40 -0
- data/lib/watch_list/dsl/context/alert_contact.rb +33 -0
- data/lib/watch_list/dsl/context/monitor.rb +82 -0
- data/lib/watch_list/dsl/context/monitor/http.rb +13 -0
- data/lib/watch_list/dsl/context/monitor/keyword.rb +30 -0
- data/lib/watch_list/dsl/context/monitor/ping.rb +4 -0
- data/lib/watch_list/dsl/context/monitor/port.rb +30 -0
- data/lib/watch_list/dsl/context/monitor/type.rb +24 -0
- data/lib/watch_list/dsl/converter.rb +107 -0
- data/lib/watch_list/exporter.rb +145 -0
- data/lib/watch_list/ext/string_ext.rb +25 -0
- data/lib/watch_list/logger.rb +30 -0
- data/lib/watch_list/utils.rb +14 -0
- data/lib/watch_list/version.rb +3 -0
- data/spec/alert_contact_spec.rb +153 -0
- data/spec/monitor_spec.rb +303 -0
- data/spec/paused_spec.rb +69 -0
- data/spec/spec_helper.rb +149 -0
- data/spec/status_spec.rb +31 -0
- data/watch_list.gemspec +27 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 39b08a35fba945bc674c19dfa99ae9d5b95a04aa
|
4
|
+
data.tar.gz: 82e8aee260fb85a80e6571e24f9ad48113e69d17
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 044e5aad31ee6923634bb761da3b0ddcc41161beb267c034c70afe78240802ae020536a9e8e7bfa5ba81762a35810634ae746fc660f57d8a391a23eb0365c567
|
7
|
+
data.tar.gz: f7924b7d7588da20d96ae02519538c577c6fec9f2d872109c4f27906d216db55467b716ae64537aa07b0cf6423f5e219fd036a599c15f4dddcbd6892db47ce81
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.0.0
|
4
|
+
script:
|
5
|
+
- bundle install
|
6
|
+
- bundle exec rake
|
7
|
+
env:
|
8
|
+
global:
|
9
|
+
- secure: "Zb/sZn5bAwqnUhSma4BqQFVaVO9wAdjlTUpl4aHLlVp9V+pRu4NVunEsvtTIZ0Y759Jw2s8BZzM7S8O2vcOSDf4D70RSrfKjyiQzBwK2NlwonAvN2lcD3AptBDxpXbvfLL2K7jSSM4/c8IUQjtJdYQooJ2nJWt/R4OTokHpp+sA="
|
10
|
+
- secure: "RxyKOUCe0+BgXThMPrHUYkddMd3mafV97qiXZfCzrP9X/kKEOTg+wNiR3Qkowjh6Ou9ultoXwEfBlwlQ44f9y/VV3qeaHjtoSgH7NVXRzEp5jLnEWyTIBwj4o4BEYHJNKRzGDsWC5TTkRgQQCOfwKeUlKPXTtx1tcN3dZtAEPqY="
|
11
|
+
- secure: "NYWq1B0s381EcvtHX2r3G8Oe3ZVfgRRwneQDI+G9HR2QIIM0sN+49agp5kSXGplSrNX2Swt7cFERPQ4YZ3o2vSnX90o7lnqZcfCh8bmOaLeV38AHlsOb5Rf9tyUyQPp+NyYUHNngUxdzrM+j8csTKwi5O+KpZKC2YedyM86BIso="
|
12
|
+
- secure: "Q2jYtrBEThr72h2HAImZXQuKbPltat5MbHt8DEl/oksv8bzqZbD5HxWVVQIBkkmKp9aefPdbDJ1hV5Y2zyo3KjYFCrXSQox11Lq3n4I0G3jaK8Ge2K+y1p/q55CBWTPOJrzHdgfEivXVwzYRvl6RuSqFVwiTdTB6kltxSxAOh4g="
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Genki Sugawara
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# watch_list
|
2
|
+
|
3
|
+
watch_list is a tool to manage [Uptime Robot](https://uptimerobot.com/).
|
4
|
+
|
5
|
+
It defines Uptime Robot monitors using Ruby DSL, and updates monitors according to DSL.
|
6
|
+
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/watch_list.svg)](http://badge.fury.io/rb/watch_list)
|
8
|
+
[![Build Status](https://travis-ci.org/winebarrel/watch_list.svg?branch=master)](https://travis-ci.org/winebarrel/watch_list)
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'watch_list'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install watch_list
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
```sh
|
29
|
+
watch_list -e -o Robotfile
|
30
|
+
vi Robotfile
|
31
|
+
watch_list -a --dry-run
|
32
|
+
watch_list -a
|
33
|
+
```
|
34
|
+
|
35
|
+
## Help
|
36
|
+
|
37
|
+
```
|
38
|
+
Usage: watch_list [options]
|
39
|
+
--api-key API_KEY
|
40
|
+
-a, --apply
|
41
|
+
-f, --file FILE
|
42
|
+
--dry-run
|
43
|
+
-e, --export
|
44
|
+
--split
|
45
|
+
-o, --output FILE
|
46
|
+
-s, --status
|
47
|
+
--no-color
|
48
|
+
--debug
|
49
|
+
-h, --help
|
50
|
+
```
|
51
|
+
|
52
|
+
## Robotfile example
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
monitor "http monitor" do
|
56
|
+
target "http://example.com"
|
57
|
+
interval 5
|
58
|
+
paused false
|
59
|
+
alert_contact :email, "alice@example.com"
|
60
|
+
type :http
|
61
|
+
end
|
62
|
+
|
63
|
+
monitor "keyword monitor" do
|
64
|
+
target "http://example.com"
|
65
|
+
interval 5
|
66
|
+
paused false
|
67
|
+
alert_contact :email, "alice@example.com"
|
68
|
+
|
69
|
+
type :keyword do
|
70
|
+
keywordtype :exists
|
71
|
+
keywordvalue "Example Domain"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
monitor "ping monitor" do
|
76
|
+
target "127.0.0.1"
|
77
|
+
interval 5
|
78
|
+
paused false
|
79
|
+
alert_contact :email, "alice@example.com"
|
80
|
+
type :ping
|
81
|
+
end
|
82
|
+
|
83
|
+
monitor "port monitor" do
|
84
|
+
target "example.com"
|
85
|
+
interval 5
|
86
|
+
paused false
|
87
|
+
alert_contact :email, "alice@example.com"
|
88
|
+
|
89
|
+
type :port do
|
90
|
+
subtype :http
|
91
|
+
port 80
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
alert_contact do
|
96
|
+
type :email
|
97
|
+
value "alice@example.com"
|
98
|
+
end
|
99
|
+
```
|
data/Rakefile
ADDED
data/bin/watch_list
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path("#{File.dirname __FILE__}/../lib")
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'watch_list'
|
6
|
+
require 'optparse'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
Version = WatchList::VERSION
|
10
|
+
DEFAULT_FILENAME = 'Robotfile'
|
11
|
+
|
12
|
+
mode = nil
|
13
|
+
file = DEFAULT_FILENAME
|
14
|
+
output_file = '-'
|
15
|
+
split = false
|
16
|
+
|
17
|
+
options = {
|
18
|
+
:apiKey => ENV['WATCH_LIST_API_KEY'],
|
19
|
+
:dry_run => false,
|
20
|
+
:color => true,
|
21
|
+
:debug => false,
|
22
|
+
}
|
23
|
+
|
24
|
+
ARGV.options do |opt|
|
25
|
+
begin
|
26
|
+
opt.on('', '--api-key API_KEY') {|v| options[:apiKey] = v }
|
27
|
+
opt.on('-a', '--apply') { mode = :apply }
|
28
|
+
opt.on('-f', '--file FILE') {|v| file = v }
|
29
|
+
opt.on('' , '--dry-run') { options[:dry_run] = true }
|
30
|
+
opt.on('-e', '--export') { mode = :export }
|
31
|
+
opt.on('' , '--split') { split = true }
|
32
|
+
opt.on('-o', '--output FILE') {|v| output_file = v }
|
33
|
+
opt.on('-s', '--status') { mode = :status }
|
34
|
+
opt.on('' , '--no-color') { options[:color] = false }
|
35
|
+
opt.on('' , '--debug') { options[:debug] = true }
|
36
|
+
|
37
|
+
opt.on('-h', '--help') do
|
38
|
+
puts opt.help
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
opt.parse!
|
43
|
+
|
44
|
+
unless mode
|
45
|
+
puts opt.help
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
|
49
|
+
raise 'apiKey is required' unless options[:apiKey]
|
50
|
+
rescue => e
|
51
|
+
$stderr.puts("[ERROR] #{e.message}")
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
String.colorize = options[:color]
|
57
|
+
|
58
|
+
begin
|
59
|
+
logger = WatchList::Logger.instance
|
60
|
+
logger.set_debug(options[:debug])
|
61
|
+
client = WatchList::Client.new(options)
|
62
|
+
|
63
|
+
case mode
|
64
|
+
when :export
|
65
|
+
if split
|
66
|
+
logger.info('Export Uptime Robot')
|
67
|
+
output_file = DEFAULT_FILENAME if output_file == '-'
|
68
|
+
requires = []
|
69
|
+
|
70
|
+
client.export do |name, dsl|
|
71
|
+
robotfile = File.join(File.dirname(output_file), "#{name}.robot")
|
72
|
+
requires << robotfile
|
73
|
+
logger.info(" write `#{robotfile}`")
|
74
|
+
|
75
|
+
open(robotfile, 'wb') do |f|
|
76
|
+
f.puts dsl
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
logger.info(" write `#{output_file}`")
|
81
|
+
|
82
|
+
open(output_file, 'wb') do |f|
|
83
|
+
requires.each do |robotfile|
|
84
|
+
f.puts "require '#{File.basename robotfile}'"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
else
|
88
|
+
if output_file == '-'
|
89
|
+
logger.info('# Export Uptime Robot')
|
90
|
+
puts client.export
|
91
|
+
else
|
92
|
+
logger.info("Export Uptime Robot to `#{output_file}`")
|
93
|
+
open(output_file, 'wb') {|f| f.puts client.export }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
when :apply
|
97
|
+
unless File.exist?(file)
|
98
|
+
raise "No Robotfile found (looking for: #{file})"
|
99
|
+
end
|
100
|
+
|
101
|
+
msg = "Apply `#{file}` to Uptime Robot"
|
102
|
+
msg << ' (dry-run)' if options[:dry_run]
|
103
|
+
logger.info(msg)
|
104
|
+
|
105
|
+
updated = client.apply(file)
|
106
|
+
|
107
|
+
if updated
|
108
|
+
logger.warn('[WARN] Applying changes takes about 30 seconds'.yellow)
|
109
|
+
else
|
110
|
+
logger.info('No change'.intense_blue)
|
111
|
+
end
|
112
|
+
when :status
|
113
|
+
puts JSON.pretty_generate(client.status)
|
114
|
+
end
|
115
|
+
rescue => e
|
116
|
+
if options[:debug]
|
117
|
+
raise e
|
118
|
+
else
|
119
|
+
$stderr.puts("[ERROR] #{e.message}".red)
|
120
|
+
exit 1
|
121
|
+
end
|
122
|
+
end
|
data/lib/watch_list.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'singleton'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'term/ansicolor'
|
5
|
+
require 'uptimerobot'
|
6
|
+
|
7
|
+
module WatchList; end
|
8
|
+
|
9
|
+
require 'watch_list/utils'
|
10
|
+
require 'watch_list/constants'
|
11
|
+
require 'watch_list/logger'
|
12
|
+
|
13
|
+
require 'watch_list/client'
|
14
|
+
require 'watch_list/driver'
|
15
|
+
require 'watch_list/dsl'
|
16
|
+
require 'watch_list/dsl/context'
|
17
|
+
require 'watch_list/dsl/context/alert_contact'
|
18
|
+
require 'watch_list/dsl/context/monitor'
|
19
|
+
require 'watch_list/dsl/context/monitor/type'
|
20
|
+
require 'watch_list/dsl/context/monitor/http'
|
21
|
+
require 'watch_list/dsl/context/monitor/keyword'
|
22
|
+
require 'watch_list/dsl/context/monitor/port'
|
23
|
+
require 'watch_list/dsl/context/monitor/ping'
|
24
|
+
require 'watch_list/dsl/converter'
|
25
|
+
require 'watch_list/exporter'
|
26
|
+
require 'watch_list/ext/string_ext'
|
27
|
+
require 'watch_list/version'
|
@@ -0,0 +1,156 @@
|
|
1
|
+
class WatchList::Client
|
2
|
+
MONITOR_ATTRS = [
|
3
|
+
:URL,
|
4
|
+
:Type,
|
5
|
+
:SubType,
|
6
|
+
:Port,
|
7
|
+
:KeywordType,
|
8
|
+
:KeywordValue,
|
9
|
+
:HTTPUsername,
|
10
|
+
:HTTPPassword,
|
11
|
+
:AlertContacts,
|
12
|
+
:Interval,
|
13
|
+
]
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@options = options
|
17
|
+
@uptimerobot = UptimeRobot::Client.new(:apiKey => options[:apiKey])
|
18
|
+
@driver = WatchList::Driver.new(@uptimerobot, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def export
|
22
|
+
exported = WatchList::Exporter.export(@uptimerobot, @options)
|
23
|
+
|
24
|
+
if block_given?
|
25
|
+
exported.each do |name, attrs|
|
26
|
+
dsl = WatchList::DSL.convert({name => attrs}, @options).strip
|
27
|
+
yield(name, dsl) unless dsl.empty?
|
28
|
+
end
|
29
|
+
else
|
30
|
+
WatchList::DSL.convert(exported, @options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def status
|
35
|
+
WatchList::Exporter.export_status(@uptimerobot, @options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def apply(file)
|
39
|
+
walk(file)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def walk(file)
|
45
|
+
expected = load_file(file)
|
46
|
+
actual = WatchList::Exporter.export(@uptimerobot, @options)
|
47
|
+
|
48
|
+
updated = walk_alert_contacts(expected[:alert_contacts], actual[:alert_contacts])
|
49
|
+
walk_monitors(expected[:monitors], actual[:monitors], expected[:alert_contacts]) || updated
|
50
|
+
end
|
51
|
+
|
52
|
+
def walk_alert_contacts(expected, actual)
|
53
|
+
updated = false
|
54
|
+
|
55
|
+
expected.each do |expected_alert_contact|
|
56
|
+
selector = proc do |i|
|
57
|
+
i.values_at(:Type, :Value) == expected_alert_contact.values_at(:Type, :Value)
|
58
|
+
end
|
59
|
+
|
60
|
+
actual_alert_contact = actual.find(&selector)
|
61
|
+
|
62
|
+
if actual_alert_contact
|
63
|
+
expected_alert_contact[:ID] = actual_alert_contact[:ID]
|
64
|
+
actual.delete_if(&selector)
|
65
|
+
else
|
66
|
+
updated = @driver.new_alert_contact(expected_alert_contact) || updated
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
actual.each do |alert_contact|
|
71
|
+
updated = @driver.delete_alert_contact(alert_contact) || updated
|
72
|
+
end
|
73
|
+
|
74
|
+
updated
|
75
|
+
end
|
76
|
+
|
77
|
+
def walk_monitors(expected, actual, alert_contacts)
|
78
|
+
updated = false
|
79
|
+
|
80
|
+
expected.each do |friendlyname, expected_monitor|
|
81
|
+
actual_monitor = actual.delete(friendlyname)
|
82
|
+
|
83
|
+
if actual_monitor
|
84
|
+
expected_monitor[:ID] = actual_monitor[:ID]
|
85
|
+
updated = walk_monitor(expected_monitor, actual_monitor, alert_contacts) || updated
|
86
|
+
else
|
87
|
+
updated = @driver.new_monitor(expected_monitor, alert_contacts) || updated
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
actual.each do |friendlyname, monitor|
|
92
|
+
updated = @driver.delete_monitor(monitor) || updated
|
93
|
+
end
|
94
|
+
|
95
|
+
updated
|
96
|
+
end
|
97
|
+
|
98
|
+
def walk_monitor(expected, actual, alert_contacts)
|
99
|
+
updated = false
|
100
|
+
delta = diff_monitor(expected, actual)
|
101
|
+
|
102
|
+
unless delta.empty?
|
103
|
+
updated = @driver.edit_monitor(expected[:ID], expected[:FriendlyName], delta, alert_contacts)
|
104
|
+
end
|
105
|
+
|
106
|
+
walk_monitor_paused(expected, actual) || updated
|
107
|
+
end
|
108
|
+
|
109
|
+
def walk_monitor_paused(expected, actual)
|
110
|
+
updated = false
|
111
|
+
expected_paused = !!expected[:Paused]
|
112
|
+
actual_pauced = (actual[:Status] == UptimeRobot::Monitor::Status::Paused)
|
113
|
+
|
114
|
+
if expected_paused != actual_pauced
|
115
|
+
updated = @driver.pause_monitor(expected[:ID], expected[:FriendlyName], expected_paused)
|
116
|
+
end
|
117
|
+
|
118
|
+
updated
|
119
|
+
end
|
120
|
+
|
121
|
+
def diff_monitor(expected, actual)
|
122
|
+
delta = {}
|
123
|
+
|
124
|
+
MONITOR_ATTRS.each do |key|
|
125
|
+
expected_value = expected[key]
|
126
|
+
actual_value = actual[key]
|
127
|
+
next if expected_value.nil? && actual_value.nil?
|
128
|
+
|
129
|
+
if expected_value.kind_of?(Array)
|
130
|
+
expected_value = expected_value.sort_by {|i| i.to_s }
|
131
|
+
end
|
132
|
+
|
133
|
+
if actual_value.kind_of?(Array)
|
134
|
+
actual_value = actual_value.sort_by {|i| i.to_s }
|
135
|
+
end
|
136
|
+
|
137
|
+
if expected_value != actual_value
|
138
|
+
delta[key] = expected_value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
delta
|
143
|
+
end
|
144
|
+
|
145
|
+
def load_file(file)
|
146
|
+
if file.kind_of?(String)
|
147
|
+
open(file) do |f|
|
148
|
+
WatchList::DSL.parse(f.read, file)
|
149
|
+
end
|
150
|
+
elsif [File, Tempfile].any? {|i| file.kind_of?(i) }
|
151
|
+
WatchList::DSL.parse(file.read, file.path)
|
152
|
+
else
|
153
|
+
raise TypeError, "can't convert #{file} into File"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|