watch_list 0.1.0

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