watch_list 0.1.0

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 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
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ test.rb
16
+ Robotfile
17
+ *.robot
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --require spec_helper
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in watch_list.gemspec
4
+ gemspec
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
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+ task :default => :spec
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